diff --git a/.editorconfig b/.editorconfig index 560067027c52..1e8be1006aa9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,6 +1,6 @@ root = true -[*.{py,pyi,c,cpp,h,rst,md,yml,json,test}] +[*.{py,pyi,c,cpp,h,rst,md,yml,yaml,json,test}] trim_trailing_whitespace = true insert_final_newline = true indent_style = space @@ -8,5 +8,5 @@ indent_style = space [*.{py,pyi,c,h,json,test}] indent_size = 4 -[*.yml] +[*.{yml,yaml}] indent_size = 2 diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 043c3ac878ab..be8582a6b221 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,2 +1,8 @@ # Adopt black and isort -97c5ee99bc98dc475512e549b252b23a6e7e0997 \ No newline at end of file +97c5ee99bc98dc475512e549b252b23a6e7e0997 +# Use builtin generics and PEP 604 for type annotations wherever possible (#13427) +23ee1e7aff357e656e3102435ad0fe3b5074571e +# Use variable annotations (#10723) +f98f78216ba9d6ab68c8e69c19e9f3c7926c5efe +# run pyupgrade (#12711) +fc335cb16315964b923eb1927e3aad1516891c28 diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 64a2e6494c1b..a88773308d5e 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,6 +2,6 @@ contact_links: - about: "Please check the linked documentation page before filing new issues." name: "Common issues and solutions" url: "https://mypy.readthedocs.io/en/stable/common_issues.html" - - about: "Please ask and answer any questions on the mypy Gitter." + - about: "Please ask and answer any questions on the python/typing Gitter." name: "Questions or Chat" url: "https://gitter.im/python/typing" diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md index 135bc2bd3b94..984e552e51b1 100644 --- a/.github/ISSUE_TEMPLATE/feature.md +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -6,8 +6,8 @@ labels: "feature" **Feature** -(A clear and concise description of your feature proposal.) + **Pitch** -(Please explain why this feature should be implemented and how it would be used. Add examples, if applicable.) + diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 7edfa03584c1..8055cfd24180 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -2,18 +2,21 @@ name: Trigger wheel build on: push: - branches: [master, 'release*'] + branches: [main, master, 'release*'] tags: ['*'] +permissions: + contents: write + jobs: build-wheels: if: github.repository == 'python/mypy' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: - python-version: '3.7' + python-version: '3.11' - name: Trigger script env: WHEELS_PUSH_TOKEN: ${{ secrets.WHEELS_PUSH_TOKEN }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a3294c08a79c..f13a3de1f2e3 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,30 +3,42 @@ name: Check documentation build on: workflow_dispatch: push: - branches: [master, 'release*'] + branches: [main, master, 'release*'] tags: ['*'] pull_request: paths: - 'docs/**' + # We now have a docs check that fails if any error codes don't have documentation, + # so it's important to do the docs build on all PRs touching mypy/errorcodes.py + # in case somebody's adding a new error code without any docs + - 'mypy/errorcodes.py' - 'mypyc/doc/**' - '**/*.rst' - '**/*.md' - CREDITS - LICENSE +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: docs: runs-on: ubuntu-latest env: TOXENV: docs TOX_SKIP_MISSING_INTERPRETERS: False + VERIFY_MYPY_ERROR_CODES: 1 steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: - python-version: '3.7' + python-version: '3.8' - name: Install tox - run: pip install --upgrade 'setuptools!=50' tox==4.4.4 + run: pip install --upgrade 'setuptools!=50' tox==4.11.0 - name: Setup tox environment run: tox run -e ${{ env.TOXENV }} --notest - name: Test diff --git a/.github/workflows/mypy_primer.yml b/.github/workflows/mypy_primer.yml index e7e4af1f07b7..07a1d0863eb2 100644 --- a/.github/workflows/mypy_primer.yml +++ b/.github/workflows/mypy_primer.yml @@ -15,6 +15,9 @@ on: - 'mypy/test/**' - 'test-data/**' +permissions: + contents: read + concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true @@ -30,11 +33,11 @@ jobs: shard-index: [0, 1, 2, 3, 4] fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: path: mypy_to_test fetch-depth: 0 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install dependencies diff --git a/.github/workflows/mypy_primer_comment.yml b/.github/workflows/mypy_primer_comment.yml index 12ce91c12910..492e03aff16e 100644 --- a/.github/workflows/mypy_primer_comment.yml +++ b/.github/workflows/mypy_primer_comment.yml @@ -44,7 +44,7 @@ jobs: - name: Post comment id: post-comment - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -78,7 +78,7 @@ jobs: if (data.trim()) { body = 'Diff from [mypy_primer](https://github.com/hauntsaninja/mypy_primer), showing the effect of this PR on open source code:\n```diff\n' + data + '```' } else { - body = 'According to [mypy_primer](https://github.com/hauntsaninja/mypy_primer), this change has no effect on the checked open source code. šŸ¤–šŸŽ‰' + body = "According to [mypy_primer](https://github.com/hauntsaninja/mypy_primer), this change doesn't affect type check results on a corpus of open source code. āœ…" } const prNumber = parseInt(fs.readFileSync("pr_number.txt", { encoding: "utf8" })) await github.rest.issues.createComment({ diff --git a/.github/workflows/sync_typeshed.yml b/.github/workflows/sync_typeshed.yml index 1db2e846f099..b545e7b0662b 100644 --- a/.github/workflows/sync_typeshed.yml +++ b/.github/workflows/sync_typeshed.yml @@ -15,12 +15,12 @@ jobs: if: github.repository == 'python/mypy' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 # TODO: use whatever solution ends up working for # https://github.com/python/typeshed/issues/8434 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: "3.10" - name: git config diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ed0c82ef5fa1..98a737a78b3b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,7 +3,7 @@ name: Tests on: workflow_dispatch: push: - branches: [master, 'release*'] + branches: [main, master, 'release*'] tags: ['*'] pull_request: paths-ignore: @@ -15,6 +15,9 @@ on: - CREDITS - LICENSE +permissions: + contents: read + concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true @@ -26,75 +29,79 @@ jobs: fail-fast: false matrix: include: - - name: Test suite with py37-windows-64 - python: '3.7' - arch: x64 - os: windows-latest - toxenv: py37 - - name: Test suite with py38-ubuntu + # Make sure to run mypyc compiled unit tests for both + # the oldest and newest supported Python versions + - name: Test suite with py38-ubuntu, mypyc-compiled python: '3.8' arch: x64 os: ubuntu-latest toxenv: py - tox_extra_args: "-n 2" + tox_extra_args: "-n 4" + test_mypyc: true + - name: Test suite with py38-windows-64 + python: '3.8' + arch: x64 + os: windows-latest + toxenv: py38 + tox_extra_args: "-n 4" - name: Test suite with py39-ubuntu python: '3.9' arch: x64 os: ubuntu-latest toxenv: py - tox_extra_args: "-n 2" - - name: Test suite with py37-ubuntu, mypyc-compiled - python: '3.7' - arch: x64 - os: ubuntu-latest - toxenv: py - tox_extra_args: "-n 2" - test_mypyc: true - - name: Test suite with py310-ubuntu, mypyc-compiled - python: '3.10' - arch: x64 - os: ubuntu-latest - toxenv: py - tox_extra_args: "-n 2" - test_mypyc: true + tox_extra_args: "-n 4" - name: Test suite with py310-ubuntu python: '3.10' arch: x64 os: ubuntu-latest toxenv: py - tox_extra_args: "-n 2" + tox_extra_args: "-n 4" - name: Test suite with py311-ubuntu, mypyc-compiled python: '3.11' arch: x64 os: ubuntu-latest toxenv: py - tox_extra_args: "-n 2" + tox_extra_args: "-n 4" test_mypyc: true - - name: mypyc runtime tests with py37-macos - python: '3.7' + - name: Test suite with py312-ubuntu, mypyc-compiled + python: '3.12' arch: x64 - os: macos-latest + os: ubuntu-latest toxenv: py - tox_extra_args: "-n 2 mypyc/test/test_run.py mypyc/test/test_external.py" - - name: mypyc runtime tests with py37-debug-build-ubuntu - python: '3.7.13' + tox_extra_args: "-n 4" + test_mypyc: true + + - name: mypyc runtime tests with py39-macos + python: '3.9.18' + arch: x64 + # TODO: macos-13 is the last one to support Python 3.9, change it to macos-latest when updating the Python version + os: macos-13 + toxenv: py + tox_extra_args: "-n 3 mypyc/test/test_run.py mypyc/test/test_external.py" + - name: mypyc runtime tests with py38-debug-build-ubuntu + python: '3.8.17' arch: x64 os: ubuntu-latest toxenv: py - tox_extra_args: "-n 2 mypyc/test/test_run.py mypyc/test/test_external.py" + tox_extra_args: "-n 4 mypyc/test/test_run.py mypyc/test/test_external.py" debug_build: true - - name: Type check our own code (py37-ubuntu) - python: '3.7' + + - name: Type check our own code (py38-ubuntu) + python: '3.8' arch: x64 os: ubuntu-latest toxenv: type - - name: Type check our own code (py37-windows-64) - python: '3.7' + - name: Type check our own code (py38-windows-64) + python: '3.8' arch: x64 os: windows-latest toxenv: type - - name: Formatting with Black + isort and code style with flake8 - python: '3.7' + + # We also run these checks with pre-commit in CI, + # but it's useful to run them with tox too, + # to ensure the tox env works as expected + - name: Formatting and code style with Black + ruff + python: '3.10' arch: x64 os: ubuntu-latest toxenv: lint @@ -113,8 +120,8 @@ jobs: # Pytest PYTEST_ADDOPTS: --color=yes steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} architecture: ${{ matrix.arch }} @@ -127,35 +134,19 @@ jobs: ./misc/build-debug-python.sh $PYTHONVERSION $PYTHONDIR $VENV source $VENV/bin/activate - name: Install tox - run: pip install --upgrade 'setuptools!=50' tox==4.4.4 + run: pip install setuptools==68.2.2 tox==4.11.0 - name: Compiled with mypyc if: ${{ matrix.test_mypyc }} run: | pip install -r test-requirements.txt CC=clang MYPYC_OPT_LEVEL=0 MYPY_USE_MYPYC=1 pip install -e . - name: Setup tox environment - run: tox run -e ${{ matrix.toxenv }} --notest + run: | + tox run -e ${{ matrix.toxenv }} --notest + python -c 'import os; print("os.cpu_count", os.cpu_count(), "os.sched_getaffinity", len(getattr(os, "sched_getaffinity", lambda *args: [])(0)))' - name: Test run: tox run -e ${{ matrix.toxenv }} --skip-pkg-install -- ${{ matrix.tox_extra_args }} - python-nightly: - runs-on: ubuntu-latest - name: Test suite with Python nightly - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: '3.12-dev' - - name: Install tox - run: pip install --upgrade 'setuptools!=50' tox==4.4.4 - - name: Setup tox environment - run: tox run -e py --notest - - name: Test - run: tox run -e py --skip-pkg-install -- "-n 2" - continue-on-error: true - - name: Mark as a success - run: exit 0 - python_32bits: runs-on: ubuntu-latest name: Test mypyc suite with 32-bit Python @@ -174,12 +165,13 @@ jobs: CXX: i686-linux-gnu-g++ CC: i686-linux-gnu-gcc steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install 32-bit build dependencies run: | sudo dpkg --add-architecture i386 && \ sudo apt-get update && sudo apt-get install -y \ zlib1g-dev:i386 \ + libgcc-s1:i386 \ g++-i686-linux-gnu \ gcc-i686-linux-gnu \ libffi-dev:i386 \ @@ -196,8 +188,8 @@ jobs: default: 3.11.1 command: python -c "import platform; print(f'{platform.architecture()=} {platform.machine()=}');" - name: Install tox - run: pip install --upgrade 'setuptools!=50' tox==4.4.4 + run: pip install setuptools==68.2.2 tox==4.11.0 - name: Setup tox environment run: tox run -e py --notest - name: Test - run: tox run -e py --skip-pkg-install -- -n 2 mypyc/test/ + run: tox run -e py --skip-pkg-install -- -n 4 mypyc/test/ diff --git a/.github/workflows/test_stubgenc.yml b/.github/workflows/test_stubgenc.yml index b48031e5c18f..519f63ac2bd7 100644 --- a/.github/workflows/test_stubgenc.yml +++ b/.github/workflows/test_stubgenc.yml @@ -1,17 +1,25 @@ -name: Test stubgenc on pybind11-mypy-demo +name: Test stubgenc on pybind11_fixtures on: workflow_dispatch: push: - branches: [master, 'release*'] + branches: [main, master, 'release*'] tags: ['*'] pull_request: paths: - 'misc/test-stubgenc.sh' - 'mypy/stubgenc.py' - 'mypy/stubdoc.py' + - 'mypy/stubutil.py' - 'test-data/stubgen/**' +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: stubgenc: # Check stub file generation for a small pybind11 project @@ -19,10 +27,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup šŸ 3.8 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.8 diff --git a/.gitignore b/.gitignore index c6761f0ed736..6c35e3d89342 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,5 @@ test_capi *.o *.a test_capi -/.mypyc-flake8-cache.json /mypyc/lib-rt/build/ /mypyc/lib-rt/*.so diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0de686b7eb01..a7ff48051aad 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,16 +1,19 @@ +exclude: '^(mypyc/external/)|(mypy/typeshed/)|misc/typeshed_patches' # Exclude all vendored code from lints repos: - - repo: https://github.com/psf/black - rev: 22.12.0 # must match test-requirements.txt + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 # must match test-requirements.txt hooks: - - id: black - - repo: https://github.com/pycqa/isort - rev: 5.11.4 # must match test-requirements.txt + - id: trailing-whitespace + - id: end-of-file-fixer + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 24.1.1 # must match test-requirements.txt hooks: - - id: isort - - repo: https://github.com/pycqa/flake8 - rev: 5.0.4 # must match test-requirements.txt + - id: black + exclude: '^(test-data/)' + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.2.0 # must match test-requirements.txt hooks: - - id: flake8 - additional_dependencies: - - flake8-bugbear==22.12.6 # must match test-requirements.txt - - flake8-noqa==1.3.0 # must match test-requirements.txt + - id: ruff + args: [--exit-non-zero-on-fix] +ci: + autoupdate_schedule: quarterly diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000000..8ec33ee641ed --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,18 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +sphinx: + configuration: docs/source/conf.py + +formats: [pdf, htmlzip, epub] + +python: + install: + - requirements: docs/requirements-docs.txt diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000000..d0ea19866892 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1921 @@ +# Mypy Release Notes + +## Next release + + + +## Mypy 1.10 + +Weā€™ve just uploaded mypy 1.10 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). Mypy is a static type checker for Python. This release includes new features, performance improvements and bug fixes. You can install it as follows: + + python3 -m pip install -U mypy + +You can read the full documentation for this release on [Read the Docs](http://mypy.readthedocs.io). + +#### Support TypeIs (PEP 742) + +Mypy now supports `TypeIs` ([PEP 742](https://peps.python.org/pep-0742/)), which allows +functions to narrow the type of a value, similar to `isinstance()`. Unlike `TypeGuard`, +`TypeIs` can narrow in both the `if` and `else` branches of an if statement: + +```python +from typing_extensions import TypeIs + +def is_str(s: object) -> TypeIs[str]: + return isinstance(s, str) + +def f(o: str | int) -> None: + if is_str(o): + # Type of o is 'str' + ... + else: + # Type of o is 'int' + ... +``` + +`TypeIs` will be added to the `typing` module in Python 3.13, but it +can be used on earlier Python versions by importing it from +`typing_extensions`. + +This feature was contributed by Jelle Zijlstra (PR [16898](https://github.com/python/mypy/pull/16898)). + +#### Support TypeVar Defaults (PEP 696) + +[PEP 696](https://peps.python.org/pep-0696/) adds support for type parameter defaults. +Example: + +```python +from typing import Generic +from typing_extensions import TypeVar + +T = TypeVar("T", default=int) + +class C(Generic[T]): + ... + +x: C = ... +y: C[str] = ... +reveal_type(x) # C[int], because of the default +reveal_type(y) # C[str] +``` + +TypeVar defaults will be added to the `typing` module in Python 3.13, but they +can be used with earlier Python releases by importing `TypeVar` from +`typing_extensions`. + +This feature was contributed by Marc Mueller (PR [16878](https://github.com/python/mypy/pull/16878) +and PR [16925](https://github.com/python/mypy/pull/16925)). + +#### Support TypeAliasType (PEP 695) +As part of the initial steps towards implementing [PEP 695](https://peps.python.org/pep-0695/), mypy now supports `TypeAliasType`. +`TypeAliasType` provides a backport of the new `type` statement in Python 3.12. + +```python +type ListOrSet[T] = list[T] | set[T] +``` + +is equivalent to: + +```python +T = TypeVar("T") +ListOrSet = TypeAliasType("ListOrSet", list[T] | set[T], type_params=(T,)) +``` + +Example of use in mypy: + +```python +from typing_extensions import TypeAliasType, TypeVar + +NewUnionType = TypeAliasType("NewUnionType", int | str) +x: NewUnionType = 42 +y: NewUnionType = 'a' +z: NewUnionType = object() # error: Incompatible types in assignment (expression has type "object", variable has type "int | str") [assignment] + +T = TypeVar("T") +ListOrSet = TypeAliasType("ListOrSet", list[T] | set[T], type_params=(T,)) +a: ListOrSet[int] = [1, 2] +b: ListOrSet[str] = {'a', 'b'} +c: ListOrSet[str] = 'test' # error: Incompatible types in assignment (expression has type "str", variable has type "list[str] | set[str]") [assignment] +``` + +`TypeAliasType` was added to the `typing` module in Python 3.12, but it can be used with earlier Python releases by importing from `typing_extensions`. + +This feature was contributed by Ali Hamdan (PR [16926](https://github.com/python/mypy/pull/16926), PR [17038](https://github.com/python/mypy/pull/17038) and PR [17053](https://github.com/python/mypy/pull/17053)) + +#### Detect Additional Unsafe Uses of super() + +Mypy will reject unsafe uses of `super()` more consistently, when the target has a +trivial (empty) body. Example: + +```python +class Proto(Protocol): + def method(self) -> int: ... + +class Sub(Proto): + def method(self) -> int: + return super().meth() # Error (unsafe) +``` + +This feature was contributed by Shantanu (PR [16756](https://github.com/python/mypy/pull/16756)). + +#### Stubgen Improvements +- Preserve empty tuple annotation (Ali Hamdan, PR [16907](https://github.com/python/mypy/pull/16907)) +- Add support for PEP 570 positional-only parameters (Ali Hamdan, PR [16904](https://github.com/python/mypy/pull/16904)) +- Replace obsolete typing aliases with builtin containers (Ali Hamdan, PR [16780](https://github.com/python/mypy/pull/16780)) +- Fix generated dataclass `__init__` signature (Ali Hamdan, PR [16906](https://github.com/python/mypy/pull/16906)) + +#### Mypyc Improvements + +- Provide an easier way to define IR-to-IR transforms (Jukka Lehtosalo, PR [16998](https://github.com/python/mypy/pull/16998)) +- Implement lowering pass and add primitives for int (in)equality (Jukka Lehtosalo, PR [17027](https://github.com/python/mypy/pull/17027)) +- Implement lowering for remaining tagged integer comparisons (Jukka Lehtosalo, PR [17040](https://github.com/python/mypy/pull/17040)) +- Optimize away some bool/bit registers (Jukka Lehtosalo, PR [17022](https://github.com/python/mypy/pull/17022)) +- Remangle redefined names produced by async with (Richard Si, PR [16408](https://github.com/python/mypy/pull/16408)) +- Optimize TYPE_CHECKING to False at Runtime (Srinivas Lade, PR [16263](https://github.com/python/mypy/pull/16263)) +- Fix compilation of unreachable comprehensions (Richard Si, PR [15721](https://github.com/python/mypy/pull/15721)) +- Don't crash on non-inlinable final local reads (Richard Si, PR [15719](https://github.com/python/mypy/pull/15719)) + +#### Documentation Improvements +- Import `TypedDict` from `typing` instead of `typing_extensions` (Riccardo Di Maio, PR [16958](https://github.com/python/mypy/pull/16958)) +- Add missing `mutable-override` to section title (James Braza, PR [16886](https://github.com/python/mypy/pull/16886)) + +#### Error Reporting Improvements + +- Use lower-case generics more consistently in error messages (Jukka Lehtosalo, PR [17035](https://github.com/python/mypy/pull/17035)) + +#### Other Notable Changes and Fixes +- Fix incorrect inferred type when accessing descriptor on union type (Matthieu Devlin, PR [16604](https://github.com/python/mypy/pull/16604)) +- Fix crash when expanding invalid `Unpack` in a `Callable` alias (Ali Hamdan, PR [17028](https://github.com/python/mypy/pull/17028)) +- Fix false positive when string formatting with string enum (roberfi, PR [16555](https://github.com/python/mypy/pull/16555)) +- Narrow individual items when matching a tuple to a sequence pattern (LoĆÆc Simon, PR [16905](https://github.com/python/mypy/pull/16905)) +- Fix false positive from type variable within TypeGuard or TypeIs (Evgeniy Slobodkin, PR [17071](https://github.com/python/mypy/pull/17071)) +- Improve `yield from` inference for unions of generators (Shantanu, PR [16717](https://github.com/python/mypy/pull/16717)) +- Fix emulating hash method logic in `attrs` classes (Hashem, PR [17016](https://github.com/python/mypy/pull/17016)) +- Add reverted typeshed commit that uses `ParamSpec` for `functools.wraps` (Tamir Duberstein, PR [16942](https://github.com/python/mypy/pull/16942)) +- Fix type narrowing for `types.EllipsisType` (Shantanu, PR [17003](https://github.com/python/mypy/pull/17003)) +- Fix single item enum match type exhaustion (Oskari Lehto, PR [16966](https://github.com/python/mypy/pull/16966)) +- Improve type inference with empty collections (Marc Mueller, PR [16994](https://github.com/python/mypy/pull/16994)) +- Fix override checking for decorated property (Shantanu, PR [16856](https://github.com/python/mypy/pull/16856)) +- Fix narrowing on match with function subject (Edward Paget, PR [16503](https://github.com/python/mypy/pull/16503)) +- Allow `+N` within `Literal[...]` (Spencer Brown, PR [16910](https://github.com/python/mypy/pull/16910)) +- Experimental: Support TypedDict within `type[...]` (Marc Mueller, PR [16963](https://github.com/python/mypy/pull/16963)) +- Experimtental: Fix issue with TypedDict with optional keys in `type[...]` (Marc Mueller, PR [17068](https://github.com/python/mypy/pull/17068)) + +#### Typeshed Updates + +Please see [git log](https://github.com/python/typeshed/commits/main?after=7c8e82fe483a40ec4cb0a2505cfdb0f3e7cc81d9+0&branch=main&path=stdlib) for full list of standard library typeshed stub changes. + + +#### Acknowledgements +Thanks to all mypy contributors who contributed to this release: + +- Alex Waygood +- Ali Hamdan +- Edward Paget +- Evgeniy Slobodkin +- Hashem +- hesam +- Hugo van Kemenade +- Ihor +- James Braza +- Jelle Zijlstra +- jhance +- Jukka Lehtosalo +- LoĆÆc Simon +- Marc Mueller +- Matthieu Devlin +- Michael R. Crusoe +- Nikita Sobolev +- Oskari Lehto +- Riccardo Di Maio +- Richard Si +- roberfi +- Roman Solomatin +- Sam Xifaras +- Shantanu +- Spencer Brown +- Srinivas Lade +- Tamir Duberstein +- youkaichao + +Iā€™d also like to thank my employer, Dropbox, for supporting mypy development. + + +## Mypy 1.9 + +Weā€™ve just uploaded mypy 1.9 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). Mypy is a static type checker for Python. This release includes new features, performance improvements and bug fixes. You can install it as follows: + + python3 -m pip install -U mypy + +You can read the full documentation for this release on [Read the Docs](http://mypy.readthedocs.io). + +#### Breaking Changes + +Because the version of typeshed we use in mypy 1.9 doesn't support 3.7, neither does mypy 1.9. (Jared Hance, PR [16883](https://github.com/python/mypy/pull/16883)) + +We are planning to enable +[local partial types](https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-local-partial-types) (enabled via the +`--local-partial-types` flag) later this year by default. This change +was announced years ago, but now it's finally happening. This is a +major backward-incompatible change, so we'll probably include it as +part of the upcoming mypy 2.0 release. This makes daemon and +non-daemon mypy runs have the same behavior by default. + +Local partial types can also be enabled in the mypy config file: +``` +local_partial_types = True +``` + +We are looking at providing a tool to make it easier to migrate +projects to use `--local-partial-types`, but it's not yet clear whether +this is practical. The migration usually involves adding some +explicit type annotations to module-level and class-level variables. + +#### Basic Support for Type Parameter Defaults (PEP 696) + +This release contains new experimental support for type parameter +defaults ([PEP 696](https://peps.python.org/pep-0696)). Please try it +out! This feature was contributed by Marc Mueller. + +Since this feature will be officially introduced in the next Python +feature release (3.13), you will need to import `TypeVar`, `ParamSpec` +or `TypeVarTuple` from `typing_extensions` to use defaults for now. + +This example adapted from the PEP defines a default for `BotT`: +```python +from typing import Generic +from typing_extensions import TypeVar + +class Bot: ... + +BotT = TypeVar("BotT", bound=Bot, default=Bot) + +class Context(Generic[BotT]): + bot: BotT + +class MyBot(Bot): ... + +# type is Bot (the default) +reveal_type(Context().bot) +# type is MyBot +reveal_type(Context[MyBot]().bot) +``` + +#### Type-checking Improvements + * Fix missing type store for overloads (Marc Mueller, PR [16803](https://github.com/python/mypy/pull/16803)) + * Fix `'WriteToConn' object has no attribute 'flush'` (Charlie Denton, PR [16801](https://github.com/python/mypy/pull/16801)) + * Improve TypeAlias error messages (Marc Mueller, PR [16831](https://github.com/python/mypy/pull/16831)) + * Support narrowing unions that include `type[None]` (Christoph Tyralla, PR [16315](https://github.com/python/mypy/pull/16315)) + * Support TypedDict functional syntax as class base type (anniel-stripe, PR [16703](https://github.com/python/mypy/pull/16703)) + * Accept multiline quoted annotations (Shantanu, PR [16765](https://github.com/python/mypy/pull/16765)) + * Allow unary + in `Literal` (Jelle Zijlstra, PR [16729](https://github.com/python/mypy/pull/16729)) + * Substitute type variables in return type of static methods (Kouroche Bouchiat, PR [16670](https://github.com/python/mypy/pull/16670)) + * Consider TypeVarTuple to be invariant (Marc Mueller, PR [16759](https://github.com/python/mypy/pull/16759)) + * Add `alias` support to `field()` in `attrs` plugin (Nikita Sobolev, PR [16610](https://github.com/python/mypy/pull/16610)) + * Improve attrs hashability detection (Tin Tvrtković, PR [16556](https://github.com/python/mypy/pull/16556)) + +#### Performance Improvements + + * Speed up finding function type variables (Jukka Lehtosalo, PR [16562](https://github.com/python/mypy/pull/16562)) + +#### Documentation Updates + + * Document supported values for `--enable-incomplete-feature` in "mypy --help" (Froger David, PR [16661](https://github.com/python/mypy/pull/16661)) + * Update new type system discussion links (thomaswhaley, PR [16841](https://github.com/python/mypy/pull/16841)) + * Add missing class instantiation to cheat sheet (Aleksi Tarvainen, PR [16817](https://github.com/python/mypy/pull/16817)) + * Document how evil `--no-strict-optional` is (Shantanu, PR [16731](https://github.com/python/mypy/pull/16731)) + * Improve mypy daemon documentation note about local partial types (Makonnen Makonnen, PR [16782](https://github.com/python/mypy/pull/16782)) + * Fix numbering error (Stefanie Molin, PR [16838](https://github.com/python/mypy/pull/16838)) + * Various documentation improvements (Shantanu, PR [16836](https://github.com/python/mypy/pull/16836)) + +#### Stubtest Improvements + * Ignore private function/method parameters when they are missing from the stub (private parameter names start with a single underscore and have a default) (PR [16507](https://github.com/python/mypy/pull/16507)) + * Ignore a new protocol dunder (Alex Waygood, PR [16895](https://github.com/python/mypy/pull/16895)) + * Private parameters can be omitted (Sebastian Rittau, PR [16507](https://github.com/python/mypy/pull/16507)) + * Add support for setting enum members to "..." (Jelle Zijlstra, PR [16807](https://github.com/python/mypy/pull/16807)) + * Adjust symbol table logic (Shantanu, PR [16823](https://github.com/python/mypy/pull/16823)) + * Fix posisitional-only handling in overload resolution (Shantanu, PR [16750](https://github.com/python/mypy/pull/16750)) + +#### Stubgen Improvements + * Fix crash on star unpack of TypeVarTuple (Ali Hamdan, PR [16869](https://github.com/python/mypy/pull/16869)) + * Use PEP 604 unions everywhere (Ali Hamdan, PR [16519](https://github.com/python/mypy/pull/16519)) + * Do not ignore property deleter (Ali Hamdan, PR [16781](https://github.com/python/mypy/pull/16781)) + * Support type stub generation for `staticmethod` (WeilerMarcel, PR [14934](https://github.com/python/mypy/pull/14934)) + +#### Acknowledgements + +ā€‹Thanks to all mypy contributors who contributed to this release: + +- Aleksi Tarvainen +- Alex Waygood +- Ali Hamdan +- anniel-stripe +- Charlie Denton +- Christoph Tyralla +- Dheeraj +- Fabian Keller +- Fabian Lewis +- Froger David +- Ihor +- Jared Hance +- Jelle Zijlstra +- Jukka Lehtosalo +- Kouroche Bouchiat +- Lukas Geiger +- Maarten Huijsmans +- Makonnen Makonnen +- Marc Mueller +- Nikita Sobolev +- Sebastian Rittau +- Shantanu +- Stefanie Molin +- Stephen Morton +- thomaswhaley +- Tin Tvrtković +- WeilerMarcel +- Wesley Collin Wright +- zipperer + +Iā€™d also like to thank my employer, Dropbox, for supporting mypy development. + +## Mypy 1.8 + +Weā€™ve just uploaded mypy 1.8 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). Mypy is a static type checker for Python. This release includes new features, performance improvements and bug fixes. You can install it as follows: + + python3 -m pip install -U mypy + +You can read the full documentation for this release on [Read the Docs](http://mypy.readthedocs.io). + +#### Type-checking Improvements + * Do not intersect types in isinstance checks if at least one is final (Christoph Tyralla, PR [16330](https://github.com/python/mypy/pull/16330)) + * Detect that `@final` class without `__bool__` cannot have falsey instances (Ilya Priven, PR [16566](https://github.com/python/mypy/pull/16566)) + * Do not allow `TypedDict` classes with extra keywords (Nikita Sobolev, PR [16438](https://github.com/python/mypy/pull/16438)) + * Do not allow class-level keywords for `NamedTuple` (Nikita Sobolev, PR [16526](https://github.com/python/mypy/pull/16526)) + * Make imprecise constraints handling more robust (Ivan Levkivskyi, PR [16502](https://github.com/python/mypy/pull/16502)) + * Fix strict-optional in extending generic TypedDict (Ivan Levkivskyi, PR [16398](https://github.com/python/mypy/pull/16398)) + * Allow type ignores of PEP 695 constructs (Shantanu, PR [16608](https://github.com/python/mypy/pull/16608)) + * Enable `type_check_only` support for `TypedDict` and `NamedTuple` (Nikita Sobolev, PR [16469](https://github.com/python/mypy/pull/16469)) + +#### Performance Improvements + * Add fast path to analyzing special form assignments (Jukka Lehtosalo, PR [16561](https://github.com/python/mypy/pull/16561)) + +#### Improvements to Error Reporting + * Don't show documentation links for plugin error codes (Ivan Levkivskyi, PR [16383](https://github.com/python/mypy/pull/16383)) + * Improve error messages for `super` checks and add more tests (Nikita Sobolev, PR [16393](https://github.com/python/mypy/pull/16393)) + * Add error code for mutable covariant override (Ivan Levkivskyi, PR [16399](https://github.com/python/mypy/pull/16399)) + +#### Stubgen Improvements + * Preserve simple defaults in function signatures (Ali Hamdan, PR [15355](https://github.com/python/mypy/pull/15355)) + * Include `__all__` in output (Jelle Zijlstra, PR [16356](https://github.com/python/mypy/pull/16356)) + * Fix stubgen regressions with pybind11 and mypy 1.7 (Chad Dombrova, PR [16504](https://github.com/python/mypy/pull/16504)) + +#### Stubtest Improvements + * Improve handling of unrepresentable defaults (Jelle Zijlstra, PR [16433](https://github.com/python/mypy/pull/16433)) + * Print more helpful errors if a function is missing from stub (Alex Waygood, PR [16517](https://github.com/python/mypy/pull/16517)) + * Support `@type_check_only` decorator (Nikita Sobolev, PR [16422](https://github.com/python/mypy/pull/16422)) + * Warn about missing `__del__` (Shantanu, PR [16456](https://github.com/python/mypy/pull/16456)) + * Fix crashes with some uses of `final` and `deprecated` (Shantanu, PR [16457](https://github.com/python/mypy/pull/16457)) + +#### Fixes to Crashes + * Fix crash with type alias to `Callable[[Unpack[Tuple[Any, ...]]], Any]` (Alex Waygood, PR [16541](https://github.com/python/mypy/pull/16541)) + * Fix crash on TypeGuard in `__call__` (Ivan Levkivskyi, PR [16516](https://github.com/python/mypy/pull/16516)) + * Fix crash on invalid enum in method (Ivan Levkivskyi, PR [16511](https://github.com/python/mypy/pull/16511)) + * Fix crash on unimported Any in TypedDict (Ivan Levkivskyi, PR [16510](https://github.com/python/mypy/pull/16510)) + +#### Documentation Updates + * Update soft-error-limit default value to -1 (Sveinung Gundersen, PR [16542](https://github.com/python/mypy/pull/16542)) + * Support Sphinx 7.x (Michael R. Crusoe, PR [16460](https://github.com/python/mypy/pull/16460)) + +#### Other Notable Changes and Fixes + * Allow mypy to output a junit file with per-file results (Matthew Wright, PR [16388](https://github.com/python/mypy/pull/16388)) + +#### Typeshed Updates + +Please see [git log](https://github.com/python/typeshed/commits/main?after=4a854366e03dee700109f8e758a08b2457ea2f51+0&branch=main&path=stdlib) for full list of standard library typeshed stub changes. + +#### Acknowledgements + +ā€‹Thanks to all mypy contributors who contributed to this release: + +- Alex Waygood +- Ali Hamdan +- Chad Dombrova +- Christoph Tyralla +- Ilya Priven +- Ivan Levkivskyi +- Jelle Zijlstra +- Jukka Lehtosalo +- Marcel Telka +- Matthew Wright +- Michael R. Crusoe +- Nikita Sobolev +- Ole Peder BrandtzƦg +- robjhornby +- Shantanu +- Sveinung Gundersen +- Valentin Stanciu + +Iā€™d also like to thank my employer, Dropbox, for supporting mypy development. + +Posted by Wesley Collin Wright + +## Mypy 1.7 + +Weā€™ve just uploaded mypy 1.7 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). Mypy is a static type checker for Python. This release includes new features, performance improvements and bug fixes. You can install it as follows: + + python3 -m pip install -U mypy + +You can read the full documentation for this release on [Read the Docs](http://mypy.readthedocs.io). + +#### Using TypedDict for `**kwargs` Typing + +Mypy now has support for using `Unpack[...]` with a TypedDict type to annotate `**kwargs` arguments enabled by default. Example: + +```python +# Or 'from typing_extensions import ...' +from typing import TypedDict, Unpack + +class Person(TypedDict): + name: str + age: int + +def foo(**kwargs: Unpack[Person]) -> None: + ... + +foo(name="x", age=1) # Ok +foo(name=1) # Error +``` + +The definition of `foo` above is equivalent to the one below, with keyword-only arguments `name` and `age`: + +```python +def foo(*, name: str, age: int) -> None: + ... +``` + +Refer to [PEP 692](https://peps.python.org/pep-0692/) for more information. Note that unlike in the current version of the PEP, mypy always treats signatures with `Unpack[SomeTypedDict]` as equivalent to their expanded forms with explicit keyword arguments, and there aren't special type checking rules for TypedDict arguments. + +This was contributed by Ivan Levkivskyi back in 2022 (PR [13471](https://github.com/python/mypy/pull/13471)). + +#### TypeVarTuple Support Enabled (Experimental) + +Mypy now has support for variadic generics (TypeVarTuple) enabled by default, as an experimental feature. Refer to [PEP 646](https://peps.python.org/pep-0646/) for the details. + +TypeVarTuple was implemented by Jared Hance and Ivan Levkivskyi over several mypy releases, with help from Jukka Lehtosalo. + +Changes included in this release: + + * Fix handling of tuple type context with unpacks (Ivan Levkivskyi, PR [16444](https://github.com/python/mypy/pull/16444)) + * Handle TypeVarTuples when checking overload constraints (robjhornby, PR [16428](https://github.com/python/mypy/pull/16428)) + * Enable Unpack/TypeVarTuple support (Ivan Levkivskyi, PR [16354](https://github.com/python/mypy/pull/16354)) + * Fix crash on unpack call special-casing (Ivan Levkivskyi, PR [16381](https://github.com/python/mypy/pull/16381)) + * Some final touches for variadic types support (Ivan Levkivskyi, PR [16334](https://github.com/python/mypy/pull/16334)) + * Support PEP-646 and PEP-692 in the same callable (Ivan Levkivskyi, PR [16294](https://github.com/python/mypy/pull/16294)) + * Support new `*` syntax for variadic types (Ivan Levkivskyi, PR [16242](https://github.com/python/mypy/pull/16242)) + * Correctly handle variadic instances with empty arguments (Ivan Levkivskyi, PR [16238](https://github.com/python/mypy/pull/16238)) + * Correctly handle runtime type applications of variadic types (Ivan Levkivskyi, PR [16240](https://github.com/python/mypy/pull/16240)) + * Support variadic tuple packing/unpacking (Ivan Levkivskyi, PR [16205](https://github.com/python/mypy/pull/16205)) + * Better support for variadic calls and indexing (Ivan Levkivskyi, PR [16131](https://github.com/python/mypy/pull/16131)) + * Subtyping and inference of user-defined variadic types (Ivan Levkivskyi, PR [16076](https://github.com/python/mypy/pull/16076)) + * Complete type analysis of variadic types (Ivan Levkivskyi, PR [15991](https://github.com/python/mypy/pull/15991)) + +#### New Way of Installing Mypyc Dependencies + +If you want to install package dependencies needed by mypyc (not just mypy), you should now install `mypy[mypyc]` instead of just `mypy`: + +``` +python3 -m pip install -U 'mypy[mypyc]' +``` + +Mypy has many more users than mypyc, so always installing mypyc dependencies would often bring unnecessary dependencies. + +This change was contributed by Shantanu (PR [16229](https://github.com/python/mypy/pull/16229)). + +#### New Rules for Re-exports + +Mypy no longer considers an import such as `import a.b as b` as an explicit re-export. The old behavior was arguably inconsistent and surprising. This may impact some stub packages, such as older versions of `types-six`. You can change the import to `from a import b as b`, if treating the import as a re-export was intentional. + +This change was contributed by Anders Kaseorg (PR [14086](https://github.com/python/mypy/pull/14086)). + +#### Improved Type Inference + +The new type inference algorithm that was recently introduced to mypy (but was not enabled by default) is now enabled by default. It improves type inference of calls to generic callables where an argument is also a generic callable, in particular. You can use `--old-type-inference` to disable the new behavior. + +The new algorithm can (rarely) produce different error messages, different error codes, or errors reported on different lines. This is more likely in cases where generic types were used incorrectly. + +The new type inference algorithm was contributed by Ivan Levkivskyi. PR [16345](https://github.com/python/mypy/pull/16345) enabled it by default. + +#### Narrowing Tuple Types Using len() + +Mypy now can narrow tuple types using `len()` checks. Example: + +```python +def f(t: tuple[int, int] | tuple[int, int, int]) -> None: + if len(t) == 2: + a, b = t # Ok + ... +``` + +This feature was contributed by Ivan Levkivskyi (PR [16237](https://github.com/python/mypy/pull/16237)). + +#### More Precise Tuple Lengths (Experimental) + +Mypy supports experimental, more precise checking of tuple type lengths through `--enable-incomplete-feature=PreciseTupleTypes`. Refer to the [documentation](https://mypy.readthedocs.io/en/latest/command_line.html#enabling-incomplete-experimental-features) for more information. + +More generally, we are planning to use `--enable-incomplete-feature` to introduce experimental features that would benefit from community feedback. + +This feature was contributed by Ivan Levkivskyi (PR [16237](https://github.com/python/mypy/pull/16237)). + +#### Mypy Changelog + +We now maintain a [changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) in the mypy Git repository. It mirrors the contents of [mypy release blog posts](https://mypy-lang.blogspot.com/). We will continue to also publish release blog posts. In the future, release blog posts will be created based on the changelog near a release date. + +This was contributed by Shantanu (PR [16280](https://github.com/python/mypy/pull/16280)). + +#### Mypy Daemon Improvements + + * Fix daemon crash caused by deleted submodule (Jukka Lehtosalo, PR [16370](https://github.com/python/mypy/pull/16370)) + * Fix file reloading in dmypy with --export-types (Ivan Levkivskyi, PR [16359](https://github.com/python/mypy/pull/16359)) + * Fix dmypy inspect on Windows (Ivan Levkivskyi, PR [16355](https://github.com/python/mypy/pull/16355)) + * Fix dmypy inspect for namespace packages (Ivan Levkivskyi, PR [16357](https://github.com/python/mypy/pull/16357)) + * Fix return type change to optional in generic function (Jukka Lehtosalo, PR [16342](https://github.com/python/mypy/pull/16342)) + * Fix daemon false positives related to module-level `__getattr__` (Jukka Lehtosalo, PR [16292](https://github.com/python/mypy/pull/16292)) + * Fix daemon crash related to ABCs (Jukka Lehtosalo, PR [16275](https://github.com/python/mypy/pull/16275)) + * Stream dmypy output instead of dumping everything at the end (Valentin Stanciu, PR [16252](https://github.com/python/mypy/pull/16252)) + * Make sure all dmypy errors are shown (Valentin Stanciu, PR [16250](https://github.com/python/mypy/pull/16250)) + +#### Mypyc Improvements + + * Generate error on duplicate function definitions (Jukka Lehtosalo, PR [16309](https://github.com/python/mypy/pull/16309)) + * Don't crash on unreachable statements (Jukka Lehtosalo, PR [16311](https://github.com/python/mypy/pull/16311)) + * Avoid cyclic reference in nested functions (Jukka Lehtosalo, PR [16268](https://github.com/python/mypy/pull/16268)) + * Fix direct `__dict__` access on inner functions in new Python (Shantanu, PR [16084](https://github.com/python/mypy/pull/16084)) + * Make tuple packing and unpacking more efficient (Jukka Lehtosalo, PR [16022](https://github.com/python/mypy/pull/16022)) + +#### Improvements to Error Reporting + + * Update starred expression error message to match CPython (Cibin Mathew, PR [16304](https://github.com/python/mypy/pull/16304)) + * Fix error code of "Maybe you forgot to use await" note (Jelle Zijlstra, PR [16203](https://github.com/python/mypy/pull/16203)) + * Use error code `[unsafe-overload]` for unsafe overloads, instead of `[misc]` (Randolf Scholz, PR [16061](https://github.com/python/mypy/pull/16061)) + * Reword the error message related to void functions (Albert Tugushev, PR [15876](https://github.com/python/mypy/pull/15876)) + * Represent bottom type as Never in messages (Shantanu, PR [15996](https://github.com/python/mypy/pull/15996)) + * Add hint for AsyncIterator incompatible return type (Ilya Priven, PR [15883](https://github.com/python/mypy/pull/15883)) + * Don't suggest stubs packages where the runtime package now ships with types (Alex Waygood, PR [16226](https://github.com/python/mypy/pull/16226)) + +#### Performance Improvements + + * Speed up type argument checking (Jukka Lehtosalo, PR [16353](https://github.com/python/mypy/pull/16353)) + * Add fast path for checking self types (Jukka Lehtosalo, PR [16352](https://github.com/python/mypy/pull/16352)) + * Cache information about whether file is typeshed file (Jukka Lehtosalo, PR [16351](https://github.com/python/mypy/pull/16351)) + * Skip expensive `repr()` in logging call when not needed (Jukka Lehtosalo, PR [16350](https://github.com/python/mypy/pull/16350)) + +#### Attrs and Dataclass Improvements + + * `dataclass.replace`: Allow transformed classes (Ilya Priven, PR [15915](https://github.com/python/mypy/pull/15915)) + * `dataclass.replace`: Fall through to typeshed signature (Ilya Priven, PR [15962](https://github.com/python/mypy/pull/15962)) + * Document `dataclass_transform` behavior (Ilya Priven, PR [16017](https://github.com/python/mypy/pull/16017)) + * `attrs`: Remove fields type check (Ilya Priven, PR [15983](https://github.com/python/mypy/pull/15983)) + * `attrs`, `dataclasses`: Don't enforce slots when base class doesn't (Ilya Priven, PR [15976](https://github.com/python/mypy/pull/15976)) + * Fix crash on dataclass field / property collision (Nikita Sobolev, PR [16147](https://github.com/python/mypy/pull/16147)) + +#### Stubgen Improvements + + * Write stubs with utf-8 encoding (JĆørgen Lind, PR [16329](https://github.com/python/mypy/pull/16329)) + * Fix missing property setter in semantic analysis mode (Ali Hamdan, PR [16303](https://github.com/python/mypy/pull/16303)) + * Unify C extension and pure python stub generators with object oriented design (Chad Dombrova, PR [15770](https://github.com/python/mypy/pull/15770)) + * Multiple fixes to the generated imports (Ali Hamdan, PR [15624](https://github.com/python/mypy/pull/15624)) + * Generate valid dataclass stubs (Ali Hamdan, PR [15625](https://github.com/python/mypy/pull/15625)) + +#### Fixes to Crashes + + * Fix incremental mode crash on TypedDict in method (Ivan Levkivskyi, PR [16364](https://github.com/python/mypy/pull/16364)) + * Fix crash on star unpack in TypedDict (Ivan Levkivskyi, PR [16116](https://github.com/python/mypy/pull/16116)) + * Fix crash on malformed TypedDict in incremental mode (Ivan Levkivskyi, PR [16115](https://github.com/python/mypy/pull/16115)) + * Fix crash with report generation on namespace packages (Shantanu, PR [16019](https://github.com/python/mypy/pull/16019)) + * Fix crash when parsing error code config with typo (Shantanu, PR [16005](https://github.com/python/mypy/pull/16005)) + * Fix `__post_init__()` internal error (Ilya Priven, PR [16080](https://github.com/python/mypy/pull/16080)) + +#### Documentation Updates + + * Make it easier to copy commands from README (Hamir Mahal, PR [16133](https://github.com/python/mypy/pull/16133)) + * Document and rename `[overload-overlap]` error code (Shantanu, PR [16074](https://github.com/python/mypy/pull/16074)) + * Document `--force-uppercase-builtins` and `--force-union-syntax` (Nikita Sobolev, PR [16049](https://github.com/python/mypy/pull/16049)) + * Document `force_union_syntax` and `force_uppercase_builtins` (Nikita Sobolev, PR [16048](https://github.com/python/mypy/pull/16048)) + * Document we're not tracking relationships between symbols (Ilya Priven, PR [16018](https://github.com/python/mypy/pull/16018)) + +#### Other Notable Changes and Fixes + + * Propagate narrowed types to lambda expressions (Ivan Levkivskyi, PR [16407](https://github.com/python/mypy/pull/16407)) + * Avoid importing from `setuptools._distutils` (Shantanu, PR [16348](https://github.com/python/mypy/pull/16348)) + * Delete recursive aliases flags (Ivan Levkivskyi, PR [16346](https://github.com/python/mypy/pull/16346)) + * Properly use proper subtyping for callables (Ivan Levkivskyi, PR [16343](https://github.com/python/mypy/pull/16343)) + * Use upper bound as inference fallback more consistently (Ivan Levkivskyi, PR [16344](https://github.com/python/mypy/pull/16344)) + * Add `[unimported-reveal]` error code (Nikita Sobolev, PR [16271](https://github.com/python/mypy/pull/16271)) + * Add `|=` and `|` operators support for `TypedDict` (Nikita Sobolev, PR [16249](https://github.com/python/mypy/pull/16249)) + * Clarify variance convention for Parameters (Ivan Levkivskyi, PR [16302](https://github.com/python/mypy/pull/16302)) + * Correctly recognize `typing_extensions.NewType` (Ganden Schaffner, PR [16298](https://github.com/python/mypy/pull/16298)) + * Fix partially defined in the case of missing type maps (Shantanu, PR [15995](https://github.com/python/mypy/pull/15995)) + * Use SPDX license identifier (Nikita Sobolev, PR [16230](https://github.com/python/mypy/pull/16230)) + * Make `__qualname__` and `__module__` available in class bodies (Anthony Sottile, PR [16215](https://github.com/python/mypy/pull/16215)) + * stubtest: Hint when args in stub need to be keyword-only (Alex Waygood, PR [16210](https://github.com/python/mypy/pull/16210)) + * Tuple slice should not propagate fallback (Thomas Grainger, PR [16154](https://github.com/python/mypy/pull/16154)) + * Fix cases of type object handling for overloads (Shantanu, PR [16168](https://github.com/python/mypy/pull/16168)) + * Fix walrus interaction with empty collections (Ivan Levkivskyi, PR [16197](https://github.com/python/mypy/pull/16197)) + * Use type variable bound when it appears as actual during inference (Ivan Levkivskyi, PR [16178](https://github.com/python/mypy/pull/16178)) + * Use upper bounds as fallback solutions for inference (Ivan Levkivskyi, PR [16184](https://github.com/python/mypy/pull/16184)) + * Special-case type inference of empty collections (Ivan Levkivskyi, PR [16122](https://github.com/python/mypy/pull/16122)) + * Allow TypedDict unpacking in Callable types (Ivan Levkivskyi, PR [16083](https://github.com/python/mypy/pull/16083)) + * Fix inference for overloaded `__call__` with generic self (Shantanu, PR [16053](https://github.com/python/mypy/pull/16053)) + * Call dynamic class hook on generic classes (Petter Friberg, PR [16052](https://github.com/python/mypy/pull/16052)) + * Preserve implicitly exported types via attribute access (Shantanu, PR [16129](https://github.com/python/mypy/pull/16129)) + * Fix a stubtest bug (Alex Waygood) + * Fix `tuple[Any, ...]` subtyping (Shantanu, PR [16108](https://github.com/python/mypy/pull/16108)) + * Lenient handling of trivial Callable suffixes (Ivan Levkivskyi, PR [15913](https://github.com/python/mypy/pull/15913)) + * Add `add_overloaded_method_to_class` helper for plugins (Nikita Sobolev, PR [16038](https://github.com/python/mypy/pull/16038)) + * Bundle `misc/proper_plugin.py` as a part of `mypy` (Nikita Sobolev, PR [16036](https://github.com/python/mypy/pull/16036)) + * Fix `case Any()` in match statement (DS/Charlie, PR [14479](https://github.com/python/mypy/pull/14479)) + * Make iterable logic more consistent (Shantanu, PR [16006](https://github.com/python/mypy/pull/16006)) + * Fix inference for properties with `__call__` (Shantanu, PR [15926](https://github.com/python/mypy/pull/15926)) + +#### Typeshed Updates + +Please see [git log](https://github.com/python/typeshed/commits/main?after=4a854366e03dee700109f8e758a08b2457ea2f51+0&branch=main&path=stdlib) for full list of standard library typeshed stub changes. + +#### Acknowledgements + +Thanks to all mypy contributors who contributed to this release: + +* Albert Tugushev +* Alex Waygood +* Ali Hamdan +* Anders Kaseorg +* Anthony Sottile +* Chad Dombrova +* Cibin Mathew +* dinaldoap +* DS/Charlie +* Eli Schwartz +* Ganden Schaffner +* Hamir Mahal +* Ihor +* Ikko Eltociear Ashimine +* Ilya Priven +* Ivan Levkivskyi +* Jelle Zijlstra +* Jukka Lehtosalo +* JĆørgen Lind +* KotlinIsland +* Matt Bogosian +* Nikita Sobolev +* Petter Friberg +* Randolf Scholz +* Shantanu +* Thomas Grainger +* Valentin Stanciu + +Iā€™d also like to thank my employer, Dropbox, for supporting mypy development. + +Posted by Jukka Lehtosalo + +## Mypy 1.6 + +[Tuesday, 10 October 2023](https://mypy-lang.blogspot.com/2023/10/mypy-16-released.html) + +Weā€™ve just uploaded mypy 1.6 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). Mypy is a static type checker for Python. This release includes new features, performance improvements and bug fixes. You can install it as follows: + + python3 -m pip install -U mypy + +You can read the full documentation for this release on [Read the Docs](http://mypy.readthedocs.io). + +#### Introduce Error Subcodes for Import Errors + +Mypy now uses the error code import-untyped if an import targets an installed library that doesnā€™t support static type checking, and no stub files are available. Other invalid imports produce the import-not-found error code. They both are subcodes of the import error code, which was previously used for both kinds of import-related errors. + +Use \--disable-error-code=import-untyped to only ignore import errors about installed libraries without stubs. This way mypy will still report errors about typos in import statements, for example. + +If you use \--warn-unused-ignore or \--strict, mypy will complain if you use \# type: ignore\[import\] to ignore an import error. You are expected to use one of the more specific error codes instead. Otherwise, ignoring the import error code continues to silence both errors. + +This feature was contributed by Shantanu (PR [15840](https://github.com/python/mypy/pull/15840), PR [14740](https://github.com/python/mypy/pull/14740)). + +#### Remove Support for Targeting Python 3.6 and Earlier + +Running mypy with \--python-version 3.6, for example, is no longer supported. Python 3.6 hasnā€™t been properly supported by mypy for some time now, and this makes it explicit. This was contributed by Nikita Sobolev (PR [15668](https://github.com/python/mypy/pull/15668)). + +#### Selective Filtering of \--disallow-untyped-calls Targets + +Using \--disallow-untyped-calls could be annoying when using libraries with missing type information, as mypy would generate many errors about code that uses the library. Now you can use \--untyped-calls-exclude=acme, for example, to disable these errors about calls targeting functions defined in the acme package. Refer to the [documentation](https://mypy.readthedocs.io/en/latest/command_line.html#cmdoption-mypy-untyped-calls-exclude) for more information. + +This feature was contributed by Ivan Levkivskyi (PR [15845](https://github.com/python/mypy/pull/15845)). + +#### Improved Type Inference between Callable Types + +Mypy now does a better job inferring type variables inside arguments of callable types. For example, this code fragment now type checks correctly: + +```python +def f(c: Callable[[T, S], None]) -> Callable[[str, T, S], None]: ... +def g(*x: int) -> None: ... + +reveal_type(f(g)) # Callable[[str, int, int], None] +``` + +This was contributed by Ivan Levkivskyi (PR [15910](https://github.com/python/mypy/pull/15910)). + +#### Donā€™t Consider None and TypeVar to Overlap in Overloads + +Mypy now doesnā€™t consider an overload item with an argument type None to overlap with a type variable: + +```python +@overload +def f(x: None) -> None: .. +@overload +def f(x: T) -> Foo[T]: ... +... +``` + +Previously mypy would generate an error about the definition of f above. This is slightly unsafe if the upper bound of T is object, since the value of the type variable could be None. We relaxed the rules a little, since this solves a common issue. + +This feature was contributed by Ivan Levkivskyi (PR [15846](https://github.com/python/mypy/pull/15846)). + +#### Improvements to \--new-type-inference + +The experimental new type inference algorithm (polymorphic inference) introduced as an opt-in feature in mypy 1.5 has several improvements: + +* Improve transitive closure computation during constraint solving (Ivan Levkivskyi, PR [15754](https://github.com/python/mypy/pull/15754)) +* Add support for upper bounds and values with \--new-type-inference (Ivan Levkivskyi, PR [15813](https://github.com/python/mypy/pull/15813)) +* Basic support for variadic types with \--new-type-inference (Ivan Levkivskyi, PR [15879](https://github.com/python/mypy/pull/15879)) +* Polymorphic inference: support for parameter specifications and lambdas (Ivan Levkivskyi, PR [15837](https://github.com/python/mypy/pull/15837)) +* Invalidate cache when adding \--new-type-inference (Marc Mueller, PR [16059](https://github.com/python/mypy/pull/16059)) + +**Note:** We are planning to enable \--new-type-inference by default in mypy 1.7. Please try this out and let us know if you encounter any issues. + +#### ParamSpec Improvements + +* Support self-types containing ParamSpec (Ivan Levkivskyi, PR [15903](https://github.com/python/mypy/pull/15903)) +* Allow ā€œā€¦ā€ in Concatenate, and clean up ParamSpec literals (Ivan Levkivskyi, PR [15905](https://github.com/python/mypy/pull/15905)) +* Fix ParamSpec inference for callback protocols (Ivan Levkivskyi, PR [15986](https://github.com/python/mypy/pull/15986)) +* Infer ParamSpec constraint from arguments (Ivan Levkivskyi, PR [15896](https://github.com/python/mypy/pull/15896)) +* Fix crash on invalid type variable with ParamSpec (Ivan Levkivskyi, PR [15953](https://github.com/python/mypy/pull/15953)) +* Fix subtyping between ParamSpecs (Ivan Levkivskyi, PR [15892](https://github.com/python/mypy/pull/15892)) + +#### Stubgen Improvements + +* Add option to include docstrings with stubgen (chylek, PR [13284](https://github.com/python/mypy/pull/13284)) +* Add required ... initializer to NamedTuple fields with default values (Nikita Sobolev, PR [15680](https://github.com/python/mypy/pull/15680)) + +#### Stubtest Improvements + +* Fix \_\_mypy-replace false positives (Alex Waygood, PR [15689](https://github.com/python/mypy/pull/15689)) +* Fix edge case for bytes enum subclasses (Alex Waygood, PR [15943](https://github.com/python/mypy/pull/15943)) +* Generate error if typeshed is missing modules from the stdlib (Alex Waygood, PR [15729](https://github.com/python/mypy/pull/15729)) +* Fixes to new check for missing stdlib modules (Alex Waygood, PR [15960](https://github.com/python/mypy/pull/15960)) +* Fix stubtest enum.Flag edge case (Alex Waygood, PR [15933](https://github.com/python/mypy/pull/15933)) + +#### Documentation Improvements + +* Do not advertise to create your own assert\_never helper (Nikita Sobolev, PR [15947](https://github.com/python/mypy/pull/15947)) +* Fix all the missing references found within the docs (Albert Tugushev, PR [15875](https://github.com/python/mypy/pull/15875)) +* Document await-not-async error code (Shantanu, PR [15858](https://github.com/python/mypy/pull/15858)) +* Improve documentation of disabling error codes (Shantanu, PR [15841](https://github.com/python/mypy/pull/15841)) + +#### Other Notable Changes and Fixes + +* Make unsupported PEP 695 features (introduced in Python 3.12) give a reasonable error message (Shantanu, PR [16013](https://github.com/python/mypy/pull/16013)) +* Remove the \--py2 command-line argument (Marc Mueller, PR [15670](https://github.com/python/mypy/pull/15670)) +* Change empty tuple from tuple\[\] to tuple\[()\] in error messages (Nikita Sobolev, PR [15783](https://github.com/python/mypy/pull/15783)) +* Fix assert\_type failures when some nodes are deferred (Nikita Sobolev, PR [15920](https://github.com/python/mypy/pull/15920)) +* Generate error on unbound TypeVar with values (Nikita Sobolev, PR [15732](https://github.com/python/mypy/pull/15732)) +* Fix over-eager types-google-cloud-ndb suggestion (Shantanu, PR [15347](https://github.com/python/mypy/pull/15347)) +* Fix type narrowing of \== None and in (None,) conditions (Marti Raudsepp, PR [15760](https://github.com/python/mypy/pull/15760)) +* Fix inference for attrs.fields (Shantanu, PR [15688](https://github.com/python/mypy/pull/15688)) +* Make ā€œawait in non-async functionā€ a non-blocking error and give it an error code (Gregory Santosa, PR [15384](https://github.com/python/mypy/pull/15384)) +* Add basic support for decorated overloads (Ivan Levkivskyi, PR [15898](https://github.com/python/mypy/pull/15898)) +* Fix TypeVar regression with self types (Ivan Levkivskyi, PR [15945](https://github.com/python/mypy/pull/15945)) +* Add \_\_match\_args\_\_ to dataclasses with no fields (Ali Hamdan, PR [15749](https://github.com/python/mypy/pull/15749)) +* Include stdout and stderr in dmypy verbose output (Valentin Stanciu, PR [15881](https://github.com/python/mypy/pull/15881)) +* Improve match narrowing and reachability analysis (Shantanu, PR [15882](https://github.com/python/mypy/pull/15882)) +* Support \_\_bool\_\_ with Literal in \--warn-unreachable (Jannic Warken, PR [15645](https://github.com/python/mypy/pull/15645)) +* Fix inheriting from generic @frozen attrs class (Ilya Priven, PR [15700](https://github.com/python/mypy/pull/15700)) +* Correctly narrow types for tuple\[type\[X\], ...\] (Nikita Sobolev, PR [15691](https://github.com/python/mypy/pull/15691)) +* Don't flag intentionally empty generators unreachable (Ilya Priven, PR [15722](https://github.com/python/mypy/pull/15722)) +* Add tox.ini to mypy sdist (Marcel Telka, PR [15853](https://github.com/python/mypy/pull/15853)) +* Fix mypyc regression with pretty (Shantanu, PR [16124](https://github.com/python/mypy/pull/16124)) + +#### Typeshed Updates + +Typeshed is now modular and distributed as separate PyPI packages for everything except the standard library stubs. Please see [git log](https://github.com/python/typeshed/commits/main?after=6a8d653a671925b0a3af61729ff8cf3f90c9c662+0&branch=main&path=stdlib) for full list of typeshed changes. + +#### Acknowledgements + +Thanks to Max Murin, who did most of the release manager work for this release (I just did the final steps). + +Thanks to all mypy contributors who contributed to this release: + +* Albert Tugushev +* Alex Waygood +* Ali Hamdan +* chylek +* EXPLOSION +* Gregory Santosa +* Ilya Priven +* Ivan Levkivskyi +* Jannic Warken +* KotlinIsland +* Marc Mueller +* Marcel Johannesmann +* Marcel Telka +* Mark Byrne +* Marti Raudsepp +* Max Murin +* Nikita Sobolev +* Shantanu +* Valentin Stanciu + +Posted by Jukka Lehtosalo + + +## Mypy 1.5 + +[Thursday, 10 August 2023](https://mypy-lang.blogspot.com/2023/08/mypy-15-released.html) + +Weā€™ve just uploaded mypy 1.5 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). Mypy is a static type checker for Python. This release includes new features, deprecations and bug fixes. You can install it as follows: + + python3 -m pip install -U mypy + +You can read the full documentation for this release on [Read the Docs](http://mypy.readthedocs.io). + +#### Drop Support for Python 3.7 + +Mypy no longer supports running with Python 3.7, which has reached end-of-life. This was contributed by Shantanu (PR [15566](https://github.com/python/mypy/pull/15566)). + +#### Optional Check to Require Explicit @override + +If you enable the explicit-override error code, mypy will generate an error if a method override doesnā€™t use the @typing.override decorator (as discussed in [PEP 698](https://peps.python.org/pep-0698/#strict-enforcement-per-project)). This way mypy will detect accidentally introduced overrides. Example: + +```python +# mypy: enable-error-code="explicit-override" + +from typing_extensions import override + +class C: + def foo(self) -> None: pass + def bar(self) -> None: pass + +class D(C): + # Error: Method "foo" is not using @override but is + # overriding a method + def foo(self) -> None: + ... + + @override + def bar(self) -> None: # OK + ... +``` + +You can enable the error code via \--enable-error-code=explicit-override on the mypy command line or enable\_error\_code = explicit-override in the mypy config file. + +The override decorator will be available in typing in Python 3.12, but you can also use the backport from a recent version of `typing_extensions` on all supported Python versions. + +This feature was contributed by Marc Mueller(PR [15512](https://github.com/python/mypy/pull/15512)). + +#### More Flexible TypedDict Creation and Update + +Mypy was previously overly strict when type checking TypedDict creation and update operations. Though these checks were often technically correct, they sometimes triggered for apparently valid code. These checks have now been relaxed by default. You can enable stricter checking by using the new \--extra-checks flag. + +Construction using the `**` syntax is now more flexible: + +```python +from typing import TypedDict + +class A(TypedDict): + foo: int + bar: int + +class B(TypedDict): + foo: int + +a: A = {"foo": 1, "bar": 2} +b: B = {"foo": 3} +a2: A = { **a, **b} # OK (previously an error) +``` + +You can also call update() with a TypedDict argument that contains a subset of the keys in the updated TypedDict: +```python +a.update(b) # OK (previously an error) +``` + +This feature was contributed by Ivan Levkivskyi (PR [15425](https://github.com/python/mypy/pull/15425)). + +#### Deprecated Flag: \--strict-concatenate + +The behavior of \--strict-concatenate is now included in the new \--extra-checks flag, and the old flag is deprecated. + +#### Optionally Show Links to Error Code Documentation + +If you use \--show-error-code-links, mypy will add documentation links to (many) reported errors. The links are not shown for error messages that are sufficiently obvious, and they are shown once per error code only. + +Example output: +``` +a.py:1: error: Need type annotation for "foo" (hint: "x: List[] = ...") [var-annotated] +a.py:1: note: See https://mypy.rtfd.io/en/stable/_refs.html#code-var-annotated for more info +``` +This was contributed by Ivan Levkivskyi (PR [15449](https://github.com/python/mypy/pull/15449)). + +#### Consistently Avoid Type Checking Unreachable Code + +If a module top level has unreachable code, mypy wonā€™t type check the unreachable statements. This is consistent with how functions behave. The behavior of \--warn-unreachable is also more consistent now. + +This was contributed by Ilya Priven (PR [15386](https://github.com/python/mypy/pull/15386)). + +#### Experimental Improved Type Inference for Generic Functions + +You can use \--new-type-inference to opt into an experimental new type inference algorithm. It fixes issues when calling a generic functions with an argument that is also a generic function, in particular. This current implementation is still incomplete, but we encourage trying it out and reporting bugs if you encounter regressions. We are planning to enable the new algorithm by default in a future mypy release. + +This feature was contributed by Ivan Levkivskyi (PR [15287](https://github.com/python/mypy/pull/15287)). + +#### Partial Support for Python 3.12 + +Mypy and mypyc now support running on recent Python 3.12 development versions. Not all new Python 3.12 features are supported, and we donā€™t ship compiled wheels for Python 3.12 yet. + +* Fix ast warnings for Python 3.12 (Nikita Sobolev, PR [15558](https://github.com/python/mypy/pull/15558)) +* mypyc: Fix multiple inheritance with a protocol on Python 3.12 (Jukka Lehtosalo, PR [15572](https://github.com/python/mypy/pull/15572)) +* mypyc: Fix self-compilation on Python 3.12 (Jukka Lehtosalo, PR [15582](https://github.com/python/mypy/pull/15582)) +* mypyc: Fix 3.12 issue with pickling of instances with \_\_dict\_\_ (Jukka Lehtosalo, PR [15574](https://github.com/python/mypy/pull/15574)) +* mypyc: Fix i16 on Python 3.12 (Jukka Lehtosalo, PR [15510](https://github.com/python/mypy/pull/15510)) +* mypyc: Fix int operations on Python 3.12 (Jukka Lehtosalo, PR [15470](https://github.com/python/mypy/pull/15470)) +* mypyc: Fix generators on Python 3.12 (Jukka Lehtosalo, PR [15472](https://github.com/python/mypy/pull/15472)) +* mypyc: Fix classes with \_\_dict\_\_ on 3.12 (Jukka Lehtosalo, PR [15471](https://github.com/python/mypy/pull/15471)) +* mypyc: Fix coroutines on Python 3.12 (Jukka Lehtosalo, PR [15469](https://github.com/python/mypy/pull/15469)) +* mypyc: Don't use \_PyErr\_ChainExceptions on 3.12, since it's deprecated (Jukka Lehtosalo, PR [15468](https://github.com/python/mypy/pull/15468)) +* mypyc: Add Python 3.12 feature macro (Jukka Lehtosalo, PR [15465](https://github.com/python/mypy/pull/15465)) + +#### Improvements to Dataclasses + +* Improve signature of dataclasses.replace (Ilya Priven, PR [14849](https://github.com/python/mypy/pull/14849)) +* Fix dataclass/protocol crash on joining types (Ilya Priven, PR [15629](https://github.com/python/mypy/pull/15629)) +* Fix strict optional handling in dataclasses (Ivan Levkivskyi, PR [15571](https://github.com/python/mypy/pull/15571)) +* Support optional types for custom dataclass descriptors (Marc Mueller, PR [15628](https://github.com/python/mypy/pull/15628)) +* Add `__slots__` attribute to dataclasses (Nikita Sobolev, PR [15649](https://github.com/python/mypy/pull/15649)) +* Support better \_\_post\_init\_\_ method signature for dataclasses (Nikita Sobolev, PR [15503](https://github.com/python/mypy/pull/15503)) + +#### Mypyc Improvements + +* Support unsigned 8-bit native integer type: mypy\_extensions.u8 (Jukka Lehtosalo, PR [15564](https://github.com/python/mypy/pull/15564)) +* Support signed 16-bit native integer type: mypy\_extensions.i16 (Jukka Lehtosalo, PR [15464](https://github.com/python/mypy/pull/15464)) +* Define mypy\_extensions.i16 in stubs (Jukka Lehtosalo, PR [15562](https://github.com/python/mypy/pull/15562)) +* Document more unsupported features and update supported features (Richard Si, PR [15524](https://github.com/python/mypy/pull/15524)) +* Fix final NamedTuple classes (Richard Si, PR [15513](https://github.com/python/mypy/pull/15513)) +* Use C99 compound literals for undefined tuple values (Jukka Lehtosalo, PR [15453](https://github.com/python/mypy/pull/15453)) +* Don't explicitly assign NULL values in setup functions (Logan Hunt, PR [15379](https://github.com/python/mypy/pull/15379)) + +#### Stubgen Improvements + +* Teach stubgen to work with complex and unary expressions (Nikita Sobolev, PR [15661](https://github.com/python/mypy/pull/15661)) +* Support ParamSpec and TypeVarTuple (Ali Hamdan, PR [15626](https://github.com/python/mypy/pull/15626)) +* Fix crash on non-str docstring (Ali Hamdan, PR [15623](https://github.com/python/mypy/pull/15623)) + +#### Documentation Updates + +* Add documentation for additional error codes (Ivan Levkivskyi, PR [15539](https://github.com/python/mypy/pull/15539)) +* Improve documentation of type narrowing (Ilya Priven, PR [15652](https://github.com/python/mypy/pull/15652)) +* Small improvements to protocol documentation (Shantanu, PR [15460](https://github.com/python/mypy/pull/15460)) +* Remove confusing instance variable example in cheat sheet (Adel Atallah, PR [15441](https://github.com/python/mypy/pull/15441)) + +#### Other Notable Fixes and Improvements + +* Constant fold additional unary and binary expressions (Richard Si, PR [15202](https://github.com/python/mypy/pull/15202)) +* Exclude the same special attributes from Protocol as CPython (Kyle Benesch, PR [15490](https://github.com/python/mypy/pull/15490)) +* Change the default value of the slots argument of attrs.define to True, to match runtime behavior (Ilya Priven, PR [15642](https://github.com/python/mypy/pull/15642)) +* Fix type of class attribute if attribute is defined in both class and metaclass (Alex Waygood, PR [14988](https://github.com/python/mypy/pull/14988)) +* Handle type the same as typing.Type in the first argument of classmethods (Erik Kemperman, PR [15297](https://github.com/python/mypy/pull/15297)) +* Fix \--find-occurrences flag (Shantanu, PR [15528](https://github.com/python/mypy/pull/15528)) +* Fix error location for class patterns (Nikita Sobolev, PR [15506](https://github.com/python/mypy/pull/15506)) +* Fix re-added file with errors in mypy daemon (Ivan Levkivskyi, PR [15440](https://github.com/python/mypy/pull/15440)) +* Fix dmypy run on Windows (Ivan Levkivskyi, PR [15429](https://github.com/python/mypy/pull/15429)) +* Fix abstract and non-abstract variant error for property deleter (Shantanu, PR [15395](https://github.com/python/mypy/pull/15395)) +* Remove special casing for "cannot" in error messages (Ilya Priven, PR [15428](https://github.com/python/mypy/pull/15428)) +* Add runtime `__slots__` attribute to attrs classes (Nikita Sobolev, PR [15651](https://github.com/python/mypy/pull/15651)) +* Add get\_expression\_type to CheckerPluginInterface (Ilya Priven, PR [15369](https://github.com/python/mypy/pull/15369)) +* Remove parameters that no longer exist from NamedTuple.\_make() (Alex Waygood, PR [15578](https://github.com/python/mypy/pull/15578)) +* Allow using typing.Self in `__all__` with an explicit @staticmethod decorator (Erik Kemperman, PR [15353](https://github.com/python/mypy/pull/15353)) +* Fix self types in subclass methods without Self annotation (Ivan Levkivskyi, PR [15541](https://github.com/python/mypy/pull/15541)) +* Check for abstract class objects in tuples (Nikita Sobolev, PR [15366](https://github.com/python/mypy/pull/15366)) + +#### Typeshed Updates + +Typeshed is now modular and distributed as separate PyPI packages for everything except the standard library stubs. Please see [git log](https://github.com/python/typeshed/commits/main?after=fc7d4722eaa54803926cee5730e1f784979c0531+0&branch=main&path=stdlib) for full list of typeshed changes. + +#### Acknowledgements + +Thanks to all mypy contributors who contributed to this release: + +* Adel Atallah +* Alex Waygood +* Ali Hamdan +* Erik Kemperman +* Federico Padua +* Ilya Priven +* Ivan Levkivskyi +* Jelle Zijlstra +* Jared Hance +* Jukka Lehtosalo +* Kyle Benesch +* Logan Hunt +* Marc Mueller +* Nikita Sobolev +* Richard Si +* Shantanu +* Stavros Ntentos +* Valentin Stanciu + +Posted by Valentin Stanciu + + +## Mypy 1.4 + +[Tuesday, 20 June 2023](https://mypy-lang.blogspot.com/2023/06/mypy-140-released.html) + +Weā€™ve just uploaded mypy 1.4 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). Mypy is a static type checker for Python. This release includes new features, performance improvements and bug fixes. You can install it as follows: + + python3 -m pip install -U mypy + +You can read the full documentation for this release on [Read the Docs](http://mypy.readthedocs.io). + +#### The Override Decorator + +Mypy can now ensure that when renaming a method, overrides are also renamed. You can explicitly mark a method as overriding a base class method by using the @typing.override decorator ([PEP 698](https://peps.python.org/pep-0698/)). If the method is then renamed in the base class while the method override is not, mypy will generate an error. The decorator will be available in typing in Python 3.12, but you can also use the backport from a recent version of `typing_extensions` on all supported Python versions. + +This feature was contributed byThomas M Kehrenberg (PR [14609](https://github.com/python/mypy/pull/14609)). + +#### Propagating Type Narrowing to Nested Functions + +Previously, type narrowing was not propagated to nested functions because it would not be sound if the narrowed variable changed between the definition of the nested function and the call site. Mypy will now propagate the narrowed type if the variable is not assigned to after the definition of the nested function: + +```python +def outer(x: str | None = None) -> None: + if x is None: + x = calculate_default() + reveal_type(x) # "str" (narrowed) + + def nested() -> None: + reveal_type(x) # Now "str" (used to be "str | None") + + nested() +``` + +This may generate some new errors because asserts that were previously necessary may become tautological or no-ops. + +This was contributed by Jukka Lehtosalo (PR [15133](https://github.com/python/mypy/pull/15133)). + +#### Narrowing Enum Values Using ā€œ==ā€ + +Mypy now allows narrowing enum types using the \== operator. Previously this was only supported when using the is operator. This makes exhaustiveness checking with enum types more usable, as the requirement to use the is operator was not very intuitive. In this example mypy can detect that the developer forgot to handle the value MyEnum.C in example + +```python +from enum import Enum + +class MyEnum(Enum): + A = 0 + B = 1 + C = 2 + +def example(e: MyEnum) -> str: # Error: Missing return statement + if e == MyEnum.A: + return 'x' + elif e == MyEnum.B: + return 'y' +``` + +Adding an extra elif case resolves the error: + +```python +... +def example(e: MyEnum) -> str: # No error -- all values covered + if e == MyEnum.A: + return 'x' + elif e == MyEnum.B: + return 'y' + elif e == MyEnum.C: + return 'z' +``` + +This change can cause false positives in test cases that have assert statements like assert o.x == SomeEnum.X when using \--strict-equality. Example: + +```python +# mypy: strict-equality + +from enum import Enum + +class MyEnum(Enum): + A = 0 + B = 1 + +class C: + x: MyEnum + ... + +def test_something() -> None: + c = C(...) + assert c.x == MyEnum.A + c.do_something_that_changes_x() + assert c.x == MyEnum.B # Error: Non-overlapping equality check +``` + +These errors can be ignored using \# type: ignore\[comparison-overlap\], or you can perform the assertion using a temporary variable as a workaround: + +```python +... +def test_something() -> None: + ... + x = c.x + assert x == MyEnum.A # Does not narrow c.x + c.do_something_that_changes_x() + x = c.x + assert x == MyEnum.B # OK +``` + +This feature was contributed by Shantanu (PR [11521](https://github.com/python/mypy/pull/11521)). + +#### Performance Improvements + +* Speed up simplification of large union types and also fix a recursive tuple crash (Shantanu, PR [15128](https://github.com/python/mypy/pull/15128)) +* Speed up union subtyping (Shantanu, PR [15104](https://github.com/python/mypy/pull/15104)) +* Don't type check most function bodies when type checking third-party library code, or generally when ignoring errors (Jukka Lehtosalo, PR [14150](https://github.com/python/mypy/pull/14150)) + +#### Improvements to Plugins + +* attrs.evolve: Support generics and unions (Ilya Konstantinov, PR [15050](https://github.com/python/mypy/pull/15050)) +* Fix ctypes plugin (Alex Waygood) + +#### Fixes to Crashes + +* Fix a crash when function-scope recursive alias appears as upper bound (Ivan Levkivskyi, PR [15159](https://github.com/python/mypy/pull/15159)) +* Fix crash on follow\_imports\_for\_stubs (Ivan Levkivskyi, PR [15407](https://github.com/python/mypy/pull/15407)) +* Fix stubtest crash in explicit init subclass (Shantanu, PR [15399](https://github.com/python/mypy/pull/15399)) +* Fix crash when indexing TypedDict with empty key (Shantanu, PR [15392](https://github.com/python/mypy/pull/15392)) +* Fix crash on NamedTuple as attribute (Ivan Levkivskyi, PR [15404](https://github.com/python/mypy/pull/15404)) +* Correctly track loop depth for nested functions/classes (Ivan Levkivskyi, PR [15403](https://github.com/python/mypy/pull/15403)) +* Fix crash on joins with recursive tuples (Ivan Levkivskyi, PR [15402](https://github.com/python/mypy/pull/15402)) +* Fix crash with custom ErrorCode subclasses (Marc Mueller, PR [15327](https://github.com/python/mypy/pull/15327)) +* Fix crash in dataclass protocol with self attribute assignment (Ivan Levkivskyi, PR [15157](https://github.com/python/mypy/pull/15157)) +* Fix crash on lambda in generic context with generic method in body (Ivan Levkivskyi, PR [15155](https://github.com/python/mypy/pull/15155)) +* Fix recursive type alias crash in make\_simplified\_union (Ivan Levkivskyi, PR [15216](https://github.com/python/mypy/pull/15216)) + +#### Improvements to Error Messages + +* Use lower-case built-in collection types such as list\[ā€¦\] instead of List\[ā€¦\] in errors when targeting Python 3.9+ (Max Murin, PR [15070](https://github.com/python/mypy/pull/15070)) +* Use X | Y union syntax in error messages when targeting Python 3.10+ (Omar Silva, PR [15102](https://github.com/python/mypy/pull/15102)) +* Use type instead of Type in errors when targeting Python 3.9+ (Rohit Sanjay, PR [15139](https://github.com/python/mypy/pull/15139)) +* Do not show unused-ignore errors in unreachable code, and make it a real error code (Ivan Levkivskyi, PR [15164](https://github.com/python/mypy/pull/15164)) +* Donā€™t limit the number of errors shown by default (Rohit Sanjay, PR [15138](https://github.com/python/mypy/pull/15138)) +* Improver message for truthy functions (madt2709, PR [15193](https://github.com/python/mypy/pull/15193)) +* Output distinct types when type names are ambiguous (teresa0605, PR [15184](https://github.com/python/mypy/pull/15184)) +* Update message about invalid exception type in try (AJ Rasmussen, PR [15131](https://github.com/python/mypy/pull/15131)) +* Add explanation if argument type is incompatible because of an unsupported numbers type (Jukka Lehtosalo, PR [15137](https://github.com/python/mypy/pull/15137)) +* Add more detail to 'signature incompatible with supertype' messages for non-callables (Ilya Priven, PR [15263](https://github.com/python/mypy/pull/15263)) + +#### Documentation Updates + +* Add \--local-partial-types note to dmypy docs (Alan Du, PR [15259](https://github.com/python/mypy/pull/15259)) +* Update getting started docs for mypyc for Windows (Valentin Stanciu, PR [15233](https://github.com/python/mypy/pull/15233)) +* Clarify usage of callables regarding type object in docs (Viicos, PR [15079](https://github.com/python/mypy/pull/15079)) +* Clarify difference between disallow\_untyped\_defs and disallow\_incomplete\_defs (Ilya Priven, PR [15247](https://github.com/python/mypy/pull/15247)) +* Use attrs and @attrs.define in documentation and tests (Ilya Priven, PR [15152](https://github.com/python/mypy/pull/15152)) + +#### Mypyc Improvements + +* Fix unexpected TypeError for certain variables with an inferred optional type (Richard Si, PR [15206](https://github.com/python/mypy/pull/15206)) +* Inline math literals (Logan Hunt, PR [15324](https://github.com/python/mypy/pull/15324)) +* Support unpacking mappings in dict display (Richard Si, PR [15203](https://github.com/python/mypy/pull/15203)) + +#### Changes to Stubgen + +* Do not remove Generic from base classes (Ali Hamdan, PR [15316](https://github.com/python/mypy/pull/15316)) +* Support yield from statements (Ali Hamdan, PR [15271](https://github.com/python/mypy/pull/15271)) +* Fix missing total from TypedDict class (Ali Hamdan, PR [15208](https://github.com/python/mypy/pull/15208)) +* Fix call-based namedtuple omitted from class bases (Ali Hamdan, PR [14680](https://github.com/python/mypy/pull/14680)) +* Support TypedDict alternative syntax (Ali Hamdan, PR [14682](https://github.com/python/mypy/pull/14682)) +* Make stubgen respect MYPY\_CACHE\_DIR (Henrik BƤƤrnhielm, PR [14722](https://github.com/python/mypy/pull/14722)) +* Fixes and simplifications (Ali Hamdan, PR [15232](https://github.com/python/mypy/pull/15232)) + +#### Other Notable Fixes and Improvements + +* Fix nested async functions when using TypeVar value restriction (Jukka Lehtosalo, PR [14705](https://github.com/python/mypy/pull/14705)) +* Always allow returning Any from lambda (Ivan Levkivskyi, PR [15413](https://github.com/python/mypy/pull/15413)) +* Add foundation for TypeVar defaults (PEP 696) (Marc Mueller, PR [14872](https://github.com/python/mypy/pull/14872)) +* Update semantic analyzer for TypeVar defaults (PEP 696) (Marc Mueller, PR [14873](https://github.com/python/mypy/pull/14873)) +* Make dict expression inference more consistent (Ivan Levkivskyi, PR [15174](https://github.com/python/mypy/pull/15174)) +* Do not block on duplicate base classes (Nikita Sobolev, PR [15367](https://github.com/python/mypy/pull/15367)) +* Generate an error when both staticmethod and classmethod decorators are used (Juhi Chandalia, PR [15118](https://github.com/python/mypy/pull/15118)) +* Fix assert\_type behaviour with literals (Carl Karsten, PR [15123](https://github.com/python/mypy/pull/15123)) +* Fix match subject ignoring redefinitions (Vincent Vanlaer, PR [15306](https://github.com/python/mypy/pull/15306)) +* Support `__all__`.remove (Shantanu, PR [15279](https://github.com/python/mypy/pull/15279)) + +#### Typeshed Updates + +Typeshed is now modular and distributed as separate PyPI packages for everything except the standard library stubs. Please see [git log](https://github.com/python/typeshed/commits/main?after=877e06ad1cfd9fd9967c0b0340a86d0c23ea89ce+0&branch=main&path=stdlib) for full list of typeshed changes. + +#### Acknowledgements + +Thanks to all mypy contributors who contributed to this release: + +* Adrian Garcia Badaracco +* AJ Rasmussen +* Alan Du +* Alex Waygood +* Ali Hamdan +* Carl Karsten +* dosisod +* Ethan Smith +* Gregory Santosa +* Heather White +* Henrik BƤƤrnhielm +* Ilya Konstantinov +* Ilya Priven +* Ivan Levkivskyi +* Juhi Chandalia +* Jukka Lehtosalo +* Logan Hunt +* madt2709 +* Marc Mueller +* Max Murin +* Nikita Sobolev +* Omar Silva +* ƖzgĆ¼r +* Richard Si +* Rohit Sanjay +* Shantanu +* teresa0605 +* Thomas M Kehrenberg +* Tin Tvrtković +* Tushar Sadhwani +* Valentin Stanciu +* Viicos +* Vincent Vanlaer +* Wesley Collin Wright +* William Santosa +* yaegassy + +Iā€™d also like to thank my employer, Dropbox, for supporting mypy development. + +Posted by Jared Hance + + +## Mypy 1.3 + +[Wednesday, 10 May 2023](https://mypy-lang.blogspot.com/2023/05/mypy-13-released.html) + + Weā€™ve just uploaded mypy 1.3 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). Mypy is a static type checker for Python. This release includes new features, performance improvements and bug fixes. You can install it as follows: + + python3 -m pip install -U mypy + +You can read the full documentation for this release on [Read the Docs](http://mypy.readthedocs.io). + +#### Performance Improvements + +* Improve performance of union subtyping (Shantanu, PR [15104](https://github.com/python/mypy/pull/15104)) +* Add negative subtype caches (Ivan Levkivskyi, PR [14884](https://github.com/python/mypy/pull/14884)) + +#### Stub Tooling Improvements + +* Stubtest: Check that the stub is abstract if the runtime is, even when the stub is an overloaded method (Alex Waygood, PR [14955](https://github.com/python/mypy/pull/14955)) +* Stubtest: Verify stub methods or properties are decorated with @final if they are decorated with @final at runtime (Alex Waygood, PR [14951](https://github.com/python/mypy/pull/14951)) +* Stubtest: Fix stubtest false positives with TypedDicts at runtime (Alex Waygood, PR [14984](https://github.com/python/mypy/pull/14984)) +* Stubgen: Support @functools.cached\_property (Nikita Sobolev, PR [14981](https://github.com/python/mypy/pull/14981)) +* Improvements to stubgenc (Chad Dombrova, PR [14564](https://github.com/python/mypy/pull/14564)) + +#### Improvements to attrs + +* Add support for converters with TypeVars on generic attrs classes (Chad Dombrova, PR [14908](https://github.com/python/mypy/pull/14908)) +* Fix attrs.evolve on bound TypeVar (Ilya Konstantinov, PR [15022](https://github.com/python/mypy/pull/15022)) + +#### Documentation Updates + +* Improve async documentation (Shantanu, PR [14973](https://github.com/python/mypy/pull/14973)) +* Improvements to cheat sheet (Shantanu, PR [14972](https://github.com/python/mypy/pull/14972)) +* Add documentation for bytes formatting error code (Shantanu, PR [14971](https://github.com/python/mypy/pull/14971)) +* Convert insecure links to use HTTPS (Marti Raudsepp, PR [14974](https://github.com/python/mypy/pull/14974)) +* Also mention overloads in async iterator documentation (Shantanu, PR [14998](https://github.com/python/mypy/pull/14998)) +* stubtest: Improve allowlist documentation (Shantanu, PR [15008](https://github.com/python/mypy/pull/15008)) +* Clarify "Using types... but not at runtime" (Jon Shea, PR [15029](https://github.com/python/mypy/pull/15029)) +* Fix alignment of cheat sheet example (Ondřej Cvacho, PR [15039](https://github.com/python/mypy/pull/15039)) +* Fix error for callback protocol matching against callable type object (Shantanu, PR [15042](https://github.com/python/mypy/pull/15042)) + +#### Error Reporting Improvements + +* Improve bytes formatting error (Shantanu, PR [14959](https://github.com/python/mypy/pull/14959)) + +#### Mypyc Improvements + +* Fix unions of bools and ints (Tomer Chachamu, PR [15066](https://github.com/python/mypy/pull/15066)) + +#### Other Fixes and Improvements + +* Fix narrowing union types that include Self with isinstance (Christoph Tyralla, PR [14923](https://github.com/python/mypy/pull/14923)) +* Allow objects matching SupportsKeysAndGetItem to be unpacked (Bryan Forbes, PR [14990](https://github.com/python/mypy/pull/14990)) +* Check type guard validity for staticmethods (EXPLOSION, PR [14953](https://github.com/python/mypy/pull/14953)) +* Fix sys.platform when cross-compiling with emscripten (Ethan Smith, PR [14888](https://github.com/python/mypy/pull/14888)) + +#### Typeshed Updates + +Typeshed is now modular and distributed as separate PyPI packages for everything except the standard library stubs. Please see [git log](https://github.com/python/typeshed/commits/main?after=b0ed50e9392a23e52445b630a808153e0e256976+0&branch=main&path=stdlib) for full list of typeshed changes. + +#### Acknowledgements + +Thanks to all mypy contributors who contributed to this release: + +* Alex Waygood +* Amin Alaee +* Bryan Forbes +* Chad Dombrova +* Charlie Denton +* Christoph Tyralla +* dosisod +* Ethan Smith +* EXPLOSION +* Ilya Konstantinov +* Ivan Levkivskyi +* Jon Shea +* Jukka Lehtosalo +* KotlinIsland +* Marti Raudsepp +* Nikita Sobolev +* Ondřej Cvacho +* Shantanu +* sobolevn +* Tomer Chachamu +* Yaroslav Halchenko + +Posted by Wesley Collin Wright. + + +## Mypy 1.2 + +[Thursday, 6 April 2023](https://mypy-lang.blogspot.com/2023/04/mypy-12-released.html) + +Weā€™ve just uploaded mypy 1.2 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). Mypy is a static type checker for Python. This release includes new features, performance improvements and bug fixes. You can install it as follows: + + python3 -m pip install -U mypy + +You can read the full documentation for this release on [Read the Docs](http://mypy.readthedocs.io). + +#### Improvements to Dataclass Transforms + +* Support implicit default for "init" parameter in field specifiers (Wesley Collin Wright and Jukka Lehtosalo, PR [15010](https://github.com/python/mypy/pull/15010)) +* Support descriptors in dataclass transform (Jukka Lehtosalo, PR [15006](https://github.com/python/mypy/pull/15006)) +* Fix frozen\_default in incremental mode (Wesley Collin Wright) +* Fix frozen behavior for base classes with direct metaclasses (Wesley Collin Wright, PR [14878](https://github.com/python/mypy/pull/14878)) + +#### Mypyc: Native Floats + +Mypyc now uses a native, unboxed representation for values of type float. Previously these were heap-allocated Python objects. Native floats are faster and use less memory. Code that uses floating-point operations heavily can be several times faster when using native floats. + +Various float operations and math functions also now have optimized implementations. Refer to the [documentation](https://mypyc.readthedocs.io/en/latest/float_operations.html) for a full list. + +This can change the behavior of existing code that uses subclasses of float. When assigning an instance of a subclass of float to a variable with the float type, it gets implicitly converted to a float instance when compiled: + +```python +from lib import MyFloat # MyFloat ia a subclass of "float" + +def example() -> None: + x = MyFloat(1.5) + y: float = x # Implicit conversion from MyFloat to float + print(type(y)) # float, not MyFloat +``` + +Previously, implicit conversions were applied to int subclasses but not float subclasses. + +Also, int values can no longer be assigned to a variable with type float in compiled code, since these types now have incompatible representations. An explicit conversion is required: + +```python +def example(n: int) -> None: + a: float = 1 # Error: cannot assign "int" to "float" + b: float = 1.0 # OK + c: float = n # Error + d: float = float(n) # OK +``` + +This restriction only applies to assignments, since they could otherwise narrow down the type of a variable from float to int. int values can still be implicitly converted to float when passed as arguments to functions that expect float values. + +Note that mypyc still doesnā€™t support arrays of unboxed float values. Using list\[float\] involves heap-allocated float objects, since list can only store boxed values. Support for efficient floating point arrays is one of the next major planned mypyc features. + +Related changes: + +* Use a native unboxed representation for floats (Jukka Lehtosalo, PR [14880](https://github.com/python/mypy/pull/14880)) +* Document native floats and integers (Jukka Lehtosalo, PR [14927](https://github.com/python/mypy/pull/14927)) +* Fixes to float to int conversion (Jukka Lehtosalo, PR [14936](https://github.com/python/mypy/pull/14936)) + +#### Mypyc: Native Integers + +Mypyc now supports signed 32-bit and 64-bit integer types in addition to the arbitrary-precision int type. You can use the types mypy\_extensions.i32 and mypy\_extensions.i64 to speed up code that uses integer operations heavily. + +Simple example: +```python +from mypy_extensions import i64 + +def inc(x: i64) -> i64: + return x + 1 +``` + +Refer to the [documentation](https://mypyc.readthedocs.io/en/latest/using_type_annotations.html#native-integer-types) for more information. This feature was contributed by Jukka Lehtosalo. + +#### Other Mypyc Fixes and Improvements + +* Support iterating over a TypedDict (Richard Si, PR [14747](https://github.com/python/mypy/pull/14747)) +* Faster coercions between different tuple types (Jukka Lehtosalo, PR [14899](https://github.com/python/mypy/pull/14899)) +* Faster calls via type aliases (Jukka Lehtosalo, PR [14784](https://github.com/python/mypy/pull/14784)) +* Faster classmethod calls via cls (Jukka Lehtosalo, PR [14789](https://github.com/python/mypy/pull/14789)) + +#### Fixes to Crashes + +* Fix crash on class-level import in protocol definition (Ivan Levkivskyi, PR [14926](https://github.com/python/mypy/pull/14926)) +* Fix crash on single item union of alias (Ivan Levkivskyi, PR [14876](https://github.com/python/mypy/pull/14876)) +* Fix crash on ParamSpec in incremental mode (Ivan Levkivskyi, PR [14885](https://github.com/python/mypy/pull/14885)) + +#### Documentation Updates + +* Update adopting \--strict documentation for 1.0 (Shantanu, PR [14865](https://github.com/python/mypy/pull/14865)) +* Some minor documentation tweaks (Jukka Lehtosalo, PR [14847](https://github.com/python/mypy/pull/14847)) +* Improve documentation of top level mypy: disable-error-code comment (Nikita Sobolev, PR [14810](https://github.com/python/mypy/pull/14810)) + +#### Error Reporting Improvements + +* Add error code to `typing_extensions` suggestion (Shantanu, PR [14881](https://github.com/python/mypy/pull/14881)) +* Add a separate error code for top-level await (Nikita Sobolev, PR [14801](https://github.com/python/mypy/pull/14801)) +* Donā€™t suggest two obsolete stub packages (Jelle Zijlstra, PR [14842](https://github.com/python/mypy/pull/14842)) +* Add suggestions for pandas-stubs and lxml-stubs (Shantanu, PR [14737](https://github.com/python/mypy/pull/14737)) + +#### Other Fixes and Improvements + +* Multiple inheritance considers callable objects as subtypes of functions (Christoph Tyralla, PR [14855](https://github.com/python/mypy/pull/14855)) +* stubtest: Respect @final runtime decorator and enforce it in stubs (Nikita Sobolev, PR [14922](https://github.com/python/mypy/pull/14922)) +* Fix false positives related to type\[\] (sterliakov, PR [14756](https://github.com/python/mypy/pull/14756)) +* Fix duplication of ParamSpec prefixes and properly substitute ParamSpecs (EXPLOSION, PR [14677](https://github.com/python/mypy/pull/14677)) +* Fix line number if `__iter__` is incorrectly reported as missing (Jukka Lehtosalo, PR [14893](https://github.com/python/mypy/pull/14893)) +* Fix incompatible overrides of overloaded generics with self types (Shantanu, PR [14882](https://github.com/python/mypy/pull/14882)) +* Allow SupportsIndex in slice expressions (Shantanu, PR [14738](https://github.com/python/mypy/pull/14738)) +* Support if statements in bodies of dataclasses and classes that use dataclass\_transform (Jacek Chałupka, PR [14854](https://github.com/python/mypy/pull/14854)) +* Allow iterable class objects to be unpacked (including enums) (Alex Waygood, PR [14827](https://github.com/python/mypy/pull/14827)) +* Fix narrowing for walrus expressions used in match statements (Shantanu, PR [14844](https://github.com/python/mypy/pull/14844)) +* Add signature for attr.evolve (Ilya Konstantinov, PR [14526](https://github.com/python/mypy/pull/14526)) +* Fix Any inference when unpacking iterators that don't directly inherit from typing.Iterator (Alex Waygood, PR [14821](https://github.com/python/mypy/pull/14821)) +* Fix unpack with overloaded `__iter__` method (Nikita Sobolev, PR [14817](https://github.com/python/mypy/pull/14817)) +* Reduce size of JSON data in mypy cache (dosisod, PR [14808](https://github.com/python/mypy/pull/14808)) +* Improve ā€œused before definitionā€ checks when a local definition has the same name as a global definition (Stas Ilinskiy, PR [14517](https://github.com/python/mypy/pull/14517)) +* Honor NoReturn as \_\_setitem\_\_ return type to mark unreachable code (sterliakov, PR [12572](https://github.com/python/mypy/pull/12572)) + +#### Typeshed Updates + +Typeshed is now modular and distributed as separate PyPI packages for everything except the standard library stubs. Please see [git log](https://github.com/python/typeshed/commits/main?after=a544b75320e97424d2d927605316383c755cdac0+0&branch=main&path=stdlib) for full list of typeshed changes. + +#### Acknowledgements + +Thanks to all mypy contributors who contributed to this release: + +* Alex Waygood +* Avasam +* Christoph Tyralla +* dosisod +* EXPLOSION +* Ilya Konstantinov +* Ivan Levkivskyi +* Jacek Chałupka +* Jelle Zijlstra +* Jukka Lehtosalo +* Marc Mueller +* Max Murin +* Nikita Sobolev +* Richard Si +* Shantanu +* Stas Ilinskiy +* sterliakov +* Wesley Collin Wright + +Posted by Jukka Lehtosalo + + +## Mypy 1.1.1 + +[Monday, 6 March 2023](https://mypy-lang.blogspot.com/2023/03/mypy-111-released.html) + + Weā€™ve just uploaded mypy 1.1.1 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). Mypy is a static type checker for Python. This release includes new features, performance improvements and bug fixes. You can install it as follows: + + python3 -m pip install -U mypy + +You can read the full documentation for this release on [Read the Docs](http://mypy.readthedocs.io). + +#### Support for `dataclass_transform`` + +This release adds full support for the dataclass\_transform decorator defined in [PEP 681](https://peps.python.org/pep-0681/#decorator-function-example). This allows decorators, base classes, and metaclasses that generate a \_\_init\_\_ method or other methods based on the properties of that class (similar to dataclasses) to have those methods recognized by mypy. + +This was contributed by Wesley Collin Wright. + +#### Dedicated Error Code for Method Assignments + +Mypy canā€™t safely check all assignments to methods (a form of monkey patching), so mypy generates an error by default. To make it easier to ignore this error, mypy now uses the new error code method-assign for this. By disabling this error code in a file or globally, mypy will no longer complain about assignments to methods if the signatures are compatible. + +Mypy also supports the old error code assignment for these assignments to prevent a backward compatibility break. More generally, we can use this mechanism in the future if we wish to split or rename another existing error code without causing backward compatibility issues. + +This was contributed by Ivan Levkivskyi (PR [14570](https://github.com/python/mypy/pull/14570)). + +#### Fixes to Crashes + +* Fix a crash on walrus in comprehension at class scope (Ivan Levkivskyi, PR [14556](https://github.com/python/mypy/pull/14556)) +* Fix crash related to value-constrained TypeVar (Shantanu, PR [14642](https://github.com/python/mypy/pull/14642)) + +#### Fixes to Cache Corruption + +* Fix generic TypedDict/NamedTuple caching (Ivan Levkivskyi, PR [14675](https://github.com/python/mypy/pull/14675)) + +#### Mypyc Fixes and Improvements + +* Raise "non-trait base must be first..." error less frequently (Richard Si, PR [14468](https://github.com/python/mypy/pull/14468)) +* Generate faster code for bool comparisons and arithmetic (Jukka Lehtosalo, PR [14489](https://github.com/python/mypy/pull/14489)) +* Optimize \_\_(a)enter\_\_/\_\_(a)exit\_\_ for native classes (Jared Hance, PR [14530](https://github.com/python/mypy/pull/14530)) +* Detect if attribute definition conflicts with base class/trait (Jukka Lehtosalo, PR [14535](https://github.com/python/mypy/pull/14535)) +* Support \_\_(r)divmod\_\_ dunders (Richard Si, PR [14613](https://github.com/python/mypy/pull/14613)) +* Support \_\_pow\_\_, \_\_rpow\_\_, and \_\_ipow\_\_ dunders (Richard Si, PR [14616](https://github.com/python/mypy/pull/14616)) +* Fix crash on star unpacking to underscore (Ivan Levkivskyi, PR [14624](https://github.com/python/mypy/pull/14624)) +* Fix iterating over a union of dicts (Richard Si, PR [14713](https://github.com/python/mypy/pull/14713)) + +#### Fixes to Detecting Undefined Names (used-before-def) + +* Correctly handle walrus operator (Stas Ilinskiy, PR [14646](https://github.com/python/mypy/pull/14646)) +* Handle walrus declaration in match subject correctly (Stas Ilinskiy, PR [14665](https://github.com/python/mypy/pull/14665)) + +#### Stubgen Improvements + +Stubgen is a tool for automatically generating draft stubs for libraries. + +* Allow aliases below the top level (Chad Dombrova, PR [14388](https://github.com/python/mypy/pull/14388)) +* Fix crash with PEP 604 union in type variable bound (Shantanu, PR [14557](https://github.com/python/mypy/pull/14557)) +* Preserve PEP 604 unions in generated .pyi files (hamdanal, PR [14601](https://github.com/python/mypy/pull/14601)) + +#### Stubtest Improvements + +Stubtest is a tool for testing that stubs conform to the implementations. + +* Update message format so that itā€™s easier to go to error location (Avasam, PR [14437](https://github.com/python/mypy/pull/14437)) +* Handle name-mangling edge cases better (Alex Waygood, PR [14596](https://github.com/python/mypy/pull/14596)) + +#### Changes to Error Reporting and Messages + +* Add new TypedDict error code typeddict-unknown-key (JoaquimEsteves, PR [14225](https://github.com/python/mypy/pull/14225)) +* Give arguments a more reasonable location in error messages (Max Murin, PR [14562](https://github.com/python/mypy/pull/14562)) +* In error messages, quote just the module's name (Ilya Konstantinov, PR [14567](https://github.com/python/mypy/pull/14567)) +* Improve misleading message about Enum() (Rodrigo Silva, PR [14590](https://github.com/python/mypy/pull/14590)) +* Suggest importing from `typing_extensions` if definition is not in typing (Shantanu, PR [14591](https://github.com/python/mypy/pull/14591)) +* Consistently use type-abstract error code (Ivan Levkivskyi, PR [14619](https://github.com/python/mypy/pull/14619)) +* Consistently use literal-required error code for TypedDicts (Ivan Levkivskyi, PR [14621](https://github.com/python/mypy/pull/14621)) +* Adjust inconsistent dataclasses plugin error messages (Wesley Collin Wright, PR [14637](https://github.com/python/mypy/pull/14637)) +* Consolidate literal bool argument error messages (Wesley Collin Wright, PR [14693](https://github.com/python/mypy/pull/14693)) + +#### Other Fixes and Improvements + +* Check that type guards accept a positional argument (EXPLOSION, PR [14238](https://github.com/python/mypy/pull/14238)) +* Fix bug with in operator used with a union of Container and Iterable (Max Murin, PR [14384](https://github.com/python/mypy/pull/14384)) +* Support protocol inference for type\[T\] via metaclass (Ivan Levkivskyi, PR [14554](https://github.com/python/mypy/pull/14554)) +* Allow overlapping comparisons between bytes-like types (Shantanu, PR [14658](https://github.com/python/mypy/pull/14658)) +* Fix mypy daemon documentation link in README (Ivan Levkivskyi, PR [14644](https://github.com/python/mypy/pull/14644)) + +#### Typeshed Updates + +Typeshed is now modular and distributed as separate PyPI packages for everything except the standard library stubs. Please see [git log](https://github.com/python/typeshed/commits/main?after=5ebf892d0710a6e87925b8d138dfa597e7bb11cc+0&branch=main&path=stdlib) for full list of typeshed changes. + +#### Acknowledgements + +Thanks to all mypy contributors who contributed to this release: + +* Alex Waygood +* Avasam +* Chad Dombrova +* dosisod +* EXPLOSION +* hamdanal +* Ilya Konstantinov +* Ivan Levkivskyi +* Jared Hance +* JoaquimEsteves +* Jukka Lehtosalo +* Marc Mueller +* Max Murin +* Michael Lee +* Michael R. Crusoe +* Richard Si +* Rodrigo Silva +* Shantanu +* Stas Ilinskiy +* Wesley Collin Wright +* Yilei "Dolee" Yang +* Yurii Karabas + +Weā€™d also like to thank our employer, Dropbox, for funding the mypy core team. + +Posted by Max Murin + + +## Mypy 1.0 + +[Monday, 6 February 2023](https://mypy-lang.blogspot.com/2023/02/mypy-10-released.html) + +Weā€™ve just uploaded mypy 1.0 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). Mypy is a static type checker for Python. This release includes new features, performance improvements and bug fixes. You can install it as follows: + + python3 -m pip install -U mypy + +You can read the full documentation for this release on [Read the Docs](http://mypy.readthedocs.io). + +#### New Release Versioning Scheme + +Now that mypy reached 1.0, weā€™ll switch to a new versioning scheme. Mypy version numbers will be of form x.y.z. + +Rules: + +* The major release number (x) is incremented if a feature release includes a significant backward incompatible change that affects a significant fraction of users. +* The minor release number (y) is incremented on each feature release. Minor releases include updated stdlib stubs from typeshed. +* The point release number (z) is incremented when there are fixes only. + +Mypy doesn't use SemVer, since most minor releases have at least minor backward incompatible changes in typeshed, at the very least. Also, many type checking features find new legitimate issues in code. These are not considered backward incompatible changes, unless the number of new errors is very high. + +Any significant backward incompatible change must be announced in the blog post for the previous feature release, before making the change. The previous release must also provide a flag to explicitly enable or disable the new behavior (whenever practical), so that users will be able to prepare for the changes and report issues. We should keep the feature flag for at least a few releases after we've switched the default. + +See [ā€Release Processā€ in the mypy wiki](https://github.com/python/mypy/wiki/Release-Process) for more details and for the most up-to-date version of the versioning scheme. + +#### Performance Improvements + +Mypy 1.0 is up to 40% faster than mypy 0.991 when type checking the Dropbox internal codebase. We also set up a daily job to measure the performance of the most recent development version of mypy to make it easier to track changes in performance. + +Many optimizations contributed to this improvement: + +* Improve performance for errors on class with many attributes (Shantanu, PR [14379](https://github.com/python/mypy/pull/14379)) +* Speed up make\_simplified\_union (Jukka Lehtosalo, PR [14370](https://github.com/python/mypy/pull/14370)) +* Micro-optimize get\_proper\_type(s) (Jukka Lehtosalo, PR [14369](https://github.com/python/mypy/pull/14369)) +* Micro-optimize flatten\_nested\_unions (Jukka Lehtosalo, PR [14368](https://github.com/python/mypy/pull/14368)) +* Some semantic analyzer micro-optimizations (Jukka Lehtosalo, PR [14367](https://github.com/python/mypy/pull/14367)) +* A few miscellaneous micro-optimizations (Jukka Lehtosalo, PR [14366](https://github.com/python/mypy/pull/14366)) +* Optimization: Avoid a few uses of contextmanagers in semantic analyzer (Jukka Lehtosalo, PR [14360](https://github.com/python/mypy/pull/14360)) +* Optimization: Enable always defined attributes in Type subclasses (Jukka Lehtosalo, PR [14356](https://github.com/python/mypy/pull/14356)) +* Optimization: Remove expensive context manager in type analyzer (Jukka Lehtosalo, PR [14357](https://github.com/python/mypy/pull/14357)) +* subtypes: fast path for Union/Union subtype check (Hugues, PR [14277](https://github.com/python/mypy/pull/14277)) +* Micro-optimization: avoid Bogus\[int\] types that cause needless boxing (Jukka Lehtosalo, PR [14354](https://github.com/python/mypy/pull/14354)) +* Avoid slow error message logic if errors not shown to user (Jukka Lehtosalo, PR [14336](https://github.com/python/mypy/pull/14336)) +* Speed up the implementation of hasattr() checks (Jukka Lehtosalo, PR [14333](https://github.com/python/mypy/pull/14333)) +* Avoid the use of a context manager in hot code path (Jukka Lehtosalo, PR [14331](https://github.com/python/mypy/pull/14331)) +* Change various type queries into faster bool type queries (Jukka Lehtosalo, PR [14330](https://github.com/python/mypy/pull/14330)) +* Speed up recursive type check (Jukka Lehtosalo, PR [14326](https://github.com/python/mypy/pull/14326)) +* Optimize subtype checking by avoiding a nested function (Jukka Lehtosalo, PR [14325](https://github.com/python/mypy/pull/14325)) +* Optimize type parameter checks in subtype checking (Jukka Lehtosalo, PR [14324](https://github.com/python/mypy/pull/14324)) +* Speed up freshening type variables (Jukka Lehtosalo, PR [14323](https://github.com/python/mypy/pull/14323)) +* Optimize implementation of TypedDict types for \*\*kwds (Jukka Lehtosalo, PR [14316](https://github.com/python/mypy/pull/14316)) + +#### Warn About Variables Used Before Definition + +Mypy will now generate an error if you use a variable before itā€™s defined. This feature is enabled by default. By default mypy reports an error when it infers that a variable is always undefined. +```python +y = x # E: Name "x" is used before definition [used-before-def] +x = 0 +``` +This feature was contributed by Stas Ilinskiy. + +#### Detect Possibly Undefined Variables (Experimental) + +A new experimental possibly-undefined error code is now available that will detect variables that may be undefined: +```python + if b: + x = 0 + print(x) # Error: Name "x" may be undefined [possibly-undefined] +``` +The error code is disabled be default, since it can generate false positives. + +This feature was contributed by Stas Ilinskiy. + +#### Support the ā€œSelfā€ Type + +There is now a simpler syntax for declaring [generic self types](https://mypy.readthedocs.io/en/stable/generics.html#generic-methods-and-generic-self) introduced in [PEP 673](https://peps.python.org/pep-0673/): the Self type. You no longer have to define a type variable to use ā€œself typesā€, and you can use them with attributes. Example from mypy documentation: +```python +from typing import Self + +class Friend: + other: Self | None = None + + @classmethod + def make_pair(cls) -> tuple[Self, Self]: + a, b = cls(), cls() + a.other = b + b.other = a + return a, b + +class SuperFriend(Friend): + pass + +# a and b have the inferred type "SuperFriend", not "Friend" +a, b = SuperFriend.make_pair() +``` +The feature was introduced in Python 3.11. In earlier Python versions a backport of Self is available in `typing_extensions`. + +This was contributed by Ivan Levkivskyi (PR [14041](https://github.com/python/mypy/pull/14041)). + +#### Support ParamSpec in Type Aliases + +ParamSpec and Concatenate can now be used in type aliases. Example: +```python +from typing import ParamSpec, Callable + +P = ParamSpec("P") +A = Callable[P, None] + +def f(c: A[int, str]) -> None: + c(1, "x") +``` +This feature was contributed by Ivan Levkivskyi (PR [14159](https://github.com/python/mypy/pull/14159)). + +#### ParamSpec and Generic Self Types No Longer Experimental + +Support for ParamSpec ([PEP 612](https://www.python.org/dev/peps/pep-0612/)) and generic self types are no longer considered experimental. + +#### Miscellaneous New Features + +* Minimal, partial implementation of dataclass\_transform ([PEP 681](https://peps.python.org/pep-0681/)) (Wesley Collin Wright, PR [14523](https://github.com/python/mypy/pull/14523)) +* Add basic support for `typing_extensions`.TypeVar (Marc Mueller, PR [14313](https://github.com/python/mypy/pull/14313)) +* Add \--debug-serialize option (Marc Mueller, PR [14155](https://github.com/python/mypy/pull/14155)) +* Constant fold initializers of final variables (Jukka Lehtosalo, PR [14283](https://github.com/python/mypy/pull/14283)) +* Enable Final instance attributes for attrs (Tin Tvrtković, PR [14232](https://github.com/python/mypy/pull/14232)) +* Allow function arguments as base classes (Ivan Levkivskyi, PR [14135](https://github.com/python/mypy/pull/14135)) +* Allow super() with mixin protocols (Ivan Levkivskyi, PR [14082](https://github.com/python/mypy/pull/14082)) +* Add type inference for dict.keys membership (Matthew Hughes, PR [13372](https://github.com/python/mypy/pull/13372)) +* Generate error for class attribute access if attribute is defined with `__slots__` (Harrison McCarty, PR [14125](https://github.com/python/mypy/pull/14125)) +* Support additional attributes in callback protocols (Ivan Levkivskyi, PR [14084](https://github.com/python/mypy/pull/14084)) + +#### Fixes to Crashes + +* Fix crash on prefixed ParamSpec with forward reference (Ivan Levkivskyi, PR [14569](https://github.com/python/mypy/pull/14569)) +* Fix internal crash when resolving the same partial type twice (Shantanu, PR [14552](https://github.com/python/mypy/pull/14552)) +* Fix crash in daemon mode on new import cycle (Ivan Levkivskyi, PR [14508](https://github.com/python/mypy/pull/14508)) +* Fix crash in mypy daemon (Ivan Levkivskyi, PR [14497](https://github.com/python/mypy/pull/14497)) +* Fix crash on Any metaclass in incremental mode (Ivan Levkivskyi, PR [14495](https://github.com/python/mypy/pull/14495)) +* Fix crash in await inside comprehension outside function (Ivan Levkivskyi, PR [14486](https://github.com/python/mypy/pull/14486)) +* Fix crash in Self type on forward reference in upper bound (Ivan Levkivskyi, PR [14206](https://github.com/python/mypy/pull/14206)) +* Fix a crash when incorrect super() is used outside a method (Ivan Levkivskyi, PR [14208](https://github.com/python/mypy/pull/14208)) +* Fix crash on overriding with frozen attrs (Ivan Levkivskyi, PR [14186](https://github.com/python/mypy/pull/14186)) +* Fix incremental mode crash on generic function appearing in nested position (Ivan Levkivskyi, PR [14148](https://github.com/python/mypy/pull/14148)) +* Fix daemon crash on malformed NamedTuple (Ivan Levkivskyi, PR [14119](https://github.com/python/mypy/pull/14119)) +* Fix crash during ParamSpec inference (Ivan Levkivskyi, PR [14118](https://github.com/python/mypy/pull/14118)) +* Fix crash on nested generic callable (Ivan Levkivskyi, PR [14093](https://github.com/python/mypy/pull/14093)) +* Fix crashes with unpacking SyntaxError (Shantanu, PR [11499](https://github.com/python/mypy/pull/11499)) +* Fix crash on partial type inference within a lambda (Ivan Levkivskyi, PR [14087](https://github.com/python/mypy/pull/14087)) +* Fix crash with enums (Michael Lee, PR [14021](https://github.com/python/mypy/pull/14021)) +* Fix crash with malformed TypedDicts and disllow-any-expr (Michael Lee, PR [13963](https://github.com/python/mypy/pull/13963)) + +#### Error Reporting Improvements + +* More helpful error for missing self (Shantanu, PR [14386](https://github.com/python/mypy/pull/14386)) +* Add error-code truthy-iterable (Marc Mueller, PR [13762](https://github.com/python/mypy/pull/13762)) +* Fix pluralization in error messages (KotlinIsland, PR [14411](https://github.com/python/mypy/pull/14411)) + +#### Mypyc: Support Match Statement + +Mypyc can now compile Python 3.10 match statements. + +This was contributed by dosisod (PR [13953](https://github.com/python/mypy/pull/13953)). + +#### Other Mypyc Fixes and Improvements + +* Optimize int(x)/float(x)/complex(x) on instances of native classes (Richard Si, PR [14450](https://github.com/python/mypy/pull/14450)) +* Always emit warnings (Richard Si, PR [14451](https://github.com/python/mypy/pull/14451)) +* Faster bool and integer conversions (Jukka Lehtosalo, PR [14422](https://github.com/python/mypy/pull/14422)) +* Support attributes that override properties (Jukka Lehtosalo, PR [14377](https://github.com/python/mypy/pull/14377)) +* Precompute set literals for "in" operations and iteration (Richard Si, PR [14409](https://github.com/python/mypy/pull/14409)) +* Don't load targets with forward references while setting up non-extension class `__all__` (Richard Si, PR [14401](https://github.com/python/mypy/pull/14401)) +* Compile away NewType type calls (Richard Si, PR [14398](https://github.com/python/mypy/pull/14398)) +* Improve error message for multiple inheritance (Joshua Bronson, PR [14344](https://github.com/python/mypy/pull/14344)) +* Simplify union types (Jukka Lehtosalo, PR [14363](https://github.com/python/mypy/pull/14363)) +* Fixes to union simplification (Jukka Lehtosalo, PR [14364](https://github.com/python/mypy/pull/14364)) +* Fix for typeshed changes to Collection (Shantanu, PR [13994](https://github.com/python/mypy/pull/13994)) +* Allow use of enum.Enum (Shantanu, PR [13995](https://github.com/python/mypy/pull/13995)) +* Fix compiling on Arch Linux (dosisod, PR [13978](https://github.com/python/mypy/pull/13978)) + +#### Documentation Improvements + +* Various documentation and error message tweaks (Jukka Lehtosalo, PR [14574](https://github.com/python/mypy/pull/14574)) +* Improve Generics documentation (Shantanu, PR [14587](https://github.com/python/mypy/pull/14587)) +* Improve protocols documentation (Shantanu, PR [14577](https://github.com/python/mypy/pull/14577)) +* Improve dynamic typing documentation (Shantanu, PR [14576](https://github.com/python/mypy/pull/14576)) +* Improve the Common Issues page (Shantanu, PR [14581](https://github.com/python/mypy/pull/14581)) +* Add a top-level TypedDict page (Shantanu, PR [14584](https://github.com/python/mypy/pull/14584)) +* More improvements to getting started documentation (Shantanu, PR [14572](https://github.com/python/mypy/pull/14572)) +* Move truthy-function documentation from ā€œoptional checksā€ to ā€œenabled by defaultā€ (Anders Kaseorg, PR [14380](https://github.com/python/mypy/pull/14380)) +* Avoid use of implicit optional in decorator factory documentation (Tom Schraitle, PR [14156](https://github.com/python/mypy/pull/14156)) +* Clarify documentation surrounding install-types (Shantanu, PR [14003](https://github.com/python/mypy/pull/14003)) +* Improve searchability for module level type ignore errors (Shantanu, PR [14342](https://github.com/python/mypy/pull/14342)) +* Advertise mypy daemon in README (Ivan Levkivskyi, PR [14248](https://github.com/python/mypy/pull/14248)) +* Add link to error codes in README (Ivan Levkivskyi, PR [14249](https://github.com/python/mypy/pull/14249)) +* Document that report generation disables cache (Ilya Konstantinov, PR [14402](https://github.com/python/mypy/pull/14402)) +* Stop saying mypy is beta software (Ivan Levkivskyi, PR [14251](https://github.com/python/mypy/pull/14251)) +* Flycheck-mypy is deprecated, since its functionality was merged to Flycheck (Ivan Levkivskyi, PR [14247](https://github.com/python/mypy/pull/14247)) +* Update code example in "Declaring decorators" (ChristianWitzler, PR [14131](https://github.com/python/mypy/pull/14131)) + +#### Stubtest Improvements + +Stubtest is a tool for testing that stubs conform to the implementations. + +* Improve error message for `__all__`\-related errors (Alex Waygood, PR [14362](https://github.com/python/mypy/pull/14362)) +* Improve heuristics for determining whether global-namespace names are imported (Alex Waygood, PR [14270](https://github.com/python/mypy/pull/14270)) +* Catch BaseException on module imports (Shantanu, PR [14284](https://github.com/python/mypy/pull/14284)) +* Associate exported symbol error with `__all__` object\_path (Nikita Sobolev, PR [14217](https://github.com/python/mypy/pull/14217)) +* Add \_\_warningregistry\_\_ to the list of ignored module dunders (Nikita Sobolev, PR [14218](https://github.com/python/mypy/pull/14218)) +* If a default is present in the stub, check that it is correct (Jelle Zijlstra, PR [14085](https://github.com/python/mypy/pull/14085)) + +#### Stubgen Improvements + +Stubgen is a tool for automatically generating draft stubs for libraries. + +* Treat dlls as C modules (Shantanu, PR [14503](https://github.com/python/mypy/pull/14503)) + +#### Other Notable Fixes and Improvements + +* Update stub suggestions based on recent typeshed changes (Alex Waygood, PR [14265](https://github.com/python/mypy/pull/14265)) +* Fix attrs protocol check with cache (Marc Mueller, PR [14558](https://github.com/python/mypy/pull/14558)) +* Fix strict equality check if operand item type has custom \_\_eq\_\_ (Jukka Lehtosalo, PR [14513](https://github.com/python/mypy/pull/14513)) +* Don't consider object always truthy (Jukka Lehtosalo, PR [14510](https://github.com/python/mypy/pull/14510)) +* Properly support union of TypedDicts as dict literal context (Ivan Levkivskyi, PR [14505](https://github.com/python/mypy/pull/14505)) +* Properly expand type in generic class with Self and TypeVar with values (Ivan Levkivskyi, PR [14491](https://github.com/python/mypy/pull/14491)) +* Fix recursive TypedDicts/NamedTuples defined with call syntax (Ivan Levkivskyi, PR [14488](https://github.com/python/mypy/pull/14488)) +* Fix type inference issue when a class inherits from Any (Shantanu, PR [14404](https://github.com/python/mypy/pull/14404)) +* Fix false positive on generic base class with six (Ivan Levkivskyi, PR [14478](https://github.com/python/mypy/pull/14478)) +* Don't read scripts without extensions as modules in namespace mode (Tim Geypens, PR [14335](https://github.com/python/mypy/pull/14335)) +* Fix inference for constrained type variables within unions (Christoph Tyralla, PR [14396](https://github.com/python/mypy/pull/14396)) +* Fix Unpack imported from typing (Marc Mueller, PR [14378](https://github.com/python/mypy/pull/14378)) +* Allow trailing commas in ini configuration of multiline values (Nikita Sobolev, PR [14240](https://github.com/python/mypy/pull/14240)) +* Fix false negatives involving Unions and generators or coroutines (Shantanu, PR [14224](https://github.com/python/mypy/pull/14224)) +* Fix ParamSpec constraint for types as callable (Vincent Vanlaer, PR [14153](https://github.com/python/mypy/pull/14153)) +* Fix type aliases with fixed-length tuples (Jukka Lehtosalo, PR [14184](https://github.com/python/mypy/pull/14184)) +* Fix issues with type aliases and new style unions (Jukka Lehtosalo, PR [14181](https://github.com/python/mypy/pull/14181)) +* Simplify unions less aggressively (Ivan Levkivskyi, PR [14178](https://github.com/python/mypy/pull/14178)) +* Simplify callable overlap logic (Ivan Levkivskyi, PR [14174](https://github.com/python/mypy/pull/14174)) +* Try empty context when assigning to union typed variables (Ivan Levkivskyi, PR [14151](https://github.com/python/mypy/pull/14151)) +* Improvements to recursive types (Ivan Levkivskyi, PR [14147](https://github.com/python/mypy/pull/14147)) +* Make non-numeric non-empty FORCE\_COLOR truthy (Shantanu, PR [14140](https://github.com/python/mypy/pull/14140)) +* Fix to recursive type aliases (Ivan Levkivskyi, PR [14136](https://github.com/python/mypy/pull/14136)) +* Correctly handle Enum name on Python 3.11 (Ivan Levkivskyi, PR [14133](https://github.com/python/mypy/pull/14133)) +* Fix class objects falling back to metaclass for callback protocol (Ivan Levkivskyi, PR [14121](https://github.com/python/mypy/pull/14121)) +* Correctly support self types in callable ClassVar (Ivan Levkivskyi, PR [14115](https://github.com/python/mypy/pull/14115)) +* Fix type variable clash in nested positions and in attributes (Ivan Levkivskyi, PR [14095](https://github.com/python/mypy/pull/14095)) +* Allow class variable as implementation for read only attribute (Ivan Levkivskyi, PR [14081](https://github.com/python/mypy/pull/14081)) +* Prevent warnings from causing dmypy to fail (Andrzej Bartosiński, PR [14102](https://github.com/python/mypy/pull/14102)) +* Correctly process nested definitions in mypy daemon (Ivan Levkivskyi, PR [14104](https://github.com/python/mypy/pull/14104)) +* Don't consider a branch unreachable if there is a possible promotion (Ivan Levkivskyi, PR [14077](https://github.com/python/mypy/pull/14077)) +* Fix incompatible overrides of overloaded methods in concrete subclasses (Shantanu, PR [14017](https://github.com/python/mypy/pull/14017)) +* Fix new style union syntax in type aliases (Jukka Lehtosalo, PR [14008](https://github.com/python/mypy/pull/14008)) +* Fix and optimise overload compatibility checking (Shantanu, PR [14018](https://github.com/python/mypy/pull/14018)) +* Improve handling of redefinitions through imports (Shantanu, PR [13969](https://github.com/python/mypy/pull/13969)) +* Preserve (some) implicitly exported types (Shantanu, PR [13967](https://github.com/python/mypy/pull/13967)) + +#### Typeshed Updates + +Typeshed is now modular and distributed as separate PyPI packages for everything except the standard library stubs. Please see [git log](https://github.com/python/typeshed/commits/main?after=ea0ae2155e8a04c9837903c3aff8dd5ad5f36ebc+0&branch=main&path=stdlib) for full list of typeshed changes. + +#### Acknowledgements + +Thanks to all mypy contributors who contributed to this release: + +* Alessio Izzo +* Alex Waygood +* Anders Kaseorg +* Andrzej Bartosiński +* Avasam +* ChristianWitzler +* Christoph Tyralla +* dosisod +* Harrison McCarty +* Hugo van Kemenade +* Hugues +* Ilya Konstantinov +* Ivan Levkivskyi +* Jelle Zijlstra +* jhance +* johnthagen +* Jonathan Daniel +* Joshua Bronson +* Jukka Lehtosalo +* KotlinIsland +* Lakshay Bisht +* Lefteris Karapetsas +* Marc Mueller +* Matthew Hughes +* Michael Lee +* Nick Drozd +* Nikita Sobolev +* Richard Si +* Shantanu +* Stas Ilinskiy +* Tim Geypens +* Tin Tvrtković +* Tom Schraitle +* Valentin Stanciu +* Vincent Vanlaer + +Weā€™d also like to thank our employer, Dropbox, for funding the mypy core team. + +Posted by Stas Ilinskiy + +## Previous releases + +For information about previous releases, refer to the posts at https://mypy-lang.blogspot.com/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b2e6cdb9734..a5d339330a75 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,32 +12,48 @@ issue tracker, pull requests, and chat, is expected to treat other people with respect and more generally to follow the guidelines articulated in the [Python Community Code of Conduct](https://www.python.org/psf/codeofconduct/). - ## Getting started with development ### Setup -#### (1) Clone the mypy repository and enter into it -``` -git clone https://github.com/python/mypy.git +#### (1) Fork the mypy repository + +Within Github, navigate to and fork the repository. + +#### (2) Clone the mypy repository and enter into it + +```bash +git clone git@github.com:/mypy.git cd mypy ``` -#### (2) Create then activate a virtual environment -``` -# On Windows, the commands may be slightly different. For more details, see -# https://docs.python.org/3/library/venv.html#creating-virtual-environments +#### (3) Create then activate a virtual environment + +```bash python3 -m venv venv source venv/bin/activate ``` -#### (3) Install the test requirements and the project +```bash +# For Windows use +python -m venv venv +. venv/Scripts/activate + +# For more details, see https://docs.python.org/3/library/venv.html#creating-virtual-environments ``` -python3 -m pip install -r test-requirements.txt -python3 -m pip install -e . + +#### (4) Install the test requirements and the project + +```bash +python -m pip install -r test-requirements.txt +python -m pip install -e . hash -r # This resets shell PATH cache, not necessary on Windows ``` +> **Note** +> You'll need Python 3.8 or higher to install all requirements listed in +> test-requirements.txt + ### Running tests Running the full test suite can take a while, and usually isn't necessary when @@ -47,22 +63,13 @@ your PR. However, if you wish to do so, you can run the full test suite like this: -``` -python3 runtests.py -``` - -You can also use `tox` to run tests (`tox` handles setting up the test environment for you): -``` -tox run -e py - -# Or some specific python version: -tox run -e py39 -# Or some specific command: -tox run -e lint +```bash +python3 runtests.py ``` Some useful commands for running specific tests include: + ```bash # Use mypy to check mypy's own code python3 runtests.py self @@ -75,21 +82,49 @@ pytest -n0 -k 'test_name' # Run all test cases in the "test-data/unit/check-dataclasses.test" file pytest mypy/test/testcheck.py::TypeCheckSuite::check-dataclasses.test -# Run the linter -flake8 - -# Run formatters -black . && isort . +# Run the formatters and linters +python runtests.py lint ``` For an in-depth guide on running and writing tests, see [the README in the test-data directory](test-data/unit/README.md). +#### Using `tox` + +You can also use [`tox`](https://tox.wiki/en/latest/) to run tests and other commands. +`tox` handles setting up test environments for you. + +```bash +# Run tests +tox run -e py + +# Run tests using some specific Python version +tox run -e py311 + +# Run a specific command +tox run -e lint + +# Run a single test from the test suite +tox run -e py -- -n0 -k 'test_name' + +# Run all test cases in the "test-data/unit/check-dataclasses.test" file using +# Python 3.11 specifically +tox run -e py311 -- mypy/test/testcheck.py::TypeCheckSuite::check-dataclasses.test + +# Set up a development environment with all the project libraries and run a command +tox -e dev -- mypy --verbose test_case.py +tox -e dev --override testenv:dev.allowlist_externals+=env -- env # inspect the environment +``` + +If you don't already have `tox` installed, you can use a virtual environment as +described above to install `tox` via `pip` (e.g., ``python3 -m pip install tox``). + ## First time contributors If you're looking for things to help with, browse our [issue tracker](https://github.com/python/mypy/issues)! In particular, look for: + - [good first issues](https://github.com/python/mypy/labels/good-first-issue) - [good second issues](https://github.com/python/mypy/labels/good-second-issue) - [documentation issues](https://github.com/python/mypy/labels/documentation) @@ -140,10 +175,6 @@ advice about good pull requests for open-source projects applies; we have [our own writeup](https://github.com/python/mypy/wiki/Good-Pull-Request) of this advice. -We are using `black` and `isort` to enforce a consistent coding style. -Run `black . && isort .` before your commits, otherwise you would receive -a CI failure. - Also, do not squash your commits after you have submitted a pull request, as this erases context during review. We will squash commits when the pull request is merged. @@ -151,28 +182,27 @@ You may also find other pages in the [Mypy developer guide](https://github.com/python/mypy/wiki/Developer-Guides) helpful in developing your change. - ## Core developer guidelines Core developers should follow these rules when processing pull requests: -* Always wait for tests to pass before merging PRs. -* Use "[Squash and merge](https://github.com/blog/2141-squash-your-commits)" +- Always wait for tests to pass before merging PRs. +- Use "[Squash and merge](https://github.com/blog/2141-squash-your-commits)" to merge PRs. -* Delete branches for merged PRs (by core devs pushing to the main repo). -* Edit the final commit message before merging to conform to the following +- Delete branches for merged PRs (by core devs pushing to the main repo). +- Edit the final commit message before merging to conform to the following style (we wish to have a clean `git log` output): - * When merging a multi-commit PR make sure that the commit message doesn't + - When merging a multi-commit PR make sure that the commit message doesn't contain the local history from the committer and the review history from the PR. Edit the message to only describe the end state of the PR. - * Make sure there is a *single* newline at the end of the commit message. + - Make sure there is a *single* newline at the end of the commit message. This way there is a single empty line between commits in `git log` output. - * Split lines as needed so that the maximum line length of the commit + - Split lines as needed so that the maximum line length of the commit message is under 80 characters, including the subject line. - * Capitalize the subject and each paragraph. - * Make sure that the subject of the commit message has no trailing dot. - * Use the imperative mood in the subject line (e.g. "Fix typo in README"). - * If the PR fixes an issue, make sure something like "Fixes #xxx." occurs + - Capitalize the subject and each paragraph. + - Make sure that the subject of the commit message has no trailing dot. + - Use the imperative mood in the subject line (e.g. "Fix typo in README"). + - If the PR fixes an issue, make sure something like "Fixes #xxx." occurs in the body of the message (not in the subject). - * Use Markdown for formatting. + - Use Markdown for formatting. diff --git a/LICENSE b/LICENSE index 991496cb4878..55d01ee19ad8 100644 --- a/LICENSE +++ b/LICENSE @@ -4,8 +4,8 @@ Mypy (and mypyc) are licensed under the terms of the MIT license, reproduced bel The MIT License -Copyright (c) 2012-2022 Jukka Lehtosalo and contributors -Copyright (c) 2015-2022 Dropbox, Inc. +Copyright (c) 2012-2023 Jukka Lehtosalo and contributors +Copyright (c) 2015-2023 Dropbox, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/MANIFEST.in b/MANIFEST.in index 1c26ae16fc78..c18b83cc0088 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -31,17 +31,18 @@ graft mypyc/doc # files necessary for testing sdist include mypy-requirements.txt include build-requirements.txt +include test-requirements.in include test-requirements.txt include mypy_self_check.ini prune misc -include misc/proper_plugin.py graft test-data include conftest.py include runtests.py include pytest.ini +include tox.ini include LICENSE mypyc/README.md -exclude .gitmodules CONTRIBUTING.md CREDITS ROADMAP.md tox.ini action.yml .editorconfig +exclude .gitmodules CONTRIBUTING.md CREDITS ROADMAP.md CHANGELOG.md action.yml .editorconfig exclude .git-blame-ignore-revs .pre-commit-config.yaml global-exclude *.py[cod] diff --git a/README.md b/README.md index 6c9f01968f92..07c170d46cb3 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Mypy: Static Typing for Python [![Chat at https://gitter.im/python/typing](https://badges.gitter.im/python/typing.svg)](https://gitter.im/python/typing?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) -[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/) +[![Linting: Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) Got a question? --------------- @@ -40,9 +40,9 @@ To report a bug or request an enhancement: tracker for that library To discuss a new type system feature: -- discuss at [typing-sig mailing list](https://mail.python.org/archives/list/typing-sig@python.org/) -- there is also some historical discussion [here](https://github.com/python/typing/issues) +- discuss at [discuss.python.org](https://discuss.python.org/c/typing/32) +- there is also some historical discussion at the [typing-sig mailing list](https://mail.python.org/archives/list/typing-sig@python.org/) and the [python/typing repo](https://github.com/python/typing/issues) What is mypy? ------------- @@ -82,6 +82,7 @@ See [the documentation](https://mypy.readthedocs.io/en/stable/index.html) for more examples and information. In particular, see: + - [type hints cheat sheet](https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html) - [getting started](https://mypy.readthedocs.io/en/stable/getting_started.html) - [list of error codes](https://mypy.readthedocs.io/en/stable/error_code_list.html) @@ -91,67 +92,75 @@ Quick start Mypy can be installed using pip: - python3 -m pip install -U mypy +```bash +python3 -m pip install -U mypy +``` If you want to run the latest version of the code, you can install from the repo directly: - python3 -m pip install -U git+https://github.com/python/mypy.git - # or if you don't have 'git' installed - python3 -m pip install -U https://github.com/python/mypy/zipball/master +```bash +python3 -m pip install -U git+https://github.com/python/mypy.git +# or if you don't have 'git' installed +python3 -m pip install -U https://github.com/python/mypy/zipball/master +``` Now you can type-check the [statically typed parts] of a program like this: - mypy PROGRAM +```bash +mypy PROGRAM +``` You can always use the Python interpreter to run your statically typed programs, even if mypy reports type errors: - python3 PROGRAM +```bash +python3 PROGRAM +``` You can also try mypy in an [online playground](https://mypy-play.net/) (developed by Yusuke Miyazaki). If you are working with large code bases, you can run mypy in [daemon mode], that will give much faster (often sub-second) incremental updates: - dmypy run -- PROGRAM +```bash +dmypy run -- PROGRAM +``` [statically typed parts]: https://mypy.readthedocs.io/en/latest/getting_started.html#function-signatures-and-dynamic-vs-static-typing [daemon mode]: https://mypy.readthedocs.io/en/stable/mypy_daemon.html - Integrations ------------ Mypy can be integrated into popular IDEs: -* Vim: - * Using [Syntastic](https://github.com/vim-syntastic/syntastic): in `~/.vimrc` add +- Vim: + - Using [Syntastic](https://github.com/vim-syntastic/syntastic): in `~/.vimrc` add `let g:syntastic_python_checkers=['mypy']` - * Using [ALE](https://github.com/dense-analysis/ale): should be enabled by default when `mypy` is installed, + - Using [ALE](https://github.com/dense-analysis/ale): should be enabled by default when `mypy` is installed, or can be explicitly enabled by adding `let b:ale_linters = ['mypy']` in `~/vim/ftplugin/python.vim` -* Emacs: using [Flycheck](https://github.com/flycheck/) -* Sublime Text: [SublimeLinter-contrib-mypy](https://github.com/fredcallaway/SublimeLinter-contrib-mypy) -* Atom: [linter-mypy](https://atom.io/packages/linter-mypy) -* PyCharm: [mypy plugin](https://github.com/dropbox/mypy-PyCharm-plugin) (PyCharm integrates +- Emacs: using [Flycheck](https://github.com/flycheck/) +- Sublime Text: [SublimeLinter-contrib-mypy](https://github.com/fredcallaway/SublimeLinter-contrib-mypy) +- Atom: [linter-mypy](https://atom.io/packages/linter-mypy) +- PyCharm: [mypy plugin](https://github.com/dropbox/mypy-PyCharm-plugin) (PyCharm integrates [its own implementation](https://www.jetbrains.com/help/pycharm/type-hinting-in-product.html) of [PEP 484](https://peps.python.org/pep-0484/)) -* VS Code: provides [basic integration](https://code.visualstudio.com/docs/python/linting#_mypy) with mypy. -* pre-commit: use [pre-commit mirrors-mypy](https://github.com/pre-commit/mirrors-mypy). +- VS Code: provides [basic integration](https://code.visualstudio.com/docs/python/linting#_mypy) with mypy. +- pre-commit: use [pre-commit mirrors-mypy](https://github.com/pre-commit/mirrors-mypy). Web site and documentation -------------------------- Additional information is available at the web site: - http://www.mypy-lang.org/ + Jump straight to the documentation: - https://mypy.readthedocs.io/ + Follow along our changelog at: - https://mypy-lang.blogspot.com/ - + Contributing ------------ @@ -164,7 +173,6 @@ To get started with developing mypy, see [CONTRIBUTING.md](CONTRIBUTING.md). If you need help getting started, don't hesitate to ask on [gitter](https://gitter.im/python/typing). - Mypyc and compiled version of mypy ---------------------------------- @@ -174,10 +182,12 @@ mypy approximately 4 times faster than if interpreted! To install an interpreted mypy instead, use: - python3 -m pip install --no-binary mypy -U mypy +```bash +python3 -m pip install --no-binary mypy -U mypy +``` To use a compiled version of a development version of mypy, directly install a binary from -https://github.com/mypyc/mypy_mypyc-wheels/releases/latest. +. -To contribute to the mypyc project, check out https://github.com/mypyc/mypyc +To contribute to the mypyc project, check out diff --git a/build-requirements.txt b/build-requirements.txt index 52c518d53bc2..aac1b95eddf7 100644 --- a/build-requirements.txt +++ b/build-requirements.txt @@ -2,4 +2,3 @@ -r mypy-requirements.txt types-psutil types-setuptools -types-typed-ast>=1.5.8,<1.6.0 diff --git a/conftest.py b/conftest.py index 0bd7b6a38031..4454b02e7f3a 100644 --- a/conftest.py +++ b/conftest.py @@ -12,7 +12,7 @@ def pytest_configure(config): # This function name is special to pytest. See -# http://doc.pytest.org/en/latest/writing_plugins.html#initialization-command-line-and-configuration-hooks +# https://doc.pytest.org/en/latest/how-to/writing_plugins.html#initialization-command-line-and-configuration-hooks def pytest_addoption(parser) -> None: parser.addoption( "--bench", action="store_true", default=False, help="Enable the benchmark test runs" diff --git a/docs/Makefile b/docs/Makefile index be69e9d88281..c87c4c1abcb2 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -9,7 +9,7 @@ BUILDDIR = build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from https://www.sphinx-doc.org/) endif # Internal variables. diff --git a/docs/README.md b/docs/README.md index 0d574c9213a5..e72164c78560 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,13 +15,13 @@ Install Sphinx and other dependencies (i.e. theme) needed for the documentation. From the `docs` directory, use `pip`: ``` -$ pip install -r requirements-docs.txt +pip install -r requirements-docs.txt ``` Build the documentation like this: ``` -$ make html +make html ``` The built documentation will be placed in the `docs/build` directory. Open @@ -33,13 +33,13 @@ Helpful documentation build commands Clean the documentation build: ``` -$ make clean +make clean ``` Test and check the links found in the documentation: ``` -$ make linkcheck +make linkcheck ``` Documentation on Read The Docs diff --git a/docs/make.bat b/docs/make.bat index 1e3d84320174..3664bed34b7e 100755 --- a/docs/make.bat +++ b/docs/make.bat @@ -56,7 +56,7 @@ if errorlevel 9009 ( echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ + echo.https://www.sphinx-doc.org/ exit /b 1 ) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 395964ad9d44..a3504b07824d 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,2 +1,2 @@ -sphinx>=4.2.0,<5.0.0 +sphinx>=5.1.0 furo>=2022.3.4 diff --git a/docs/source/additional_features.rst b/docs/source/additional_features.rst index ef5bf9e8936d..ae625c157654 100644 --- a/docs/source/additional_features.rst +++ b/docs/source/additional_features.rst @@ -9,10 +9,9 @@ of the previous sections. Dataclasses *********** -In Python 3.7, a new :py:mod:`dataclasses` module has been added to the standard library. -This module allows defining and customizing simple boilerplate-free classes. -They can be defined using the :py:func:`@dataclasses.dataclass -` decorator: +The :py:mod:`dataclasses` module allows defining and customizing simple +boilerplate-free classes. They can be defined using the +:py:func:`@dataclasses.dataclass ` decorator: .. code-block:: python @@ -72,12 +71,12 @@ and :pep:`557`. Caveats/Known Issues ==================== -Some functions in the :py:mod:`dataclasses` module, such as :py:func:`~dataclasses.replace` and :py:func:`~dataclasses.asdict`, +Some functions in the :py:mod:`dataclasses` module, such as :py:func:`~dataclasses.asdict`, have imprecise (too permissive) types. This will be fixed in future releases. Mypy does not yet recognize aliases of :py:func:`dataclasses.dataclass `, and will -probably never recognize dynamically computed decorators. The following examples -do **not** work: +probably never recognize dynamically computed decorators. The following example +does **not** work: .. code-block:: python @@ -95,16 +94,37 @@ do **not** work: """ attribute: int - @dataclass_wrapper - class DynamicallyDecorated: - """ - Mypy doesn't recognize this as a dataclass because it is decorated by a - function returning `dataclass` rather than by `dataclass` itself. - """ - attribute: int - AliasDecorated(attribute=1) # error: Unexpected keyword argument - DynamicallyDecorated(attribute=1) # error: Unexpected keyword argument + + +To have Mypy recognize a wrapper of :py:func:`dataclasses.dataclass ` +as a dataclass decorator, consider using the :py:func:`~typing.dataclass_transform` decorator: + +.. code-block:: python + + from dataclasses import dataclass, Field + from typing import TypeVar, dataclass_transform + + T = TypeVar('T') + + @dataclass_transform(field_specifiers=(Field,)) + def my_dataclass(cls: type[T]) -> type[T]: + ... + return dataclass(cls) + + +Data Class Transforms +********************* + +Mypy supports the :py:func:`~typing.dataclass_transform` decorator as described in +`PEP 681 `_. + +.. note:: + + Pragmatically, mypy will assume such classes have the internal attribute :code:`__dataclass_fields__` + (even though they might lack it in runtime) and will assume functions such as :py:func:`dataclasses.is_dataclass` + and :py:func:`dataclasses.fields` treat them as if they were dataclasses + (even though they may fail at runtime). .. _attrs_package: @@ -121,55 +141,54 @@ Type annotations can be added as follows: import attr - @attr.s + @attrs.define class A: - one: int = attr.ib() # Variable annotation (Python 3.6+) - two = attr.ib() # type: int # Type comment - three = attr.ib(type=int) # type= argument + one: int + two: int = 7 + three: int = attrs.field(8) -If you're using ``auto_attribs=True`` you must use variable annotations. +If you're using ``auto_attribs=False`` you must use ``attrs.field``: .. code-block:: python - import attr + import attrs - @attr.s(auto_attribs=True) + @attrs.define class A: - one: int - two: int = 7 - three: int = attr.ib(8) + one: int = attrs.field() # Variable annotation (Python 3.6+) + two = attrs.field() # type: int # Type comment + three = attrs.field(type=int) # type= argument Typeshed has a couple of "white lie" annotations to make type checking -easier. :py:func:`attr.ib` and :py:class:`attr.Factory` actually return objects, but the +easier. :py:func:`attrs.field` and :py:class:`attrs.Factory` actually return objects, but the annotation says these return the types that they expect to be assigned to. That enables this to work: .. code-block:: python - import attr - from typing import Dict + import attrs - @attr.s(auto_attribs=True) + @attrs.define class A: - one: int = attr.ib(8) - two: Dict[str, str] = attr.Factory(dict) - bad: str = attr.ib(16) # Error: can't assign int to str + one: int = attrs.field(8) + two: dict[str, str] = attrs.Factory(dict) + bad: str = attrs.field(16) # Error: can't assign int to str Caveats/Known Issues ==================== * The detection of attr classes and attributes works by function name only. This means that if you have your own helper functions that, for example, - ``return attr.ib()`` mypy will not see them. + ``return attrs.field()`` mypy will not see them. * All boolean arguments that mypy cares about must be literal ``True`` or ``False``. e.g the following will not work: .. code-block:: python - import attr + import attrs YES = True - @attr.s(init=YES) + @attrs.define(init=YES) class A: ... @@ -178,7 +197,7 @@ Caveats/Known Issues :py:meth:`__init__ ` will be replaced by ``Any``. * :ref:`Validator decorators ` - and `default decorators `_ + and `default decorators `_ are not type-checked against the attribute they are setting/validating. * Method definitions added by mypy currently overwrite any existing method diff --git a/docs/source/cheat_sheet_py3.rst b/docs/source/cheat_sheet_py3.rst index 5aa1770512b8..b8e43960fd09 100644 --- a/docs/source/cheat_sheet_py3.rst +++ b/docs/source/cheat_sheet_py3.rst @@ -21,7 +21,7 @@ See :ref:`type-inference-and-annotations` for more details. # You don't need to initialize a variable to annotate it a: int # Ok (no value at runtime until assigned) - # Doing so is useful in conditional branches + # Doing so can be useful in conditional branches child: bool if age < 18: child = True @@ -34,7 +34,7 @@ Useful built-in types .. code-block:: python - # For most types, just use the name of the type. + # For most types, just use the name of the type in the annotation # Note that mypy can usually infer the type of a variable from its value, # so technically these annotations are redundant x: int = 1 @@ -75,10 +75,11 @@ Useful built-in types # Use Optional[X] for a value that could be None # Optional[X] is the same as X | None or Union[X, None] x: Optional[str] = "something" if some_condition() else None - # Mypy understands a value can't be None in an if-statement if x is not None: + # Mypy understands x won't be None here because of the if-statement print(x.upper()) - # If a value can never be None due to some invariants, use an assert + # If you know a value can never be None due to some logic that mypy doesn't + # understand, use an assert assert x is not None print(x.upper()) @@ -102,10 +103,10 @@ Functions def show(value: str, excitement: int = 10) -> None: print(value + "!" * excitement) - # Note that arguments without a type are dynamically typed (treated as Any) - # and that functions without any annotations not checked - def untyped(x): - x.anything() + 1 + "string" # no errors + # Note that arguments without a type are dynamically typed (treated as Any) + # and that functions without any annotations are not checked + def untyped(x): + x.anything() + 1 + "string" # no errors # This is how you annotate a callable (function) value x: Callable[[int, float], float] = f @@ -132,7 +133,7 @@ Functions # Mypy understands positional-only and keyword-only arguments # Positional-only arguments can also be marked by using a name starting with # two underscores - def quux(x: int, / *, y: int) -> None: + def quux(x: int, /, *, y: int) -> None: pass quux(3, y=5) # Ok @@ -151,6 +152,8 @@ Classes .. code-block:: python + from typing import ClassVar + class BankAccount: # The "__init__" method doesn't return anything, so it gets return # type "None" just like any other method that doesn't return anything @@ -177,8 +180,6 @@ Classes class AuditedBankAccount(BankAccount): # You can optionally declare instance variables in the class body audit_log: list[str] - # This is an instance variable with a default value - auditor_name: str = "The Spanish Inquisition" def __init__(self, account_name: str, initial_balance: int = 0) -> None: super().__init__(account_name, initial_balance) @@ -210,6 +211,7 @@ Classes # This will allow access to any A.x, if x is compatible with the return type def __getattr__(self, name: str) -> int: ... + a = A() a.foo = 42 # Works a.bar = 'Ex-parrot' # Fails type checking @@ -259,6 +261,8 @@ When you're puzzled or when things are complicated In some cases type annotations can cause issues at runtime, see :ref:`runtime_troubles` for dealing with this. +See :ref:`silencing-type-errors` for details on how to silence errors. + Standard "duck types" ********************* @@ -294,37 +298,11 @@ that are common in idiomatic Python are standardized. f({3: 'yes', 4: 'no'}) - -You can even make your own duck types using :ref:`protocol-types`. - -Coroutines and asyncio -********************** - -See :ref:`async-and-await` for the full detail on typing coroutines and asynchronous code. - -.. code-block:: python - - import asyncio - - # A coroutine is typed like a normal function - async def countdown35(tag: str, count: int) -> str: - while count > 0: - print(f'T-minus {count} ({tag})') - await asyncio.sleep(0.1) - count -= 1 - return "Blastoff!" - - -Miscellaneous -************* - -.. code-block:: python - import sys from typing import IO - # Use IO[] for functions that should accept or return any - # object that comes from an open() call (IO[] does not + # Use IO[str] or IO[bytes] for functions that should accept or return + # objects that come from an open() call (note that IO does not # distinguish between reading, writing or other modes) def get_sys_IO(mode: str = 'w') -> IO[str]: if mode == 'w': @@ -334,19 +312,38 @@ Miscellaneous else: return sys.stdout - # Forward references are useful if you want to reference a class before - # it is defined + +You can even make your own duck types using :ref:`protocol-types`. + +Forward references +****************** + +.. code-block:: python + + # You may want to reference a class before it is defined. + # This is known as a "forward reference". def f(foo: A) -> int: # This will fail at runtime with 'A' is not defined ... - class A: + # However, if you add the following special import: + from __future__ import annotations + # It will work at runtime and type checking will succeed as long as there + # is a class of that name later on in the file + def f(foo: A) -> int: # Ok ... - # If you use the string literal 'A', it will pass as long as there is a - # class of that name later on in the file - def f(foo: 'A') -> int: # Ok + # Another option is to just put the type in quotes + def f(foo: 'A') -> int: # Also ok ... + class A: + # This can also come up if you need to reference a class in a type + # annotation inside the definition of that class + @classmethod + def create(cls) -> A: + ... + +See :ref:`forward-references` for more details. Decorators ********** @@ -365,3 +362,20 @@ Decorator functions can be expressed via generics. See def decorator_args(url: str) -> Callable[[F], F]: ... + +Coroutines and asyncio +********************** + +See :ref:`async-and-await` for the full detail on typing coroutines and asynchronous code. + +.. code-block:: python + + import asyncio + + # A coroutine is typed like a normal function + async def countdown(tag: str, count: int) -> str: + while count > 0: + print(f'T-minus {count} ({tag})') + await asyncio.sleep(0.1) + count -= 1 + return "Blastoff!" diff --git a/docs/source/class_basics.rst b/docs/source/class_basics.rst index 1d4164192318..1d80da5830ec 100644 --- a/docs/source/class_basics.rst +++ b/docs/source/class_basics.rst @@ -208,6 +208,38 @@ override has a compatible signature: subtype such as ``list[int]``. Similarly, you can vary argument types **contravariantly** -- subclasses can have more general argument types. +In order to ensure that your code remains correct when renaming methods, +it can be helpful to explicitly mark a method as overriding a base +method. This can be done with the ``@override`` decorator. ``@override`` +can be imported from ``typing`` starting with Python 3.12 or from +``typing_extensions`` for use with older Python versions. If the base +method is then renamed while the overriding method is not, mypy will +show an error: + +.. code-block:: python + + from typing import override + + class Base: + def f(self, x: int) -> None: + ... + def g_renamed(self, y: str) -> None: + ... + + class Derived1(Base): + @override + def f(self, x: int) -> None: # OK + ... + + @override + def g(self, y: str) -> None: # Error: no corresponding base method found + ... + +.. note:: + + Use :ref:`--enable-error-code explicit-override ` to require + that method overrides use the ``@override`` decorator. Emit an error if it is missing. + You can also override a statically typed method with a dynamically typed one. This allows dynamically typed code to override methods defined in library classes without worrying about their type @@ -231,7 +263,7 @@ effect at runtime: Abstract base classes and multiple inheritance ********************************************** -Mypy supports Python :doc:`abstract base classes ` (ABCs). Abstract classes +Mypy supports Python :doc:`abstract base classes ` (ABCs). Abstract classes have at least one abstract method or property that must be implemented by any *concrete* (non-abstract) subclass. You can define abstract base classes using the :py:class:`abc.ABCMeta` metaclass and the :py:func:`@abc.abstractmethod ` @@ -339,8 +371,7 @@ property or an instance variable. Slots ***** -When a class has explicitly defined -`__slots__ `_, +When a class has explicitly defined :std:term:`__slots__`, mypy will check that all attributes assigned to are members of ``__slots__``: .. code-block:: python diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index 31d23db204eb..4a7ead3e8724 100644 --- a/docs/source/command_line.rst +++ b/docs/source/command_line.rst @@ -133,7 +133,7 @@ imports. This flag tells mypy that top-level packages will be based in either the current directory, or a member of the ``MYPYPATH`` environment variable or - :confval:`mypy_path` config option. This option is only useful in + :confval:`mypy_path` config option. This option is only useful in the absence of `__init__.py`. See :ref:`Mapping file paths to modules ` for details. @@ -350,15 +350,48 @@ definitions or calls. This flag reports an error whenever a function with type annotations calls a function defined without annotations. +.. option:: --untyped-calls-exclude + + This flag allows to selectively disable :option:`--disallow-untyped-calls` + for functions and methods defined in specific packages, modules, or classes. + Note that each exclude entry acts as a prefix. For example (assuming there + are no type annotations for ``third_party_lib`` available): + + .. code-block:: python + + # mypy --disallow-untyped-calls + # --untyped-calls-exclude=third_party_lib.module_a + # --untyped-calls-exclude=foo.A + from third_party_lib.module_a import some_func + from third_party_lib.module_b import other_func + import foo + + some_func() # OK, function comes from module `third_party_lib.module_a` + other_func() # E: Call to untyped function "other_func" in typed context + + foo.A().meth() # OK, method was defined in class `foo.A` + foo.B().meth() # E: Call to untyped function "meth" in typed context + + # file foo.py + class A: + def meth(self): pass + class B: + def meth(self): pass + .. option:: --disallow-untyped-defs This flag reports an error whenever it encounters a function definition - without type annotations. + without type annotations or with incomplete type annotations. + (a superset of :option:`--disallow-incomplete-defs`). + + For example, it would report an error for :code:`def f(a, b)` and :code:`def f(a: int, b)`. .. option:: --disallow-incomplete-defs This flag reports an error whenever it encounters a partly annotated - function definition. + function definition, while still allowing entirely unannotated definitions. + + For example, it would report an error for :code:`def f(a: int, b)` but not :code:`def f(a, b)`. .. option:: --check-untyped-defs @@ -382,7 +415,6 @@ None and Optional handling ************************** The following flags adjust how mypy handles values of type ``None``. -For more details, see :ref:`no_strict_optional`. .. _implicit-optional: @@ -402,16 +434,19 @@ For more details, see :ref:`no_strict_optional`. **Note:** This was disabled by default starting in mypy 0.980. +.. _no_strict_optional: + .. option:: --no-strict-optional - This flag disables strict checking of :py:data:`~typing.Optional` + This flag effectively disables checking of :py:data:`~typing.Optional` types and ``None`` values. With this option, mypy doesn't - generally check the use of ``None`` values -- they are valid - everywhere. See :ref:`no_strict_optional` for more about this feature. + generally check the use of ``None`` values -- it is treated + as compatible with every type. - **Note:** Strict optional checking was enabled by default starting in - mypy 0.600, and in previous versions it had to be explicitly enabled - using ``--strict-optional`` (which is still accepted). + .. warning:: + + ``--no-strict-optional`` is evil. Avoid using it and definitely do + not use it without understanding what it does. .. _configuring-warnings: @@ -607,6 +642,34 @@ of the above sections. assert text is not None # OK, check against None is allowed as a special case. +.. option:: --extra-checks + + This flag enables additional checks that are technically correct but may be + impractical in real code. In particular, it prohibits partial overlap in + ``TypedDict`` updates, and makes arguments prepended via ``Concatenate`` + positional-only. For example: + + .. code-block:: python + + from typing import TypedDict + + class Foo(TypedDict): + a: int + + class Bar(TypedDict): + a: int + b: int + + def test(foo: Foo, bar: Bar) -> None: + # This is technically unsafe since foo can have a subtype of Foo at + # runtime, where type of key "b" is incompatible with int, see below + bar.update(foo) + + class Bad(Foo): + b: str + bad: Bad = {"a": 0, "b": "no"} + test(bad, bar) + .. option:: --strict This flag mode enables all optional error checking flags. You can see the @@ -726,7 +789,18 @@ in error messages. disable reporting most additional errors. The limit only applies if it seems likely that most of the remaining errors will not be useful or they may be overly noisy. If ``N`` is negative, there is - no limit. The default limit is 200. + no limit. The default limit is -1. + +.. option:: --force-uppercase-builtins + + Always use ``List`` instead of ``list`` in error messages, + even on Python 3.9+. + +.. option:: --force-union-syntax + + Always use ``Union[]`` and ``Optional[]`` for union types + in error messages (instead of the ``|`` operator), + even on Python 3.10+. .. _incremental: @@ -919,6 +993,58 @@ format into the specified directory. library or specify mypy installation with the setuptools extra ``mypy[reports]``. + +Enabling incomplete/experimental features +***************************************** + +.. option:: --enable-incomplete-feature {PreciseTupleTypes} + + Some features may require several mypy releases to implement, for example + due to their complexity, potential for backwards incompatibility, or + ambiguous semantics that would benefit from feedback from the community. + You can enable such features for early preview using this flag. Note that + it is not guaranteed that all features will be ultimately enabled by + default. In *rare cases* we may decide to not go ahead with certain + features. + +List of currently incomplete/experimental features: + +* ``PreciseTupleTypes``: this feature will infer more precise tuple types in + various scenarios. Before variadic types were added to the Python type system + by :pep:`646`, it was impossible to express a type like "a tuple with + at least two integers". The best type available was ``tuple[int, ...]``. + Therefore, mypy applied very lenient checking for variable-length tuples. + Now this type can be expressed as ``tuple[int, int, *tuple[int, ...]]``. + For such more precise types (when explicitly *defined* by a user) mypy, + for example, warns about unsafe index access, and generally handles them + in a type-safe manner. However, to avoid problems in existing code, mypy + does not *infer* these precise types when it technically can. Here are + notable examples where ``PreciseTupleTypes`` infers more precise types: + + .. code-block:: python + + numbers: tuple[int, ...] + + more_numbers = (1, *numbers, 1) + reveal_type(more_numbers) + # Without PreciseTupleTypes: tuple[int, ...] + # With PreciseTupleTypes: tuple[int, *tuple[int, ...], int] + + other_numbers = (1, 1) + numbers + reveal_type(other_numbers) + # Without PreciseTupleTypes: tuple[int, ...] + # With PreciseTupleTypes: tuple[int, int, *tuple[int, ...]] + + if len(numbers) > 2: + reveal_type(numbers) + # Without PreciseTupleTypes: tuple[int, ...] + # With PreciseTupleTypes: tuple[int, int, int, *tuple[int, ...]] + else: + reveal_type(numbers) + # Without PreciseTupleTypes: tuple[int, ...] + # With PreciseTupleTypes: tuple[()] | tuple[int] | tuple[int, int] + + Miscellaneous ************* diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index afb8e7d3ffe1..cfe82e19e77b 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -41,7 +41,7 @@ once you add annotations: def foo(a: str) -> str: return '(' + a.split() + ')' - # error: Unsupported operand types for + ("str" and List[str]) + # error: Unsupported operand types for + ("str" and "list[str]") If you don't know what types to add, you can use ``Any``, but beware: @@ -119,7 +119,7 @@ and mypy doesn't complain**. return None # No error! You may have disabled strict optional checking (see -:ref:`no_strict_optional` for more). +:ref:`--no-strict-optional ` for more). .. _silencing_checker: @@ -186,7 +186,7 @@ Ignoring a whole file * To only ignore errors, use a top-level ``# mypy: ignore-errors`` comment instead. * To only ignore errors with a specific error code, use a top-level - ``# mypy: disable-error-code=...`` comment. + ``# mypy: disable-error-code="..."`` comment. Example: ``# mypy: disable-error-code="truthy-bool, ignore-without-code"`` * To replace the contents of a module with ``Any``, use a per-module ``follow_imports = skip``. See :ref:`Following imports ` for details. @@ -226,7 +226,7 @@ dict to a new variable, as mentioned earlier: .. code-block:: python - a: List[int] = [] + a: list[int] = [] Without the annotation mypy can't always figure out the precise type of ``a``. @@ -238,7 +238,7 @@ modification operation in the same scope (such as ``append`` for a list): .. code-block:: python - a = [] # Okay because followed by append, inferred type List[int] + a = [] # Okay because followed by append, inferred type list[int] for i in range(n): a.append(i * i) @@ -276,7 +276,7 @@ not support ``sort()``) as a list and sort it in-place: def f(x: Sequence[int]) -> None: # Type of x is Sequence[int] here; we don't know the concrete type. x = list(x) - # Type of x is List[int] here. + # Type of x is list[int] here. x.sort() # Okay! See :ref:`type-narrowing` for more information. @@ -296,8 +296,8 @@ unexpected errors when combined with type inference. For example: class A: ... class B(A): ... - lst = [A(), A()] # Inferred type is List[A] - new_lst = [B(), B()] # inferred type is List[B] + lst = [A(), A()] # Inferred type is list[A] + new_lst = [B(), B()] # inferred type is list[B] lst = new_lst # mypy will complain about this, because List is invariant Possible strategies in such situations are: @@ -306,7 +306,7 @@ Possible strategies in such situations are: .. code-block:: python - new_lst: List[A] = [B(), B()] + new_lst: list[A] = [B(), B()] lst = new_lst # OK * Make a copy of the right hand side: @@ -319,7 +319,7 @@ Possible strategies in such situations are: .. code-block:: python - def f_bad(x: List[A]) -> A: + def f_bad(x: list[A]) -> A: return x[0] f_bad(new_lst) # Fails @@ -489,7 +489,7 @@ understand how mypy handles a particular piece of code. Example: .. code-block:: python - reveal_type((1, 'hello')) # Revealed type is "Tuple[builtins.int, builtins.str]" + reveal_type((1, 'hello')) # Revealed type is "tuple[builtins.int, builtins.str]" You can also use ``reveal_locals()`` at any line in a file to see the types of all local variables at once. Example: @@ -541,7 +541,7 @@ Consider this example: .. code-block:: python - from typing_extensions import Protocol + from typing import Protocol class P(Protocol): x: float @@ -561,7 +561,7 @@ the protocol definition: .. code-block:: python - from typing_extensions import Protocol + from typing import Protocol class P(Protocol): @property @@ -622,16 +622,16 @@ instructions at the `mypyc wheels repo 0.5: tp = A else: diff --git a/docs/source/conf.py b/docs/source/conf.py index 5faefdc92ed1..fa76734054ac 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -35,7 +35,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ["sphinx.ext.intersphinx"] +extensions = ["sphinx.ext.intersphinx", "docs.source.html_builder"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -266,9 +266,8 @@ intersphinx_mapping = { "python": ("https://docs.python.org/3", None), - "six": ("https://six.readthedocs.io", None), - "attrs": ("http://www.attrs.org/en/stable", None), - "cython": ("http://docs.cython.org/en/latest", None), + "attrs": ("https://www.attrs.org/en/stable/", None), + "cython": ("https://docs.cython.org/en/latest", None), "monkeytype": ("https://monkeytype.readthedocs.io/en/latest", None), "setuptools": ("https://setuptools.readthedocs.io/en/latest", None), } diff --git a/docs/source/config_file.rst b/docs/source/config_file.rst index 3b96e6bd7a5a..ac110cbed9f1 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -3,17 +3,26 @@ The mypy configuration file =========================== -Mypy supports reading configuration settings from a file. By default -it uses the file ``mypy.ini`` with a fallback to ``.mypy.ini``, then ``pyproject.toml``, -then ``setup.cfg`` in the current directory, then ``$XDG_CONFIG_HOME/mypy/config``, then -``~/.config/mypy/config``, and finally ``.mypy.ini`` in the user home directory -if none of them are found; the :option:`--config-file ` command-line flag can be used -to read a different file instead (see :ref:`config-file-flag`). +Mypy is very configurable. This is most useful when introducing typing to +an existing codebase. See :ref:`existing-code` for concrete advice for +that situation. + +Mypy supports reading configuration settings from a file with the following precedence order: + + 1. ``./mypy.ini`` + 2. ``./.mypy.ini`` + 3. ``./pyproject.toml`` + 4. ``./setup.cfg`` + 5. ``$XDG_CONFIG_HOME/mypy/config`` + 6. ``~/.config/mypy/config`` + 7. ``~/.mypy.ini`` It is important to understand that there is no merging of configuration -files, as it would lead to ambiguity. The :option:`--config-file ` flag -has the highest precedence and must be correct; otherwise mypy will report -an error and exit. Without command line option, mypy will look for configuration files in the above mentioned order. +files, as it would lead to ambiguity. The :option:`--config-file ` +command-line flag has the highest precedence and +must be correct; otherwise mypy will report an error and exit. Without the +command line option, mypy will look for configuration files in the +precedence order above. Most flags correspond closely to :ref:`command-line flags ` but there are some differences in flag names and some @@ -103,8 +112,8 @@ their name or by (when applicable) swapping their prefix from ``disallow`` to ``allow`` (and vice versa). -Examples -******** +Example ``mypy.ini`` +******************** Here is an example of a ``mypy.ini`` file. To use this config file, place it at the root of your repo and run mypy. @@ -233,10 +242,8 @@ section of the command line docs. Crafting a single regular expression that excludes multiple files while remaining human-readable can be a challenge. The above example demonstrates one approach. ``(?x)`` enables the ``VERBOSE`` flag for the subsequent regular expression, which - `ignores most whitespace and supports comments`__. The above is equivalent to: - ``(^one\.py$|two\.pyi$|^three\.)``. - - .. __: https://docs.python.org/3/library/re.html#re.X + :py:data:`ignores most whitespace and supports comments `. + The above is equivalent to: ``(^one\.py$|two\.pyi$|^three\.)``. For more details, see :option:`--exclude `. @@ -361,7 +368,7 @@ section of the command line docs. .. confval:: no_site_packages - :type: bool + :type: boolean :default: False Disables using type information in installed packages (see :pep:`561`). @@ -485,7 +492,38 @@ section of the command line docs. :default: False Disallows calling functions without type annotations from functions with type - annotations. + annotations. Note that when used in per-module options, it enables/disables + this check **inside** the module(s) specified, not for functions that come + from that module(s), for example config like this: + + .. code-block:: ini + + [mypy] + disallow_untyped_calls = True + + [mypy-some.library.*] + disallow_untyped_calls = False + + will disable this check inside ``some.library``, not for your code that + imports ``some.library``. If you want to selectively disable this check for + all your code that imports ``some.library`` you should instead use + :confval:`untyped_calls_exclude`, for example: + + .. code-block:: ini + + [mypy] + disallow_untyped_calls = True + untyped_calls_exclude = some.library + +.. confval:: untyped_calls_exclude + + :type: comma-separated list of strings + + Selectively excludes functions and methods defined in specific packages, + modules, and classes from action of :confval:`disallow_untyped_calls`. + This also applies to all submodules of packages (i.e. everything inside + a given prefix). Note, this option does not support per-file configuration, + the exclusions list is defined globally for all your code. .. confval:: disallow_untyped_defs @@ -493,14 +531,19 @@ section of the command line docs. :default: False Disallows defining functions without type annotations or with incomplete type - annotations. + annotations (a superset of :confval:`disallow_incomplete_defs`). + + For example, it would report an error for :code:`def f(a, b)` and :code:`def f(a: int, b)`. .. confval:: disallow_incomplete_defs :type: boolean :default: False - Disallows defining functions with incomplete type annotations. + Disallows defining functions with incomplete type annotations, while still + allowing entirely unannotated definitions. + + For example, it would report an error for :code:`def f(a: int, b)` but not :code:`def f(a, b)`. .. confval:: check_untyped_defs @@ -541,10 +584,15 @@ section of the command line docs. :type: boolean :default: True - Enables or disables strict Optional checks. If False, mypy treats ``None`` + Effectively disables checking of :py:data:`~typing.Optional` + types and ``None`` values. With this option, mypy doesn't + generally check the use of ``None`` values -- it is treated as compatible with every type. - **Note:** This was False by default in mypy versions earlier than 0.600. + .. warning:: + + ``strict_optional = false`` is evil. Avoid using it and definitely do + not use it without understanding what it does. Configuring warnings @@ -775,6 +823,22 @@ These options may only be set in the global section (``[mypy]``). Show absolute paths to files. +.. confval:: force_uppercase_builtins + + :type: boolean + :default: False + + Always use ``List`` instead of ``list`` in error messages, + even on Python 3.9+. + +.. confval:: force_union_syntax + + :type: boolean + :default: False + + Always use ``Union[]`` and ``Optional[]`` for union types + in error messages (instead of the ``|`` operator), + even on Python 3.10+. Incremental mode **************** diff --git a/docs/source/error_code_list.rst b/docs/source/error_code_list.rst index 0388cd2165dd..64d9a1d03287 100644 --- a/docs/source/error_code_list.rst +++ b/docs/source/error_code_list.rst @@ -8,6 +8,8 @@ with default options. See :ref:`error-codes` for general documentation about error codes. :ref:`error-codes-optional` documents additional error codes that you can enable. +.. _code-attr-defined: + Check that attribute exists [attr-defined] ------------------------------------------ @@ -43,6 +45,8 @@ A reference to a missing attribute is given the ``Any`` type. In the above example, the type of ``non_existent`` will be ``Any``, which can be important if you silence the error. +.. _code-union-attr: + Check that attribute exists in each union item [union-attr] ----------------------------------------------------------- @@ -75,6 +79,8 @@ You can often work around these errors by using ``assert isinstance(obj, ClassNa or ``assert obj is not None`` to tell mypy that you know that the type is more specific than what mypy thinks. +.. _code-name-defined: + Check that name is defined [name-defined] ----------------------------------------- @@ -89,6 +95,7 @@ This example accidentally calls ``sort()`` instead of :py:func:`sorted`: x = sort([3, 2, 4]) # Error: Name "sort" is not defined [name-defined] +.. _code-used-before-def: Check that a variable is not used before it's defined [used-before-def] ----------------------------------------------------------------------- @@ -105,6 +112,7 @@ Example: print(x) # Error: Name "x" is used before definition [used-before-def] x = 123 +.. _code-call-arg: Check arguments in calls [call-arg] ----------------------------------- @@ -124,6 +132,8 @@ Example: greet('jack') # OK greet('jill', 'jack') # Error: Too many arguments for "greet" [call-arg] +.. _code-arg-type: + Check argument types [arg-type] ------------------------------- @@ -144,6 +154,8 @@ Example: # expected "list[int]" [arg-type] print(first(t)) +.. _code-call-overload: + Check calls to overloaded functions [call-overload] --------------------------------------------------- @@ -175,6 +187,8 @@ Example: # Error: No overload variant of "inc_maybe" matches argument type "float" [call-overload] inc_maybe(1.2) +.. _code-valid-type: + Check validity of types [valid-type] ------------------------------------ @@ -207,6 +221,8 @@ You can use :py:data:`~typing.Callable` as the type for callable objects: for x in objs: f(x) +.. _code-var-annotated: + Require annotation if variable type is unclear [var-annotated] -------------------------------------------------------------- @@ -239,6 +255,8 @@ To address this, we add an explicit annotation: reveal_type(Bundle().items) # list[str] +.. _code-override: + Check validity of overrides [override] -------------------------------------- @@ -275,6 +293,8 @@ Example: arg: bool) -> int: ... +.. _code-return: + Check that function returns a value [return] -------------------------------------------- @@ -303,6 +323,40 @@ Example: else: raise ValueError('not defined for zero') +.. _code-empty-body: + +Check that functions don't have empty bodies outside stubs [empty-body] +----------------------------------------------------------------------- + +This error code is similar to the ``[return]`` code but is emitted specifically +for functions and methods with empty bodies (if they are annotated with +non-trivial return type). Such a distinction exists because in some contexts +an empty body can be valid, for example for an abstract method or in a stub +file. Also old versions of mypy used to unconditionally allow functions with +empty bodies, so having a dedicated error code simplifies cross-version +compatibility. + +Note that empty bodies are allowed for methods in *protocols*, and such methods +are considered implicitly abstract: + +.. code-block:: python + + from abc import abstractmethod + from typing import Protocol + + class RegularABC: + @abstractmethod + def foo(self) -> int: + pass # OK + def bar(self) -> int: + pass # Error: Missing return statement [empty-body] + + class Proto(Protocol): + def bar(self) -> int: + pass # OK + +.. _code-return-value: + Check that return value is compatible [return-value] ---------------------------------------------------- @@ -317,6 +371,8 @@ Example: # Error: Incompatible return value type (got "int", expected "str") [return-value] return x + 1 +.. _code-assignment: + Check types in assignment statement [assignment] ------------------------------------------------ @@ -339,12 +395,14 @@ Example: # variable has type "str") [assignment] r.name = 5 +.. _code-method-assign: + Check that assignment target is not a method [method-assign] ------------------------------------------------------------ In general, assigning to a method on class object or instance (a.k.a. monkey-patching) is ambiguous in terms of types, since Python's static type -system cannot express difference between bound and unbound callable types. +system cannot express the difference between bound and unbound callable types. Consider this example: .. code-block:: python @@ -355,18 +413,20 @@ Consider this example: def h(self: A) -> None: pass - A.f = h # type of h is Callable[[A], None] - A().f() # this works - A.f = A().g # type of A().g is Callable[[], None] - A().f() # but this also works at runtime + A.f = h # Type of h is Callable[[A], None] + A().f() # This works + A.f = A().g # Type of A().g is Callable[[], None] + A().f() # ...but this also works at runtime To prevent the ambiguity, mypy will flag both assignments by default. If this -error code is disabled, mypy will treat all method assignments r.h.s. as unbound, -so the second assignment will still generate an error. +error code is disabled, mypy will treat the assigned value in all method assignments as unbound, +so only the second assignment will still generate an error. .. note:: - This error code is a sub-error code of a wider ``[assignment]`` code. + This error code is a subcode of the more general ``[assignment]`` code. + +.. _code-type-var: Check type variable values [type-var] ------------------------------------- @@ -390,6 +450,8 @@ Example: # Error: Value of type variable "T1" of "add" cannot be "str" [type-var] add('x', 'y') +.. _code-operator: + Check uses of various operators [operator] ------------------------------------------ @@ -404,6 +466,8 @@ Example: # Error: Unsupported operand types for + ("int" and "str") [operator] 1 + 'x' +.. _code-index: + Check indexing operations [index] --------------------------------- @@ -425,6 +489,8 @@ Example: # Error: Invalid index type "bytes" for "dict[str, int]"; expected type "str" [index] a[b'x'] = 4 +.. _code-list-item: + Check list items [list-item] ---------------------------- @@ -439,6 +505,8 @@ Example: # Error: List item 0 has incompatible type "int"; expected "str" [list-item] a: list[str] = [0] +.. _code-dict-item: + Check dict items [dict-item] ---------------------------- @@ -453,21 +521,23 @@ Example: # Error: Dict entry 0 has incompatible type "str": "str"; expected "str": "int" [dict-item] d: dict[str, int] = {'key': 'value'} +.. _code-typeddict-item: + Check TypedDict items [typeddict-item] -------------------------------------- -When constructing a ``TypedDict`` object, mypy checks that each key and value is compatible -with the ``TypedDict`` type that is inferred from the surrounding context. +When constructing a TypedDict object, mypy checks that each key and value is compatible +with the TypedDict type that is inferred from the surrounding context. -When getting a ``TypedDict`` item, mypy checks that the key -exists. When assigning to a ``TypedDict``, mypy checks that both the +When getting a TypedDict item, mypy checks that the key +exists. When assigning to a TypedDict, mypy checks that both the key and the value are valid. Example: .. code-block:: python - from typing_extensions import TypedDict + from typing import TypedDict class Point(TypedDict): x: int @@ -477,17 +547,22 @@ Example: # TypedDict item "x" has type "int") [typeddict-item] p: Point = {'x': 1.2, 'y': 4} +.. _code-typeddict-unknown-key: + Check TypedDict Keys [typeddict-unknown-key] -------------------------------------------- -When constructing a ``TypedDict`` object, mypy checks whether the definition -contains unknown keys. For convenience's sake, mypy will not generate an error -when a ``TypedDict`` has extra keys if it's passed to a function as an argument. -However, it will generate an error when these are created. Example: +When constructing a TypedDict object, mypy checks whether the +definition contains unknown keys, to catch invalid keys and +misspellings. On the other hand, mypy will not generate an error when +a previously constructed TypedDict value with extra keys is passed +to a function as an argument, since TypedDict values support +structural subtyping ("static duck typing") and the keys are assumed +to have been validated at the point of construction. Example: .. code-block:: python - from typing_extensions import TypedDict + from typing import TypedDict class Point(TypedDict): x: int @@ -502,13 +577,13 @@ However, it will generate an error when these are created. Example: a: Point = {"x": 1, "y": 4} b: Point3D = {"x": 2, "y": 5, "z": 6} - # OK - add_x_coordinates(a, b) + add_x_coordinates(a, b) # OK + # Error: Extra key "z" for TypedDict "Point" [typeddict-unknown-key] add_x_coordinates(a, {"x": 1, "y": 4, "z": 5}) - -Setting an unknown value on a ``TypedDict`` will also generate this error: +Setting a TypedDict item using an unknown key will also generate this +error, since it could be a misspelling: .. code-block:: python @@ -516,9 +591,9 @@ Setting an unknown value on a ``TypedDict`` will also generate this error: # Error: Extra key "z" for TypedDict "Point" [typeddict-unknown-key] a["z"] = 3 - -Whereas reading an unknown value will generate the more generic/serious -``typeddict-item``: +Reading an unknown key will generate the more general (and serious) +``typeddict-item`` error, which is likely to result in an exception at +runtime: .. code-block:: python @@ -528,7 +603,9 @@ Whereas reading an unknown value will generate the more generic/serious .. note:: - This error code is a sub-error code of a wider ``[typeddict-item]`` code. + This error code is a subcode of the wider ``[typeddict-item]`` code. + +.. _code-has-type: Check that type of target is known [has-type] --------------------------------------------- @@ -569,8 +646,20 @@ the issue: def set_y(self) -> None: self.y: int = self.x # Added annotation here -Check that import target can be found [import] ----------------------------------------------- +.. _code-import: + +Check for an issue with imports [import] +---------------------------------------- + +Mypy generates an error if it can't resolve an `import` statement. +This is a parent error code of `import-not-found` and `import-untyped` + +See :ref:`ignore-missing-imports` for how to work around these errors. + +.. _code-import-not-found: + +Check that import target can be found [import-not-found] +-------------------------------------------------------- Mypy generates an error if it can't find the source code or a stub file for an imported module. @@ -579,11 +668,33 @@ Example: .. code-block:: python - # Error: Cannot find implementation or library stub for module named 'acme' [import] - import acme + # Error: Cannot find implementation or library stub for module named "m0dule_with_typo" [import-not-found] + import m0dule_with_typo See :ref:`ignore-missing-imports` for how to work around these errors. +.. _code-import-untyped: + +Check that import target can be found [import-untyped] +-------------------------------------------------------- + +Mypy generates an error if it can find the source code for an imported module, +but that module does not provide type annotations (via :ref:`PEP 561 `). + +Example: + +.. code-block:: python + + # Error: Library stubs not installed for "bs4" [import-untyped] + import bs4 + # Error: Skipping analyzing "no_py_typed": module is installed, but missing library stubs or py.typed marker [import-untyped] + import no_py_typed + +In some cases, these errors can be fixed by installing an appropriate +stub package. See :ref:`ignore-missing-imports` for more details. + +.. _code-no-redef: + Check that each name is defined once [no-redef] ----------------------------------------------- @@ -610,6 +721,8 @@ Example: # (the first definition wins!) A('x') +.. _code-func-returns-value: + Check that called function returns a value [func-returns-value] --------------------------------------------------------------- @@ -628,10 +741,12 @@ returns ``None``: # OK: we don't do anything with the return value f() - # Error: "f" does not return a value [func-returns-value] + # Error: "f" does not return a value (it only ever returns None) [func-returns-value] if f(): print("not false") +.. _code-abstract: + Check instantiation of abstract classes [abstract] -------------------------------------------------- @@ -663,6 +778,8 @@ Example: # Error: Cannot instantiate abstract class "Thing" with abstract attribute "save" [abstract] t = Thing() +.. _code-type-abstract: + Safe handling of abstract type object types [type-abstract] ----------------------------------------------------------- @@ -689,6 +806,8 @@ Example: # Error: Only concrete class can be given where "Type[Config]" is expected [type-abstract] make_many(Config, 5) +.. _code-safe-super: + Check that call to an abstract method via super is valid [safe-super] --------------------------------------------------------------------- @@ -711,10 +830,12 @@ will cause runtime errors, so mypy prevents you from doing so: Mypy considers the following as trivial bodies: a ``pass`` statement, a literal ellipsis ``...``, a docstring, and a ``raise NotImplementedError`` statement. +.. _code-valid-newtype: + Check the target of NewType [valid-newtype] ------------------------------------------- -The target of a :py:func:`NewType ` definition must be a class type. It can't +The target of a :py:class:`~typing.NewType` definition must be a class type. It can't be a union type, ``Any``, or various other special types. You can also get this error if the target has been imported from a @@ -735,6 +856,8 @@ To work around the issue, you can either give mypy access to the sources for ``acme`` or create a stub file for the module. See :ref:`ignore-missing-imports` for more information. +.. _code-exit-return: + Check the return type of __exit__ [exit-return] ----------------------------------------------- @@ -745,7 +868,7 @@ the return type affects which lines mypy thinks are reachable after a ``True`` may swallow exceptions. An imprecise return type can result in mysterious errors reported near ``with`` statements. -To fix this, use either ``typing_extensions.Literal[False]`` or +To fix this, use either ``typing.Literal[False]`` or ``None`` as the return type. Returning ``None`` is equivalent to returning ``False`` in this context, since both are treated as false values. @@ -774,7 +897,7 @@ You can use ``Literal[False]`` to fix the error: .. code-block:: python - from typing_extensions import Literal + from typing import Literal class MyContext: ... @@ -791,6 +914,8 @@ You can also use ``None``: def __exit__(self, exc, value, tb) -> None: # Also OK print('exit') +.. _code-name-match: + Check that naming is consistent [name-match] -------------------------------------------- @@ -804,14 +929,16 @@ consistently when using the call-based syntax. Example: # Error: First argument to namedtuple() should be "Point2D", not "Point" Point2D = NamedTuple("Point", [("x", int), ("y", int)]) +.. _code-literal-required: + Check that literal is used where expected [literal-required] ------------------------------------------------------------ There are some places where only a (string) literal value is expected for the purposes of static type checking, for example a ``TypedDict`` key, or a ``__match_args__`` item. Providing a ``str``-valued variable in such contexts -will result in an error. Note however, in many cases you can use ``Final``, -or ``Literal`` variables, for example: +will result in an error. Note that in many cases you can also use ``Final`` +or ``Literal`` variables. Example: .. code-block:: python @@ -833,6 +960,8 @@ or ``Literal`` variables, for example: # expected one of ("x", "y") [literal-required] p[key] +.. _code-no-overload-impl: + Check that overloaded functions have an implementation [no-overload-impl] ------------------------------------------------------------------------- @@ -855,6 +984,8 @@ implementation. def func(value): pass # actual implementation +.. _code-unused-coroutine: + Check that coroutine return value is used [unused-coroutine] ------------------------------------------------------------ @@ -878,6 +1009,41 @@ otherwise unused variable: _ = f() # No error +.. _code-top-level-await: + +Warn about top level await expressions [top-level-await] +-------------------------------------------------------- + +This error code is separate from the general ``[syntax]`` errors, because in +some environments (e.g. IPython) a top level ``await`` is allowed. In such +environments a user may want to use ``--disable-error-code=top-level-await``, +that allows to still have errors for other improper uses of ``await``, for +example: + +.. code-block:: python + + async def f() -> None: + ... + + top = await f() # Error: "await" outside function [top-level-await] + +.. _code-await-not-async: + +Warn about await expressions used outside of coroutines [await-not-async] +------------------------------------------------------------------------- + +``await`` must be used inside a coroutine. + +.. code-block:: python + + async def f() -> None: + ... + + def g() -> None: + await f() # Error: "await" outside coroutine ("async def") [await-not-async] + +.. _code-assert-type: + Check types in assert_type [assert-type] ---------------------------------------- @@ -892,6 +1058,8 @@ the provided type. assert_type([1], list[str]) # Error +.. _code-truthy-function: + Check that function isn't used in boolean context [truthy-function] ------------------------------------------------------------------- @@ -905,6 +1073,129 @@ Functions will always evaluate to true in boolean contexts. if f: # Error: Function "Callable[[], Any]" could always be true in boolean context [truthy-function] pass +.. _code-str-format: + +Check that string formatting/interpolation is type-safe [str-format] +-------------------------------------------------------------------- + +Mypy will check that f-strings, ``str.format()`` calls, and ``%`` interpolations +are valid (when corresponding template is a literal string). This includes +checking number and types of replacements, for example: + +.. code-block:: python + + # Error: Cannot find replacement for positional format specifier 1 [str-format] + "{} and {}".format("spam") + "{} and {}".format("spam", "eggs") # OK + # Error: Not all arguments converted during string formatting [str-format] + "{} and {}".format("spam", "eggs", "cheese") + + # Error: Incompatible types in string interpolation + # (expression has type "float", placeholder has type "int") [str-format] + "{:d}".format(3.14) + +.. _code-str-bytes-safe: + +Check for implicit bytes coercions [str-bytes-safe] +------------------------------------------------------------------- + +Warn about cases where a bytes object may be converted to a string in an unexpected manner. + +.. code-block:: python + + b = b"abc" + + # Error: If x = b'abc' then f"{x}" or "{}".format(x) produces "b'abc'", not "abc". + # If this is desired behavior, use f"{x!r}" or "{!r}".format(x). + # Otherwise, decode the bytes [str-bytes-safe] + print(f"The alphabet starts with {b}") + + # Okay + print(f"The alphabet starts with {b!r}") # The alphabet starts with b'abc' + print(f"The alphabet starts with {b.decode('utf-8')}") # The alphabet starts with abc + +.. _code-overload-overlap: + +Check that overloaded functions don't overlap [overload-overlap] +---------------------------------------------------------------- + +Warn if multiple ``@overload`` variants overlap in potentially unsafe ways. +This guards against the following situation: + +.. code-block:: python + + from typing import overload + + class A: ... + class B(A): ... + + @overload + def foo(x: B) -> int: ... # Error: Overloaded function signatures 1 and 2 overlap with incompatible return types [overload-overlap] + @overload + def foo(x: A) -> str: ... + def foo(x): ... + + def takes_a(a: A) -> str: + return foo(a) + + a: A = B() + value = takes_a(a) + # mypy will think that value is a str, but it could actually be an int + reveal_type(value) # Revealed type is "builtins.str" + + +Note that in cases where you ignore this error, mypy will usually still infer the +types you expect. + +See :ref:`overloading ` for more explanation. + +.. _code-annotation-unchecked: + +Notify about an annotation in an unchecked function [annotation-unchecked] +-------------------------------------------------------------------------- + +Sometimes a user may accidentally omit an annotation for a function, and mypy +will not check the body of this function (unless one uses +:option:`--check-untyped-defs ` or +:option:`--disallow-untyped-defs `). To avoid +such situations go unnoticed, mypy will show a note, if there are any type +annotations in an unchecked function: + +.. code-block:: python + + def test_assignment(): # "-> None" return annotation is missing + # Note: By default the bodies of untyped functions are not checked, + # consider using --check-untyped-defs [annotation-unchecked] + x: int = "no way" + +Note that mypy will still exit with return code ``0``, since such behaviour is +specified by :pep:`484`. + +.. _code-prop-decorator: + +Decorator preceding property not supported [prop-decorator] +----------------------------------------------------------- + +Mypy does not yet support analysis of decorators that precede the property +decorator. If the decorator does not preserve the declared type of the property, +mypy will not infer the correct type for the declaration. If the decorator cannot +be moved after the ``@property`` decorator, then you must use a type ignore +comment: + +.. code-block:: python + + class MyClass + @special # type: ignore[prop-decorator] + @property + def magic(self) -> str: + return "xyzzy" + +.. note:: + + For backward compatibility, this error code is a subcode of the generic ``[misc]`` code. + +.. _code-syntax: + Report syntax errors [syntax] ----------------------------- @@ -912,6 +1203,8 @@ If the code being checked is not syntactically valid, mypy issues a syntax error. Most, but not all, syntax errors are *blocking errors*: they can't be ignored with a ``# type: ignore`` comment. +.. _code-misc: + Miscellaneous checks [misc] --------------------------- diff --git a/docs/source/error_code_list2.rst b/docs/source/error_code_list2.rst index f160515f0a9e..465d1c7a6583 100644 --- a/docs/source/error_code_list2.rst +++ b/docs/source/error_code_list2.rst @@ -15,6 +15,8 @@ error codes that are enabled by default. options by using a :ref:`configuration file ` or :ref:`command-line options `. +.. _code-type-arg: + Check that type arguments exist [type-arg] ------------------------------------------ @@ -34,6 +36,8 @@ Example: def remove_dups(items: list) -> list: ... +.. _code-no-untyped-def: + Check that every function has an annotation [no-untyped-def] ------------------------------------------------------------ @@ -62,6 +66,8 @@ Example: def __init__(self) -> None: self.value = 0 +.. _code-redundant-cast: + Check that cast is not redundant [redundant-cast] ------------------------------------------------- @@ -82,6 +88,8 @@ Example: # Error: Redundant cast to "int" [redundant-cast] return cast(int, x) +.. _code-redundant-self: + Check that methods do not have redundant Self annotations [redundant-self] -------------------------------------------------------------------------- @@ -104,6 +112,8 @@ Example: def copy(self: Self) -> Self: return type(self)() +.. _code-comparison-overlap: + Check that comparisons are overlapping [comparison-overlap] ----------------------------------------------------------- @@ -135,6 +145,8 @@ literal: def is_magic(x: bytes) -> bool: return x == b'magic' # OK +.. _code-no-untyped-call: + Check that no untyped functions are called [no-untyped-call] ------------------------------------------------------------ @@ -154,6 +166,7 @@ Example: def bad(): ... +.. _code-no-any-return: Check that function does not return Any value [no-any-return] ------------------------------------------------------------- @@ -175,6 +188,8 @@ Example: # Error: Returning Any from function declared to return "str" [no-any-return] return fields(x)[0] +.. _code-no-any-unimported: + Check that types have no Any components due to missing imports [no-any-unimported] ---------------------------------------------------------------------------------- @@ -195,6 +210,8 @@ that ``Cat`` falls back to ``Any`` in a type annotation: def feed(cat: Cat) -> None: ... +.. _code-unreachable: + Check that statement or expression is unreachable [unreachable] --------------------------------------------------------------- @@ -214,6 +231,8 @@ incorrect control flow or conditional checks that are accidentally always true o # Error: Statement is unreachable [unreachable] print('unreachable') +.. _code-redundant-expr: + Check that expression is redundant [redundant-expr] --------------------------------------------------- @@ -236,6 +255,34 @@ mypy generates an error if it thinks that an expression is redundant. [i for i in range(x) if isinstance(i, int)] +.. _code-possibly-undefined: + +Warn about variables that are defined only in some execution paths [possibly-undefined] +--------------------------------------------------------------------------------------- + +If you use :option:`--enable-error-code possibly-undefined `, +mypy generates an error if it cannot verify that a variable will be defined in +all execution paths. This includes situations when a variable definition +appears in a loop, in a conditional branch, in an except handler, etc. For +example: + +.. code-block:: python + + # Use "mypy --enable-error-code possibly-undefined ..." + + from typing import Iterable + + def test(values: Iterable[int], flag: bool) -> None: + if flag: + a = 1 + z = a + 1 # Error: Name "a" may be undefined [possibly-undefined] + + for v in values: + b = v + z = b + 1 # Error: Name "b" may be undefined [possibly-undefined] + +.. _code-truthy-bool: + Check that expression is not implicitly true in boolean context [truthy-bool] ----------------------------------------------------------------------------- @@ -259,6 +306,7 @@ Using an iterable value in a boolean context has a separate error code if foo: ... +.. _code-truthy-iterable: Check that iterable is not implicitly true in boolean context [truthy-iterable] ------------------------------------------------------------------------------- @@ -286,8 +334,7 @@ items`` check is actually valid. If that is the case, it is recommended to annotate ``items`` as ``Collection[int]`` instead of ``Iterable[int]``. - -.. _ignore-without-code: +.. _code-ignore-without-code: Check that ``# type: ignore`` include an error code [ignore-without-code] ------------------------------------------------------------------------- @@ -319,6 +366,8 @@ Example: # Error: "Foo" has no attribute "nme"; maybe "name"? f.nme = 42 # type: ignore[assignment] +.. _code-unused-awaitable: + Check that awaitable return value is used [unused-awaitable] ------------------------------------------------------------ @@ -347,3 +396,178 @@ silence the error: async def g() -> None: _ = asyncio.create_task(f()) # No error + +.. _code-unused-ignore: + +Check that ``# type: ignore`` comment is used [unused-ignore] +------------------------------------------------------------- + +If you use :option:`--enable-error-code unused-ignore `, +or :option:`--warn-unused-ignores ` +mypy generates an error if you don't use a ``# type: ignore`` comment, i.e. if +there is a comment, but there would be no error generated by mypy on this line +anyway. + +Example: + +.. code-block:: python + + # Use "mypy --warn-unused-ignores ..." + + def add(a: int, b: int) -> int: + # Error: unused "type: ignore" comment + return a + b # type: ignore + +Note that due to a specific nature of this comment, the only way to selectively +silence it, is to include the error code explicitly. Also note that this error is +not shown if the ``# type: ignore`` is not used due to code being statically +unreachable (e.g. due to platform or version checks). + +Example: + +.. code-block:: python + + # Use "mypy --warn-unused-ignores ..." + + import sys + + try: + # The "[unused-ignore]" is needed to get a clean mypy run + # on both Python 3.8, and 3.9 where this module was added + import graphlib # type: ignore[import,unused-ignore] + except ImportError: + pass + + if sys.version_info >= (3, 9): + # The following will not generate an error on either + # Python 3.8, or Python 3.9 + 42 + "testing..." # type: ignore + +.. _code-explicit-override: + +Check that ``@override`` is used when overriding a base class method [explicit-override] +---------------------------------------------------------------------------------------- + +If you use :option:`--enable-error-code explicit-override ` +mypy generates an error if you override a base class method without using the +``@override`` decorator. An error will not be emitted for overrides of ``__init__`` +or ``__new__``. See `PEP 698 `_. + +.. note:: + + Starting with Python 3.12, the ``@override`` decorator can be imported from ``typing``. + To use it with older Python versions, import it from ``typing_extensions`` instead. + +Example: + +.. code-block:: python + + # Use "mypy --enable-error-code explicit-override ..." + + from typing import override + + class Parent: + def f(self, x: int) -> None: + pass + + def g(self, y: int) -> None: + pass + + + class Child(Parent): + def f(self, x: int) -> None: # Error: Missing @override decorator + pass + + @override + def g(self, y: int) -> None: + pass + +.. _code-mutable-override: + +Check that overrides of mutable attributes are safe [mutable-override] +---------------------------------------------------------------------- + +`mutable-override` will enable the check for unsafe overrides of mutable attributes. +For historical reasons, and because this is a relatively common pattern in Python, +this check is not enabled by default. The example below is unsafe, and will be +flagged when this error code is enabled: + +.. code-block:: python + + from typing import Any + + class C: + x: float + y: float + z: float + + class D(C): + x: int # Error: Covariant override of a mutable attribute + # (base class "C" defined the type as "float", + # expression has type "int") [mutable-override] + y: float # OK + z: Any # OK + + def f(c: C) -> None: + c.x = 1.1 + d = D() + f(d) + d.x >> 1 # This will crash at runtime, because d.x is now float, not an int + +.. _code-unimported-reveal: + +Check that ``reveal_type`` is imported from typing or typing_extensions [unimported-reveal] +------------------------------------------------------------------------------------------- + +Mypy used to have ``reveal_type`` as a special builtin +that only existed during type-checking. +In runtime it fails with expected ``NameError``, +which can cause real problem in production, hidden from mypy. + +But, in Python3.11 :py:func:`typing.reveal_type` was added. +``typing_extensions`` ported this helper to all supported Python versions. + +Now users can actually import ``reveal_type`` to make the runtime code safe. + +.. note:: + + Starting with Python 3.11, the ``reveal_type`` function can be imported from ``typing``. + To use it with older Python versions, import it from ``typing_extensions`` instead. + +.. code-block:: python + + # Use "mypy --enable-error-code unimported-reveal" + + x = 1 + reveal_type(x) # Note: Revealed type is "builtins.int" \ + # Error: Name "reveal_type" is not defined + +Correct usage: + +.. code-block:: python + + # Use "mypy --enable-error-code unimported-reveal" + from typing import reveal_type # or `typing_extensions` + + x = 1 + # This won't raise an error: + reveal_type(x) # Note: Revealed type is "builtins.int" + +When this code is enabled, using ``reveal_locals`` is always an error, +because there's no way one can import it. + +.. _code-narrowed-type-not-subtype: + +Check that ``TypeIs`` narrows types [narrowed-type-not-subtype] +--------------------------------------------------------------- + +:pep:`742` requires that when ``TypeIs`` is used, the narrowed +type must be a subtype of the original type:: + + from typing_extensions import TypeIs + + def f(x: int) -> TypeIs[str]: # Error, str is not a subtype of int + ... + + def g(x: object) -> TypeIs[str]: # OK + ... diff --git a/docs/source/error_codes.rst b/docs/source/error_codes.rst index 34bb8ab6b5e1..35fad161f8a2 100644 --- a/docs/source/error_codes.rst +++ b/docs/source/error_codes.rst @@ -19,22 +19,6 @@ Most error codes are shared between multiple related error messages. Error codes may change in future mypy releases. - -Displaying error codes ----------------------- - -Error codes are displayed by default. Use :option:`--hide-error-codes ` -or config ``hide_error_codes = True`` to hide error codes. Error codes are shown inside square brackets: - -.. code-block:: text - - $ mypy prog.py - prog.py:1: error: "str" has no attribute "trim" [attr-defined] - -It's also possible to require error codes for ``type: ignore`` comments. -See :ref:`ignore-without-code` for more information. - - .. _silence-error-codes: Silencing errors based on error codes @@ -43,11 +27,7 @@ Silencing errors based on error codes You can use a special comment ``# type: ignore[code, ...]`` to only ignore errors with a specific error code (or codes) on a particular line. This can be used even if you have not configured mypy to show -error codes. Currently it's only possible to disable arbitrary error -codes on individual lines using this comment. - -You can also use :option:`--disable-error-code ` -to disable specific error codes globally. +error codes. This example shows how to ignore an error about an imported name mypy thinks is undefined: @@ -58,17 +38,17 @@ thinks is undefined: # definition. from foolib import foo # type: ignore[attr-defined] - -Enabling specific error codes ------------------------------ +Enabling/disabling specific error codes globally +------------------------------------------------ There are command-line flags and config file settings for enabling certain optional error codes, such as :option:`--disallow-untyped-defs `, which enables the ``no-untyped-def`` error code. -You can use :option:`--enable-error-code ` to -enable specific error codes that don't have a dedicated command-line -flag or config file setting. +You can use :option:`--enable-error-code ` +and :option:`--disable-error-code ` +to enable or disable specific error codes that don't have a dedicated +command-line flag or config file setting. Per-module enabling/disabling error codes ----------------------------------------- @@ -107,20 +87,28 @@ still keep the other two error codes enabled. The overall logic is following: * Individual config sections *adjust* them per glob/module -* Inline ``# mypy: ...`` comments can further *adjust* them for a specific - module +* Inline ``# mypy: disable-error-code="..."`` comments can further + *adjust* them for a specific module. + For example: ``# mypy: disable-error-code="truthy-bool, ignore-without-code"`` So one can e.g. enable some code globally, disable it for all tests in the corresponding config section, and then re-enable it with an inline comment in some specific test. -Sub-error codes of other error codes ------------------------------------- +Subcodes of error codes +----------------------- -In rare cases (mostly for backwards compatibility reasons), some error -code may be covered by another, wider error code. For example, an error with +In some cases, mostly for backwards compatibility reasons, an error +code may be covered also by another, wider error code. For example, an error with code ``[method-assign]`` can be ignored by ``# type: ignore[assignment]``. Similar logic works for disabling error codes globally. If a given error code -is a sub code of another one, it must mentioned in the docs for the narrower -code. This hierarchy is not nested, there cannot be sub-error codes of other -sub-error codes. +is a subcode of another one, it will be mentioned in the documentation for the narrower +code. This hierarchy is not nested: there cannot be subcodes of other +subcodes. + + +Requiring error codes +--------------------- + +It's possible to require error codes be specified in ``type: ignore`` comments. +See :ref:`ignore-without-code` for more information. diff --git a/docs/source/existing_code.rst b/docs/source/existing_code.rst index 410d7af0c350..0a5ac2bfa8f6 100644 --- a/docs/source/existing_code.rst +++ b/docs/source/existing_code.rst @@ -31,8 +31,8 @@ invocation to your codebase, or adding your mypy invocation to existing tools you use to run tests, like ``tox``. * Make sure everyone runs mypy with the same options. Checking a mypy - :ref:`configuration file ` into your codebase can help - with this. + :ref:`configuration file ` into your codebase is the + easiest way to do this. * Make sure everyone type checks the same set of files. See :ref:`specifying-code-to-be-checked` for details. @@ -48,7 +48,7 @@ A simple CI script could look something like this: .. code-block:: text - python3 -m pip install mypy==0.971 + python3 -m pip install mypy==1.8 # Run your standardised mypy invocation, e.g. mypy my_project # This could also look like `scripts/run_mypy.sh`, `tox run -e mypy`, `make mypy`, etc @@ -74,6 +74,11 @@ You could even invert this, by setting ``ignore_errors = True`` in your global config section and only enabling error reporting with ``ignore_errors = False`` for the set of modules you are ready to type check. +The per-module configuration that mypy's configuration file allows can be +extremely useful. Many configuration options can be enabled or disabled +only for specific modules. In particular, you can also enable or disable +various error codes on a per-module basis, see :ref:`error-codes`. + Fixing errors related to imports -------------------------------- @@ -89,7 +94,7 @@ that it can't find, that don't have types, or don't have stub files: Sometimes these can be fixed by installing the relevant packages or stub libraries in the environment you're running ``mypy`` in. -See :ref:`ignore-missing-imports` for a complete reference on these errors +See :ref:`fix-missing-imports` for a complete reference on these errors and the ways in which you can fix them. You'll likely find that you want to suppress all errors from importing @@ -118,13 +123,15 @@ codebase, use a config like this: ignore_missing_imports = True If you get a large number of errors, you may want to ignore all errors -about missing imports, for instance by setting :confval:`ignore_missing_imports` -to true globally. This can hide errors later on, so we recommend avoiding this +about missing imports, for instance by setting +:option:`--disable-error-code=import-untyped `. +or setting :confval:`ignore_missing_imports` to true globally. +This can hide errors later on, so we recommend avoiding this if possible. Finally, mypy allows fine-grained control over specific import following behaviour. It's very easy to silently shoot yourself in the foot when playing -around with these, so it's mostly recommended as a last resort. For more +around with these, so this should be a last resort. For more details, look :ref:`here `. Prioritise annotating widely imported modules @@ -183,7 +190,7 @@ An excellent goal to aim for is to have your codebase pass when run against ``my This basically ensures that you will never have a type related error without an explicit circumvention somewhere (such as a ``# type: ignore`` comment). -The following config is equivalent to ``--strict`` (as of mypy 0.990): +The following config is equivalent to ``--strict`` (as of mypy 1.0): .. code-block:: text @@ -191,7 +198,6 @@ The following config is equivalent to ``--strict`` (as of mypy 0.990): warn_unused_configs = True warn_redundant_casts = True warn_unused_ignores = True - no_implicit_optional = True # Getting these passing should be easy strict_equality = True diff --git a/docs/source/extending_mypy.rst b/docs/source/extending_mypy.rst index daf863616334..bbbec2ad3880 100644 --- a/docs/source/extending_mypy.rst +++ b/docs/source/extending_mypy.rst @@ -159,7 +159,7 @@ This hook will be also called for instantiation of classes. This is a good choice if the return type is too complex to be expressed by regular python typing. -**get_function_signature_hook** is used to adjust the signature of a function. +**get_function_signature_hook()** is used to adjust the signature of a function. **get_method_hook()** is the same as ``get_function_hook()`` but for methods instead of module level functions. @@ -237,3 +237,12 @@ mypy's cache for that module so that it can be rechecked. This hook should be used to report to mypy any relevant configuration data, so that mypy knows to recheck the module if the configuration changes. The hooks should return data encodable as JSON. + +Useful tools +************ + +Mypy ships ``mypy.plugins.proper_plugin`` plugin which can be useful +for plugin authors, since it finds missing ``get_proper_type()`` calls, +which is a pretty common mistake. + +It is recommended to enable it is a part of your plugin's CI. diff --git a/docs/source/faq.rst b/docs/source/faq.rst index d97929c2cfa6..195805382cd3 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -36,7 +36,7 @@ Here are some potential benefits of mypy-style static typing: grows, you can adapt tricky application logic to static typing to help maintenance. -See also the `front page `_ of the mypy web +See also the `front page `_ of the mypy web site. Would my project benefit from static typing? @@ -202,7 +202,7 @@ Mypy is a cool project. Can I help? *********************************** Any help is much appreciated! `Contact -`_ the developers if you would +`_ the developers if you would like to contribute. Any help related to development, design, publicity, documentation, testing, web site maintenance, financing, etc. can be helpful. You can learn a lot by contributing, and anybody diff --git a/docs/source/generics.rst b/docs/source/generics.rst index 9ac79f90121d..01ae7534ba93 100644 --- a/docs/source/generics.rst +++ b/docs/source/generics.rst @@ -770,8 +770,7 @@ protocols mostly follow the normal rules for generic classes. Example: .. code-block:: python - from typing import TypeVar - from typing_extensions import Protocol + from typing import Protocol, TypeVar T = TypeVar('T') diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index 9b927097cfd2..049d7af003b5 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -16,7 +16,7 @@ may not make much sense otherwise. Installing and running mypy *************************** -Mypy requires Python 3.7 or later to run. You can install mypy using pip: +Mypy requires Python 3.8 or later to run. You can install mypy using pip: .. code-block:: shell @@ -66,7 +66,7 @@ This is the case even if you misuse the function! def greeting(name): return 'Hello ' + name - # These calls will fail when the program run, but mypy does not report an error + # These calls will fail when the program runs, but mypy does not report an error # because "greeting" does not have type annotations. greeting(123) greeting(b"Alice") @@ -256,15 +256,14 @@ Mypy can also understand how to work with types from libraries that you use. For instance, mypy comes out of the box with an intimate knowledge of the Python standard library. For example, here is a function which uses the -``Path`` object from the -`pathlib standard library module `_: +``Path`` object from the :doc:`pathlib standard library module `: .. code-block:: python from pathlib import Path def load_template(template_path: Path, name: str) -> str: - # Mypy knows that `file_path` has a `read_text` method that returns a str + # Mypy knows that `template_path` has a `read_text` method that returns a str template = template_path.read_text() # ...so it understands this line type checks return template.replace('USERNAME', name) diff --git a/docs/source/html_builder.py b/docs/source/html_builder.py new file mode 100644 index 000000000000..ea3594e0617b --- /dev/null +++ b/docs/source/html_builder.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +import json +import os +import textwrap +from pathlib import Path +from typing import Any + +from sphinx.addnodes import document +from sphinx.application import Sphinx +from sphinx.builders.html import StandaloneHTMLBuilder +from sphinx.environment import BuildEnvironment + + +class MypyHTMLBuilder(StandaloneHTMLBuilder): + def __init__(self, app: Sphinx, env: BuildEnvironment) -> None: + super().__init__(app, env) + self._ref_to_doc = {} + + def write_doc(self, docname: str, doctree: document) -> None: + super().write_doc(docname, doctree) + self._ref_to_doc.update({_id: docname for _id in doctree.ids}) + + def _verify_error_codes(self) -> None: + from mypy.errorcodes import error_codes + + missing_error_codes = {c for c in error_codes if f"code-{c}" not in self._ref_to_doc} + if missing_error_codes: + raise ValueError( + f"Some error codes are not documented: {', '.join(sorted(missing_error_codes))}" + ) + + def _write_ref_redirector(self) -> None: + if os.getenv("VERIFY_MYPY_ERROR_CODES"): + self._verify_error_codes() + p = Path(self.outdir) / "_refs.html" + data = f""" + + + + + + """ + p.write_text(textwrap.dedent(data)) + + def finish(self) -> None: + super().finish() + self._write_ref_redirector() + + +def setup(app: Sphinx) -> dict[str, Any]: + app.add_builder(MypyHTMLBuilder, override=True) + + return {"version": "0.1", "parallel_read_safe": True, "parallel_write_safe": True} diff --git a/docs/source/index.rst b/docs/source/index.rst index 7ab3edebad39..c9dc6bc1f8c9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -109,7 +109,7 @@ Contents :caption: Project Links GitHub - Website + Website Indices and tables ================== diff --git a/docs/source/installed_packages.rst b/docs/source/installed_packages.rst index b9a3b891c99c..fa4fae1a0b3e 100644 --- a/docs/source/installed_packages.rst +++ b/docs/source/installed_packages.rst @@ -119,7 +119,7 @@ The ``setup.py`` file could look like this: .. code-block:: python - from distutils.core import setup + from setuptools import setup setup( name="SuperPackageA", @@ -129,11 +129,6 @@ The ``setup.py`` file could look like this: packages=["package_a"] ) -.. note:: - - If you use :doc:`setuptools `, you must pass the option ``zip_safe=False`` to - ``setup()``, or mypy will not be able to find the installed package. - Some packages have a mix of stub files and runtime files. These packages also require a ``py.typed`` file. An example can be seen below: @@ -150,7 +145,7 @@ The ``setup.py`` file might look like this: .. code-block:: python - from distutils.core import setup + from setuptools import setup setup( name="SuperPackageB", @@ -180,7 +175,7 @@ The ``setup.py`` might look like this: .. code-block:: python - from distutils.core import setup + from setuptools import setup setup( name="SuperPackageC", diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst index b575a6eac4c5..d07eb40753f3 100644 --- a/docs/source/kinds_of_types.rst +++ b/docs/source/kinds_of_types.rst @@ -200,6 +200,28 @@ using bidirectional type inference: If you want to give the argument or return value types explicitly, use an ordinary, perhaps nested function definition. +Callables can also be used against type objects, matching their +``__init__`` or ``__new__`` signature: + +.. code-block:: python + + from typing import Callable + + class C: + def __init__(self, app: str) -> None: + pass + + CallableType = Callable[[str], C] + + def class_or_callable(arg: CallableType) -> None: + inst = arg("my_app") + reveal_type(inst) # Revealed type is "C" + +This is useful if you want ``arg`` to be either a ``Callable`` returning an +instance of ``C`` or the type of ``C`` itself. This also works with +:ref:`callback protocols `. + + .. _union-types: Union types @@ -407,74 +429,6 @@ the runtime with some limitations (see :ref:`runtime_troubles`). t2: int | None # equivalent to Optional[int] -.. _no_strict_optional: - -Disabling strict optional checking -********************************** - -Mypy also has an option to treat ``None`` as a valid value for every -type (in case you know Java, it's useful to think of it as similar to -the Java ``null``). In this mode ``None`` is also valid for primitive -types such as ``int`` and ``float``, and :py:data:`~typing.Optional` types are -not required. - -The mode is enabled through the :option:`--no-strict-optional ` command-line -option. In mypy versions before 0.600 this was the default mode. You -can enable this option explicitly for backward compatibility with -earlier mypy versions, in case you don't want to introduce optional -types to your codebase yet. - -It will cause mypy to silently accept some buggy code, such as -this example -- it's not recommended if you can avoid it: - -.. code-block:: python - - def inc(x: int) -> int: - return x + 1 - - x = inc(None) # No error reported by mypy if strict optional mode disabled! - -However, making code "optional clean" can take some work! You can also use -:ref:`the mypy configuration file ` to migrate your code -to strict optional checking one file at a time, since there exists -the per-module flag -:confval:`strict_optional` to control strict optional mode. - -Often it's still useful to document whether a variable can be -``None``. For example, this function accepts a ``None`` argument, -but it's not obvious from its signature: - -.. code-block:: python - - def greeting(name: str) -> str: - if name: - return f'Hello, {name}' - else: - return 'Hello, stranger' - - print(greeting('Python')) # Okay! - print(greeting(None)) # Also okay! - -You can still use :py:data:`Optional[t] ` to document that ``None`` is a -valid argument type, even if strict ``None`` checking is not -enabled: - -.. code-block:: python - - from typing import Optional - - def greeting(name: Optional[str]) -> str: - if name: - return f'Hello, {name}' - else: - return 'Hello, stranger' - -Mypy treats this as semantically equivalent to the previous example -if strict optional checking is disabled, since ``None`` is implicitly -valid for any type, but it's much more -useful for a programmer who is reading the code. This also makes -it easier to migrate to strict ``None`` checking in the future. - .. _type-aliases: Type aliases diff --git a/docs/source/literal_types.rst b/docs/source/literal_types.rst index a66d300bd0fd..283bf7f9dba1 100644 --- a/docs/source/literal_types.rst +++ b/docs/source/literal_types.rst @@ -329,13 +329,10 @@ perform an exhaustiveness check, you need to update your code to use an .. code-block:: python from typing import Literal, NoReturn + from typing_extensions import assert_never PossibleValues = Literal['one', 'two'] - def assert_never(value: NoReturn) -> NoReturn: - # This also works at runtime as well - assert False, f'This code should never be reached, got: {value}' - def validate(x: PossibleValues) -> bool: if x == 'one': return True @@ -443,10 +440,7 @@ Let's start with a definition: from enum import Enum from typing import NoReturn - - def assert_never(value: NoReturn) -> NoReturn: - # This also works in runtime as well: - assert False, f'This code should never be reached, got: {value}' + from typing_extensions import assert_never class Direction(Enum): up = 'up' diff --git a/docs/source/more_types.rst b/docs/source/more_types.rst index ff5e8d384351..cb3ef64b39a7 100644 --- a/docs/source/more_types.rst +++ b/docs/source/more_types.rst @@ -2,7 +2,7 @@ More types ========== This section introduces a few additional kinds of types, including :py:data:`~typing.NoReturn`, -:py:func:`NewType `, and types for async code. It also discusses +:py:class:`~typing.NewType`, and types for async code. It also discusses how to give functions more precise types using overloads. All of these are only situationally useful, so feel free to skip this section and come back when you have a need for some of them. @@ -11,7 +11,7 @@ Here's a quick summary of what's covered here: * :py:data:`~typing.NoReturn` lets you tell mypy that a function never returns normally. -* :py:func:`NewType ` lets you define a variant of a type that is treated as a +* :py:class:`~typing.NewType` lets you define a variant of a type that is treated as a separate type by mypy but is identical to the original type at runtime. For example, you can have ``UserId`` as a variant of ``int`` that is just an ``int`` at runtime. @@ -75,7 +75,7 @@ certain values from base class instances. Example: ... However, this approach introduces some runtime overhead. To avoid this, the typing -module provides a helper object :py:func:`NewType ` that creates simple unique types with +module provides a helper object :py:class:`~typing.NewType` that creates simple unique types with almost zero runtime overhead. Mypy will treat the statement ``Derived = NewType('Derived', Base)`` as being roughly equivalent to the following definition: @@ -113,12 +113,12 @@ implicitly casting from ``UserId`` where ``int`` is expected. Examples: num: int = UserId(5) + 1 -:py:func:`NewType ` accepts exactly two arguments. The first argument must be a string literal +:py:class:`~typing.NewType` accepts exactly two arguments. The first argument must be a string literal containing the name of the new type and must equal the name of the variable to which the new type is assigned. The second argument must be a properly subclassable class, i.e., not a type construct like :py:data:`~typing.Union`, etc. -The callable returned by :py:func:`NewType ` accepts only one argument; this is equivalent to +The callable returned by :py:class:`~typing.NewType` accepts only one argument; this is equivalent to supporting only one constructor accepting an instance of the base class (see above). Example: @@ -139,12 +139,12 @@ Example: tcp_packet = TcpPacketId(127, 0) # Fails in type checker and at runtime You cannot use :py:func:`isinstance` or :py:func:`issubclass` on the object returned by -:py:func:`~typing.NewType`, nor can you subclass an object returned by :py:func:`~typing.NewType`. +:py:class:`~typing.NewType`, nor can you subclass an object returned by :py:class:`~typing.NewType`. .. note:: - Unlike type aliases, :py:func:`NewType ` will create an entirely new and - unique type when used. The intended purpose of :py:func:`NewType ` is to help you + Unlike type aliases, :py:class:`~typing.NewType` will create an entirely new and + unique type when used. The intended purpose of :py:class:`~typing.NewType` is to help you detect cases where you accidentally mixed together the old base type and the new derived type. @@ -160,7 +160,7 @@ You cannot use :py:func:`isinstance` or :py:func:`issubclass` on the object retu name_by_id(3) # ints and UserId are synonymous - But a similar example using :py:func:`NewType ` will not typecheck: + But a similar example using :py:class:`~typing.NewType` will not typecheck: .. code-block:: python @@ -501,7 +501,7 @@ To prevent these kinds of issues, mypy will detect and prohibit inherently unsaf overlapping overloads on a best-effort basis. Two variants are considered unsafely overlapping when both of the following are true: -1. All of the arguments of the first variant are compatible with the second. +1. All of the arguments of the first variant are potentially compatible with the second. 2. The return type of the first variant is *not* compatible with (e.g. is not a subtype of) the second. @@ -510,6 +510,9 @@ the ``object`` argument in the second, yet the ``int`` return type is not a subt ``str``. Both conditions are true, so mypy will correctly flag ``unsafe_func`` as being unsafe. +Note that in cases where you ignore the overlapping overload error, mypy will usually +still infer the types you expect at callsites. + However, mypy will not detect *all* unsafe uses of overloads. For example, suppose we modify the above snippet so it calls ``summarize`` instead of ``unsafe_func``: @@ -824,11 +827,11 @@ classes are generic, self-type allows giving them precise signatures: Typing async/await ****************** -Mypy supports the ability to type coroutines that use the ``async/await`` -syntax introduced in Python 3.5. For more information regarding coroutines and -this new syntax, see :pep:`492`. +Mypy lets you type coroutines that use the ``async/await`` syntax. +For more information regarding coroutines, see :pep:`492` and the +`asyncio documentation `_. -Functions defined using ``async def`` are typed just like normal functions. +Functions defined using ``async def`` are typed similar to normal functions. The return type annotation should be the same as the type of the value you expect to get back when ``await``-ing the coroutine. @@ -839,65 +842,40 @@ expect to get back when ``await``-ing the coroutine. async def format_string(tag: str, count: int) -> str: return f'T-minus {count} ({tag})' - async def countdown_1(tag: str, count: int) -> str: + async def countdown(tag: str, count: int) -> str: while count > 0: - my_str = await format_string(tag, count) # has type 'str' + my_str = await format_string(tag, count) # type is inferred to be str print(my_str) await asyncio.sleep(0.1) count -= 1 return "Blastoff!" - loop = asyncio.get_event_loop() - loop.run_until_complete(countdown_1("Millennium Falcon", 5)) - loop.close() + asyncio.run(countdown("Millennium Falcon", 5)) -The result of calling an ``async def`` function *without awaiting* will be a -value of type :py:class:`Coroutine[Any, Any, T] `, which is a subtype of +The result of calling an ``async def`` function *without awaiting* will +automatically be inferred to be a value of type +:py:class:`Coroutine[Any, Any, T] `, which is a subtype of :py:class:`Awaitable[T] `: .. code-block:: python - my_coroutine = countdown_1("Millennium Falcon", 5) - reveal_type(my_coroutine) # has type 'Coroutine[Any, Any, str]' + my_coroutine = countdown("Millennium Falcon", 5) + reveal_type(my_coroutine) # Revealed type is "typing.Coroutine[Any, Any, builtins.str]" -.. note:: +.. _async-iterators: - :ref:`reveal_type() ` displays the inferred static type of - an expression. +Asynchronous iterators +---------------------- -You may also choose to create a subclass of :py:class:`~typing.Awaitable` instead: - -.. code-block:: python - - from typing import Any, Awaitable, Generator - import asyncio - - class MyAwaitable(Awaitable[str]): - def __init__(self, tag: str, count: int) -> None: - self.tag = tag - self.count = count - - def __await__(self) -> Generator[Any, None, str]: - for i in range(n, 0, -1): - print(f'T-minus {i} ({tag})') - yield from asyncio.sleep(0.1) - return "Blastoff!" - - def countdown_3(tag: str, count: int) -> Awaitable[str]: - return MyAwaitable(tag, count) - - loop = asyncio.get_event_loop() - loop.run_until_complete(countdown_3("Heart of Gold", 5)) - loop.close() - -To create an iterable coroutine, subclass :py:class:`~typing.AsyncIterator`: +If you have an asynchronous iterator, you can use the +:py:class:`~typing.AsyncIterator` type in your annotations: .. code-block:: python from typing import Optional, AsyncIterator import asyncio - class arange(AsyncIterator[int]): + class arange: def __init__(self, start: int, stop: int, step: int) -> None: self.start = start self.stop = stop @@ -914,35 +892,92 @@ To create an iterable coroutine, subclass :py:class:`~typing.AsyncIterator`: else: return self.count - async def countdown_4(tag: str, n: int) -> str: - async for i in arange(n, 0, -1): + async def run_countdown(tag: str, countdown: AsyncIterator[int]) -> str: + async for i in countdown: print(f'T-minus {i} ({tag})') await asyncio.sleep(0.1) return "Blastoff!" - loop = asyncio.get_event_loop() - loop.run_until_complete(countdown_4("Serenity", 5)) - loop.close() + asyncio.run(run_countdown("Serenity", arange(5, 0, -1))) -If you use coroutines in legacy code that was originally written for -Python 3.4, which did not support the ``async def`` syntax, you would -instead use the :py:func:`@asyncio.coroutine ` -decorator to convert a generator into a coroutine, and use a -generator type as the return type: +Async generators (introduced in :pep:`525`) are an easy way to create +async iterators: .. code-block:: python - from typing import Any, Generator + from typing import AsyncGenerator, Optional import asyncio - @asyncio.coroutine - def countdown_2(tag: str, count: int) -> Generator[Any, None, str]: - while count > 0: - print(f'T-minus {count} ({tag})') - yield from asyncio.sleep(0.1) - count -= 1 - return "Blastoff!" + # Could also type this as returning AsyncIterator[int] + async def arange(start: int, stop: int, step: int) -> AsyncGenerator[int, None]: + current = start + while (step > 0 and current < stop) or (step < 0 and current > stop): + yield current + current += step + + asyncio.run(run_countdown("Battlestar Galactica", arange(5, 0, -1))) + +One common confusion is that the presence of a ``yield`` statement in an +``async def`` function has an effect on the type of the function: + +.. code-block:: python + + from typing import AsyncIterator + + async def arange(stop: int) -> AsyncIterator[int]: + # When called, arange gives you an async iterator + # Equivalent to Callable[[int], AsyncIterator[int]] + i = 0 + while i < stop: + yield i + i += 1 + + async def coroutine(stop: int) -> AsyncIterator[int]: + # When called, coroutine gives you something you can await to get an async iterator + # Equivalent to Callable[[int], Coroutine[Any, Any, AsyncIterator[int]]] + return arange(stop) + + async def main() -> None: + reveal_type(arange(5)) # Revealed type is "typing.AsyncIterator[builtins.int]" + reveal_type(coroutine(5)) # Revealed type is "typing.Coroutine[Any, Any, typing.AsyncIterator[builtins.int]]" + + await arange(5) # Error: Incompatible types in "await" (actual type "AsyncIterator[int]", expected type "Awaitable[Any]") + reveal_type(await coroutine(5)) # Revealed type is "typing.AsyncIterator[builtins.int]" + +This can sometimes come up when trying to define base classes, Protocols or overloads: + +.. code-block:: python + + from typing import AsyncIterator, Protocol, overload + + class LauncherIncorrect(Protocol): + # Because launch does not have yield, this has type + # Callable[[], Coroutine[Any, Any, AsyncIterator[int]]] + # instead of + # Callable[[], AsyncIterator[int]] + async def launch(self) -> AsyncIterator[int]: + raise NotImplementedError + + class LauncherCorrect(Protocol): + def launch(self) -> AsyncIterator[int]: + raise NotImplementedError + + class LauncherAlsoCorrect(Protocol): + async def launch(self) -> AsyncIterator[int]: + raise NotImplementedError + if False: + yield 0 + + # The type of the overloads is independent of the implementation. + # In particular, their type is not affected by whether or not the + # implementation contains a `yield`. + # Use of `def`` makes it clear the type is Callable[..., AsyncIterator[int]], + # whereas with `async def` it would be Callable[..., Coroutine[Any, Any, AsyncIterator[int]]] + @overload + def launch(*, count: int = ...) -> AsyncIterator[int]: ... + @overload + def launch(*, time: float = ...) -> AsyncIterator[int]: ... - loop = asyncio.get_event_loop() - loop.run_until_complete(countdown_2("USS Enterprise", 5)) - loop.close() + async def launch(*, count: int = 0, time: float = 0) -> AsyncIterator[int]: + # The implementation of launch is an async generator and contains a yield + yield 0 diff --git a/docs/source/mypy_daemon.rst b/docs/source/mypy_daemon.rst index ec12283ea3bb..6c511e14eb95 100644 --- a/docs/source/mypy_daemon.rst +++ b/docs/source/mypy_daemon.rst @@ -59,6 +59,11 @@ you have a large codebase.) back to the stable functionality. See :ref:`follow-imports` for details on how these work. +.. note:: + + The mypy daemon requires ``--local-partial-types`` and automatically enables it. + + Daemon client commands ********************** diff --git a/docs/source/protocols.rst b/docs/source/protocols.rst index cb51809a66d5..731562867691 100644 --- a/docs/source/protocols.rst +++ b/docs/source/protocols.rst @@ -3,26 +3,24 @@ Protocols and structural subtyping ================================== -Mypy supports two ways of deciding whether two classes are compatible -as types: nominal subtyping and structural subtyping. - -*Nominal* subtyping is strictly based on the class hierarchy. If class ``D`` -inherits class ``C``, it's also a subtype of ``C``, and instances of -``D`` can be used when ``C`` instances are expected. This form of -subtyping is used by default in mypy, since it's easy to understand -and produces clear and concise error messages, and since it matches -how the native :py:func:`isinstance ` check works -- based on class +The Python type system supports two ways of deciding whether two objects are +compatible as types: nominal subtyping and structural subtyping. + +*Nominal* subtyping is strictly based on the class hierarchy. If class ``Dog`` +inherits class ``Animal``, it's a subtype of ``Animal``. Instances of ``Dog`` +can be used when ``Animal`` instances are expected. This form of subtyping +is what Python's type system predominantly uses: it's easy to +understand and produces clear and concise error messages, and matches how the +native :py:func:`isinstance ` check works -- based on class hierarchy. -*Structural* subtyping is based on the operations that can be performed with an object. Class ``D`` is -a structural subtype of class ``C`` if the former has all attributes -and methods of the latter, and with compatible types. +*Structural* subtyping is based on the operations that can be performed with an +object. Class ``Dog`` is a structural subtype of class ``Animal`` if the former +has all attributes and methods of the latter, and with compatible types. -Structural subtyping can be seen as a static equivalent of duck -typing, which is well known to Python programmers. Mypy provides -support for structural subtyping via protocol classes described -below. See :pep:`544` for the detailed specification of protocols -and structural subtyping in Python. +Structural subtyping can be seen as a static equivalent of duck typing, which is +well known to Python programmers. See :pep:`544` for the detailed specification +of protocols and structural subtyping in Python. .. _predefined_protocols: @@ -60,8 +58,7 @@ For example, ``IntList`` below is iterable, over ``int`` values: :ref:`predefined_protocols_reference` lists all protocols defined in :py:mod:`typing` and the signatures of the corresponding methods you need to define -to implement each protocol (the signatures can be left out, as always, but mypy -won't type check unannotated methods). +to implement each protocol. Simple user-defined protocols ***************************** @@ -71,8 +68,7 @@ class: .. code-block:: python - from typing import Iterable - from typing_extensions import Protocol + from typing import Iterable, Protocol class SupportsClose(Protocol): # Empty method body (explicit '...') @@ -89,18 +85,12 @@ class: for item in items: item.close() - close_all([Resource(), open('some/file')]) # Okay! + close_all([Resource(), open('some/file')]) # OK ``Resource`` is a subtype of the ``SupportsClose`` protocol since it defines a compatible ``close`` method. Regular file objects returned by :py:func:`open` are similarly compatible with the protocol, as they support ``close()``. -.. note:: - - The ``Protocol`` base class is provided in the ``typing_extensions`` - package for Python 3.4-3.7. Starting with Python 3.8, ``Protocol`` - is included in the ``typing`` module. - Defining subprotocols and subclassing protocols *********************************************** @@ -171,6 +161,13 @@ abstract: ExplicitSubclass() # error: Cannot instantiate abstract class 'ExplicitSubclass' # with abstract attributes 'attr' and 'method' +Similarly, explicitly assigning to a protocol instance can be a way to ask the +type checker to verify that your class implements a protocol: + +.. code-block:: python + + _proto: SomeProto = cast(ExplicitSubclass, None) + Invariance of protocol attributes ********************************* @@ -228,8 +225,7 @@ such as trees and linked lists: .. code-block:: python - from typing import TypeVar, Optional - from typing_extensions import Protocol + from typing import TypeVar, Optional, Protocol class TreeLike(Protocol): value: int @@ -257,7 +253,7 @@ rudimentary support for runtime structural checks: .. code-block:: python - from typing_extensions import Protocol, runtime_checkable + from typing import Protocol, runtime_checkable @runtime_checkable class Portable(Protocol): @@ -300,8 +296,7 @@ member: .. code-block:: python - from typing import Optional, Iterable - from typing_extensions import Protocol + from typing import Optional, Iterable, Protocol class Combiner(Protocol): def __call__(self, *vals: bytes, maxlen: Optional[int] = None) -> list[bytes]: ... @@ -319,14 +314,13 @@ member: batch_proc([], bad_cb) # Error! Argument 2 has incompatible type because of # different name and kind in the callback -Callback protocols and :py:data:`~typing.Callable` types can be used interchangeably. +Callback protocols and :py:data:`~typing.Callable` types can be used mostly interchangeably. Argument names in :py:meth:`__call__ ` methods must be identical, unless a double underscore prefix is used. For example: .. code-block:: python - from typing import Callable, TypeVar - from typing_extensions import Protocol + from typing import Callable, Protocol, TypeVar T = TypeVar('T') diff --git a/docs/source/running_mypy.rst b/docs/source/running_mypy.rst index b0cefec9dafa..42474ae94c48 100644 --- a/docs/source/running_mypy.rst +++ b/docs/source/running_mypy.rst @@ -305,16 +305,20 @@ not catch errors in its use. The ``.*`` after ``foobar`` will ignore imports of ``foobar`` modules and subpackages in addition to the ``foobar`` top-level package namespace. -3. To suppress *all* missing import errors for *all* libraries in your codebase, - invoke mypy with the :option:`--ignore-missing-imports ` command line flag or set - the :confval:`ignore_missing_imports` - config file option to True - in the *global* section of your mypy config file:: +3. To suppress *all* missing import errors for *all* untyped libraries + in your codebase, use :option:`--disable-error-code=import-untyped `. + See :ref:`code-import-untyped` for more details on this error code. + + You can also set :confval:`disable_error_code`, like so:: [mypy] - ignore_missing_imports = True + disable_error_code = import-untyped + - We recommend using this approach only as a last resort: it's equivalent + You can also set the :option:`--ignore-missing-imports ` + command line flag or set the :confval:`ignore_missing_imports` config file + option to True in the *global* section of your mypy config file. We + recommend avoiding ``--ignore-missing-imports`` if possible: it's equivalent to adding a ``# type: ignore`` to all unresolved imports in your codebase. @@ -387,11 +391,11 @@ this error, try: installing into the environment you expect by running pip like ``python -m pip ...``. -2. Reading the :ref:`finding-imports` section below to make sure you +3. Reading the :ref:`finding-imports` section below to make sure you understand how exactly mypy searches for and finds modules and modify how you're invoking mypy accordingly. -3. Directly specifying the directory containing the module you want to +4. Directly specifying the directory containing the module you want to type check from the command line, by using the :confval:`mypy_path` or :confval:`files` config file options, or by using the ``MYPYPATH`` environment variable. @@ -401,7 +405,7 @@ this error, try: For example, suppose you are trying to add the module ``foo.bar.baz`` which is located at ``~/foo-project/src/foo/bar/baz.py``. In this case, you must run ``mypy ~/foo-project/src`` (or set the ``MYPYPATH`` to - ``~/foo-project/src``. + ``~/foo-project/src``). .. _finding-imports: diff --git a/docs/source/runtime_troubles.rst b/docs/source/runtime_troubles.rst index a62652111de6..66ab7b3a84c7 100644 --- a/docs/source/runtime_troubles.rst +++ b/docs/source/runtime_troubles.rst @@ -86,7 +86,7 @@ required to be valid Python syntax. For more details, see :pep:`563`. * :ref:`type aliases `; * :ref:`type narrowing `; - * type definitions (see :py:class:`~typing.TypeVar`, :py:func:`~typing.NewType`, :py:class:`~typing.NamedTuple`); + * type definitions (see :py:class:`~typing.TypeVar`, :py:class:`~typing.NewType`, :py:class:`~typing.NamedTuple`); * base classes. .. code-block:: python @@ -117,6 +117,8 @@ Since code inside ``if TYPE_CHECKING:`` is not executed at runtime, it provides a convenient way to tell mypy something without the code being evaluated at runtime. This is most useful for resolving :ref:`import cycles `. +.. _forward-references: + Class name forward references ----------------------------- @@ -261,7 +263,7 @@ If your subclass is also generic, you can use the following: reveal_type(task_queue.get()) # Reveals str In Python 3.9, we can just inherit directly from ``Queue[str]`` or ``Queue[T]`` -since its :py:class:`queue.Queue` implements :py:meth:`__class_getitem__`, so +since its :py:class:`queue.Queue` implements :py:meth:`~object.__class_getitem__`, so the class object can be subscripted at runtime without issue. Using types defined in stubs but not at runtime @@ -275,10 +277,18 @@ sections, these can be dealt with by using :ref:`typing.TYPE_CHECKING .. code-block:: python + from __future__ import annotations from typing import TYPE_CHECKING if TYPE_CHECKING: from _typeshed import SupportsRichComparison + def f(x: SupportsRichComparison) -> None + +The ``from __future__ import annotations`` is required to avoid +a ``NameError`` when using the imported symbol. +For more information and caveats, see the section on +:ref:`future annotations `. + .. _generic-builtins: Using generic builtins diff --git a/docs/source/stubgen.rst b/docs/source/stubgen.rst index f06c9c066bb7..c9e52956379a 100644 --- a/docs/source/stubgen.rst +++ b/docs/source/stubgen.rst @@ -127,12 +127,22 @@ alter the default behavior: unwanted side effects, such as the running of tests. Stubgen tries to skip test modules even without this option, but this does not always work. -.. option:: --parse-only +.. option:: --no-analysis Don't perform semantic analysis of source files. This may generate worse stubs -- in particular, some module, class, and function aliases may be represented as variables with the ``Any`` type. This is generally only - useful if semantic analysis causes a critical mypy error. + useful if semantic analysis causes a critical mypy error. Does not apply to + C extension modules. Incompatible with :option:`--inspect-mode`. + +.. option:: --inspect-mode + + Import and inspect modules instead of parsing source code. This is the default + behavior for C modules and pyc-only packages. The flag is useful to force + inspection for pure Python modules that make use of dynamically generated + members that would otherwise be omitted when using the default behavior of + code parsing. Implies :option:`--no-analysis` as analysis requires source + code. .. option:: --doc-dir PATH @@ -163,6 +173,11 @@ Additional flags Instead, only export imported names that are not referenced in the module that contains the import. +.. option:: --include-docstrings + + Include docstrings in stubs. This will add docstrings to Python function and + classes stubs and to C extension function stubs. + .. option:: --search-path PATH Specify module search directories, separated by colons (only used if diff --git a/docs/source/stubs.rst b/docs/source/stubs.rst index 7c84a9718b3e..c0a3f8b88111 100644 --- a/docs/source/stubs.rst +++ b/docs/source/stubs.rst @@ -114,7 +114,7 @@ For example: .. code-block:: python - from typing_extensions import Protocol + from typing import Protocol class Resource(Protocol): def ok_1(self, foo: list[str] = ...) -> None: ... diff --git a/docs/source/stubtest.rst b/docs/source/stubtest.rst index a8279eb6c239..59889252f056 100644 --- a/docs/source/stubtest.rst +++ b/docs/source/stubtest.rst @@ -42,7 +42,7 @@ test Python's official collection of library stubs, `typeshed `_. .. warning:: - + stubtest will import and execute Python code from the packages it checks. Example @@ -69,7 +69,7 @@ Here's a quick example of what stubtest can do: error: library.foo is inconsistent, runtime argument "x" has a default value but stub argument does not Stub: at line 3 def (x: builtins.int) - Runtime: at line 3 in file ~/library.py + Runtime: in file ~/library.py:3 def (x=None) error: library.x variable differs from runtime type Literal['hello, stubtest'] @@ -85,7 +85,7 @@ Usage Running stubtest can be as simple as ``stubtest module_to_check``. Run :option:`stubtest --help` for a quick summary of options. -Subtest must be able to import the code to be checked, so make sure that mypy +Stubtest must be able to import the code to be checked, so make sure that mypy is installed in the same environment as the library to be tested. In some cases, setting ``PYTHONPATH`` can help stubtest find the code to import. @@ -122,14 +122,29 @@ The rest of this section documents the command line interface of stubtest. allowlists. Allowlists can be created with --generate-allowlist. Allowlists support regular expressions. + The presence of an entry in the allowlist means stubtest will not generate + any errors for the corresponding definition. + .. option:: --generate-allowlist Print an allowlist (to stdout) to be used with --allowlist + When introducing stubtest to an existing project, this is an easy way to + silence all existing errors. + .. option:: --ignore-unused-allowlist Ignore unused allowlist entries + Without this option enabled, the default is for stubtest to complain if an + allowlist entry is not necessary for stubtest to pass successfully. + + Note if an allowlist entry is a regex that matches the empty string, + stubtest will never consider it unused. For example, to get + `--ignore-unused-allowlist` behaviour for a single allowlist entry like + ``foo.bar`` you could add an allowlist entry ``(foo\.bar)?``. + This can be useful when an error only occurs on a specific platform. + .. option:: --mypy-config-file FILE Use specified mypy config file to determine mypy plugins and mypy path diff --git a/docs/source/type_narrowing.rst b/docs/source/type_narrowing.rst index 72a816679140..4c5c2851edd0 100644 --- a/docs/source/type_narrowing.rst +++ b/docs/source/type_narrowing.rst @@ -3,7 +3,7 @@ Type narrowing ============== -This section is dedicated to several type narrowing +This section is dedicated to several type narrowing techniques which are supported by mypy. Type narrowing is when you convince a type checker that a broader type is actually more specific, for instance, that an object of type ``Shape`` is actually of the narrower type ``Square``. @@ -14,10 +14,11 @@ Type narrowing expressions The simplest way to narrow a type is to use one of the supported expressions: -- :py:func:`isinstance` like in ``isinstance(obj, float)`` will narrow ``obj`` to have ``float`` type -- :py:func:`issubclass` like in ``issubclass(cls, MyClass)`` will narrow ``cls`` to be ``Type[MyClass]`` -- :py:class:`type` like in ``type(obj) is int`` will narrow ``obj`` to have ``int`` type -- :py:func:`callable` like in ``callable(obj)`` will narrow object to callable type +- :py:func:`isinstance` like in :code:`isinstance(obj, float)` will narrow ``obj`` to have ``float`` type +- :py:func:`issubclass` like in :code:`issubclass(cls, MyClass)` will narrow ``cls`` to be ``Type[MyClass]`` +- :py:class:`type` like in :code:`type(obj) is int` will narrow ``obj`` to have ``int`` type +- :py:func:`callable` like in :code:`callable(obj)` will narrow object to callable type +- :code:`obj is not None` will narrow object to its :ref:`non-optional form ` Type narrowing is contextual. For example, based on the condition, mypy will narrow an expression only within an ``if`` branch: @@ -83,6 +84,7 @@ We can also use ``assert`` to narrow types in the same context: reveal_type(x) # Revealed type is "builtins.int" print(x + '!') # Typechecks with `mypy`, but fails in runtime. + issubclass ~~~~~~~~~~ @@ -271,7 +273,7 @@ Generic TypeGuards else: reveal_type(names) # tuple[str, ...] -Typeguards with parameters +TypeGuards with parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~ Type guard functions can accept extra arguments: @@ -293,7 +295,7 @@ Type guard functions can accept extra arguments: TypeGuards as methods ~~~~~~~~~~~~~~~~~~~~~ - A method can also serve as the ``TypeGuard``: +A method can also serve as a ``TypeGuard``: .. code-block:: python @@ -359,3 +361,33 @@ What happens here? .. note:: The same will work with ``isinstance(x := a, float)`` as well. + +Limitations +----------- + +Mypy's analysis is limited to individual symbols and it will not track +relationships between symbols. For example, in the following code +it's easy to deduce that if :code:`a` is None then :code:`b` must not be, +therefore :code:`a or b` will always be a string, but Mypy will not be able to tell that: + +.. code-block:: python + + def f(a: str | None, b: str | None) -> str: + if a is not None or b is not None: + return a or b # Incompatible return value type (got "str | None", expected "str") + return 'spam' + +Tracking these sort of cross-variable conditions in a type checker would add significant complexity +and performance overhead. + +You can use an ``assert`` to convince the type checker, override it with a :ref:`cast ` +or rewrite the function to be slightly more verbose: + +.. code-block:: python + + def f(a: str | None, b: str | None) -> str: + if a is not None: + return a + elif b is not None: + return b + return 'spam' diff --git a/docs/source/typed_dict.rst b/docs/source/typed_dict.rst index 19a717d7feb7..e5ce2927db4d 100644 --- a/docs/source/typed_dict.rst +++ b/docs/source/typed_dict.rst @@ -25,7 +25,7 @@ dictionary value depends on the key: .. code-block:: python - from typing_extensions import TypedDict + from typing import TypedDict Movie = TypedDict('Movie', {'name': str, 'year': int}) @@ -189,7 +189,7 @@ in Python 3.6 and later: .. code-block:: python - from typing_extensions import TypedDict + from typing import TypedDict # "from typing_extensions" in Python 3.7 and earlier class Movie(TypedDict): name: str diff --git a/misc/analyze_cache.py b/misc/analyze_cache.py index 8b805d8da0bc..33205f5132fc 100644 --- a/misc/analyze_cache.py +++ b/misc/analyze_cache.py @@ -6,8 +6,8 @@ import os import os.path from collections import Counter -from typing import Any, Dict, Iterable -from typing_extensions import Final, TypeAlias as _TypeAlias +from typing import Any, Dict, Final, Iterable +from typing_extensions import TypeAlias as _TypeAlias ROOT: Final = ".mypy_cache/3.5" @@ -62,7 +62,7 @@ def load_json(data_path: str, meta_path: str) -> CacheData: def get_files(root: str) -> Iterable[CacheData]: - for (dirpath, dirnames, filenames) in os.walk(root): + for dirpath, dirnames, filenames in os.walk(root): for filename in filenames: if filename.endswith(".data.json"): meta_filename = filename.replace(".data.json", ".meta.json") diff --git a/misc/async_matrix.py b/misc/async_matrix.py deleted file mode 100644 index d4612dd81799..000000000000 --- a/misc/async_matrix.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python3 -"""Test various combinations of generators/coroutines. - -This was used to cross-check the errors in the test case -testFullCoroutineMatrix in test-data/unit/check-async-await.test. -""" - -from __future__ import annotations - -import sys -from types import coroutine -from typing import Any, Awaitable, Generator, Iterator - -# The various things you might try to use in `await` or `yield from`. - - -def plain_generator() -> Generator[str, None, int]: - yield "a" - return 1 - - -async def plain_coroutine() -> int: - return 1 - - -@coroutine -def decorated_generator() -> Generator[str, None, int]: - yield "a" - return 1 - - -@coroutine -async def decorated_coroutine() -> int: - return 1 - - -class It(Iterator[str]): - stop = False - - def __iter__(self) -> It: - return self - - def __next__(self) -> str: - if self.stop: - raise StopIteration("end") - else: - self.stop = True - return "a" - - -def other_iterator() -> It: - return It() - - -class Aw(Awaitable[int]): - def __await__(self) -> Generator[str, Any, int]: - yield "a" - return 1 - - -def other_coroutine() -> Aw: - return Aw() - - -# The various contexts in which `await` or `yield from` might occur. - - -def plain_host_generator(func) -> Generator[str, None, None]: - yield "a" - x = 0 - f = func() - try: - x = yield from f # noqa: F841 - finally: - try: - f.close() - except AttributeError: - pass - - -async def plain_host_coroutine(func) -> None: - x = 0 - x = await func() # noqa: F841 - - -@coroutine -def decorated_host_generator(func) -> Generator[str, None, None]: - yield "a" - x = 0 - f = func() - try: - x = yield from f # noqa: F841 - finally: - try: - f.close() - except AttributeError: - pass - - -@coroutine -async def decorated_host_coroutine(func) -> None: - x = 0 - x = await func() # noqa: F841 - - -# Main driver. - - -def main() -> None: - verbose = "-v" in sys.argv - for host in [ - plain_host_generator, - plain_host_coroutine, - decorated_host_generator, - decorated_host_coroutine, - ]: - print() - print("==== Host:", host.__name__) - for func in [ - plain_generator, - plain_coroutine, - decorated_generator, - decorated_coroutine, - other_iterator, - other_coroutine, - ]: - print(" ---- Func:", func.__name__) - try: - f = host(func) - for i in range(10): - try: - x = f.send(None) - if verbose: - print(" yield:", x) - except StopIteration as e: - if verbose: - print(" stop:", e.value) - break - else: - if verbose: - print(" ???? still going") - except Exception as e: - print(" error:", repr(e)) - - -# Run main(). - -if __name__ == "__main__": - main() diff --git a/misc/build-debug-python.sh b/misc/build-debug-python.sh index f652d6ad9937..8dd1bff4c9ed 100755 --- a/misc/build-debug-python.sh +++ b/misc/build-debug-python.sh @@ -26,7 +26,7 @@ fi curl -O https://www.python.org/ftp/python/$VERSION/Python-$VERSION.tgz tar zxf Python-$VERSION.tgz cd Python-$VERSION -CPPFLAGS="$CPPFLAGS" LDFLAGS="$LDFLAGS" ./configure CFLAGS="-DPy_DEBUG -DPy_TRACE_REFS -DPYMALLOC_DEBUG" --with-pydebug --prefix=$PREFIX +CPPFLAGS="$CPPFLAGS" LDFLAGS="$LDFLAGS" ./configure CFLAGS="-DPy_DEBUG -DPy_TRACE_REFS -DPYMALLOC_DEBUG" --with-pydebug --prefix=$PREFIX --with-trace-refs make -j4 make install $PREFIX/bin/python3 -m pip install virtualenv diff --git a/misc/cherry-pick-typeshed.py b/misc/cherry-pick-typeshed.py index af08009c2a8f..7e3b8b56e65f 100644 --- a/misc/cherry-pick-typeshed.py +++ b/misc/cherry-pick-typeshed.py @@ -53,6 +53,7 @@ def main() -> None: "--index", "--directory=mypy/typeshed", "--exclude=**/tests/**", + "--exclude=**/test_cases/**", diff_file, ], check=True, diff --git a/misc/convert-cache.py b/misc/convert-cache.py index e5da9c2650d5..2a8a9579c11b 100755 --- a/misc/convert-cache.py +++ b/misc/convert-cache.py @@ -8,6 +8,7 @@ from __future__ import annotations import os +import re import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -36,15 +37,23 @@ def main() -> None: input_dir = args.input_dir output_dir = args.output_dir or input_dir + assert os.path.isdir(output_dir), f"{output_dir} is not a directory" if args.to_sqlite: input: MetadataStore = FilesystemMetadataStore(input_dir) output: MetadataStore = SqliteMetadataStore(output_dir) else: + fnam = os.path.join(input_dir, "cache.db") + msg = f"{fnam} does not exist" + if not re.match(r"[0-9]+\.[0-9]+$", os.path.basename(input_dir)): + msg += f" (are you missing Python version at the end, e.g. {input_dir}/3.11)" + assert os.path.isfile(fnam), msg input, output = SqliteMetadataStore(input_dir), FilesystemMetadataStore(output_dir) for s in input.list_all(): if s.endswith(".json"): - assert output.write(s, input.read(s), input.getmtime(s)), "Failed to write cache file!" + assert output.write( + s, input.read(s), input.getmtime(s) + ), f"Failed to write cache file {s}!" output.commit() diff --git a/misc/dump-ast.py b/misc/dump-ast.py index 6f70bbc8c9ed..7fdf905bae0b 100755 --- a/misc/dump-ast.py +++ b/misc/dump-ast.py @@ -9,7 +9,7 @@ import sys from mypy import defaults -from mypy.errors import CompileError +from mypy.errors import CompileError, Errors from mypy.options import Options from mypy.parse import parse @@ -19,7 +19,7 @@ def dump(fname: str, python_version: tuple[int, int], quiet: bool = False) -> No options.python_version = python_version with open(fname, "rb") as f: s = f.read() - tree = parse(s, fname, None, errors=None, options=options) + tree = parse(s, fname, None, errors=Errors(options), options=options) if not quiet: print(tree) diff --git a/misc/fix_annotate.py b/misc/fix_annotate.py deleted file mode 100644 index 7fffba8a8507..000000000000 --- a/misc/fix_annotate.py +++ /dev/null @@ -1,219 +0,0 @@ -"""Fixer for lib2to3 that inserts mypy annotations into all methods. - -The simplest way to run this is to copy it into lib2to3's "fixes" -subdirectory and then run "2to3 -f annotate" over your files. - -The fixer transforms e.g. - - def foo(self, bar, baz=12): - return bar + baz - -into - - def foo(self, bar, baz=12): - # type: (Any, int) -> Any - return bar + baz - -It does not do type inference but it recognizes some basic default -argument values such as numbers and strings (and assumes their type -implies the argument type). - -It also uses some basic heuristics to decide whether to ignore the -first argument: - - - always if it's named 'self' - - if there's a @classmethod decorator - -Finally, it knows that __init__() is supposed to return None. -""" - -from __future__ import annotations - -import os -import re -from lib2to3.fixer_base import BaseFix -from lib2to3.fixer_util import syms, token, touch_import -from lib2to3.patcomp import compile_pattern -from lib2to3.pytree import Leaf, Node - - -class FixAnnotate(BaseFix): - - # This fixer is compatible with the bottom matcher. - BM_compatible = True - - # This fixer shouldn't run by default. - explicit = True - - # The pattern to match. - PATTERN = """ - funcdef< 'def' name=any parameters< '(' [args=any] ')' > ':' suite=any+ > - """ - - counter = None if not os.getenv("MAXFIXES") else int(os.getenv("MAXFIXES")) - - def transform(self, node, results): - if FixAnnotate.counter is not None: - if FixAnnotate.counter <= 0: - return - suite = results["suite"] - children = suite[0].children - - # NOTE: I've reverse-engineered the structure of the parse tree. - # It's always a list of nodes, the first of which contains the - # entire suite. Its children seem to be: - # - # [0] NEWLINE - # [1] INDENT - # [2...n-2] statements (the first may be a docstring) - # [n-1] DEDENT - # - # Comments before the suite are part of the INDENT's prefix. - # - # "Compact" functions (e.g. "def foo(x, y): return max(x, y)") - # have a different structure that isn't matched by PATTERN. - # - # print('-'*60) - # print(node) - # for i, ch in enumerate(children): - # print(i, repr(ch.prefix), repr(ch)) - # - # Check if there's already an annotation. - for ch in children: - if ch.prefix.lstrip().startswith("# type:"): - return # There's already a # type: comment here; don't change anything. - - # Compute the annotation - annot = self.make_annotation(node, results) - - # Insert '# type: {annot}' comment. - # For reference, see lib2to3/fixes/fix_tuple_params.py in stdlib. - if len(children) >= 2 and children[1].type == token.INDENT: - children[1].prefix = "{}# type: {}\n{}".format( - children[1].value, annot, children[1].prefix - ) - children[1].changed() - if FixAnnotate.counter is not None: - FixAnnotate.counter -= 1 - - # Also add 'from typing import Any' at the top. - if "Any" in annot: - touch_import("typing", "Any", node) - - def make_annotation(self, node, results): - name = results["name"] - assert isinstance(name, Leaf), repr(name) - assert name.type == token.NAME, repr(name) - decorators = self.get_decorators(node) - is_method = self.is_method(node) - if name.value == "__init__" or not self.has_return_exprs(node): - restype = "None" - else: - restype = "Any" - args = results.get("args") - argtypes = [] - if isinstance(args, Node): - children = args.children - elif isinstance(args, Leaf): - children = [args] - else: - children = [] - # Interpret children according to the following grammar: - # (('*'|'**')? NAME ['=' expr] ','?)* - stars = inferred_type = "" - in_default = False - at_start = True - for child in children: - if isinstance(child, Leaf): - if child.value in ("*", "**"): - stars += child.value - elif child.type == token.NAME and not in_default: - if not is_method or not at_start or "staticmethod" in decorators: - inferred_type = "Any" - else: - # Always skip the first argument if it's named 'self'. - # Always skip the first argument of a class method. - if child.value == "self" or "classmethod" in decorators: - pass - else: - inferred_type = "Any" - elif child.value == "=": - in_default = True - elif in_default and child.value != ",": - if child.type == token.NUMBER: - if re.match(r"\d+[lL]?$", child.value): - inferred_type = "int" - else: - inferred_type = "float" # TODO: complex? - elif child.type == token.STRING: - if child.value.startswith(("u", "U")): - inferred_type = "unicode" - else: - inferred_type = "str" - elif child.type == token.NAME and child.value in ("True", "False"): - inferred_type = "bool" - elif child.value == ",": - if inferred_type: - argtypes.append(stars + inferred_type) - # Reset - stars = inferred_type = "" - in_default = False - at_start = False - if inferred_type: - argtypes.append(stars + inferred_type) - return "(" + ", ".join(argtypes) + ") -> " + restype - - # The parse tree has a different shape when there is a single - # decorator vs. when there are multiple decorators. - DECORATED = "decorated< (d=decorator | decorators< dd=decorator+ >) funcdef >" - decorated = compile_pattern(DECORATED) - - def get_decorators(self, node): - """Return a list of decorators found on a function definition. - - This is a list of strings; only simple decorators - (e.g. @staticmethod) are returned. - - If the function is undecorated or only non-simple decorators - are found, return []. - """ - if node.parent is None: - return [] - results = {} - if not self.decorated.match(node.parent, results): - return [] - decorators = results.get("dd") or [results["d"]] - decs = [] - for d in decorators: - for child in d.children: - if isinstance(child, Leaf) and child.type == token.NAME: - decs.append(child.value) - return decs - - def is_method(self, node): - """Return whether the node occurs (directly) inside a class.""" - node = node.parent - while node is not None: - if node.type == syms.classdef: - return True - if node.type == syms.funcdef: - return False - node = node.parent - return False - - RETURN_EXPR = "return_stmt< 'return' any >" - return_expr = compile_pattern(RETURN_EXPR) - - def has_return_exprs(self, node): - """Traverse the tree below node looking for 'return expr'. - - Return True if at least 'return expr' is found, False if not. - (If both 'return' and 'return expr' are found, return True.) - """ - results = {} - if self.return_expr.match(node, results): - return True - return any( - child.type not in (syms.funcdef, syms.classdef) and self.has_return_exprs(child) - for child in node.children - ) diff --git a/misc/gen_blog_post_html.py b/misc/gen_blog_post_html.py new file mode 100644 index 000000000000..7170696d5d09 --- /dev/null +++ b/misc/gen_blog_post_html.py @@ -0,0 +1,171 @@ +"""Converter from CHANGELOG.md (Markdown) to HTML suitable for a mypy blog post. + +How to use: + +1. Write release notes in CHANGELOG.md. +2. Make sure the heading for the next release is of form `## Mypy X.Y`. +2. Run `misc/gen_blog_post_html.py X.Y > target.html`. +4. Manually inspect and tweak the result. + +Notes: + +* There are some fragile assumptions. Double check the output. +""" + +import argparse +import html +import os +import re +import sys + + +def format_lists(h: str) -> str: + a = h.splitlines() + r = [] + i = 0 + bullets = ("- ", "* ", " * ") + while i < len(a): + if a[i].startswith(bullets): + r.append("

    ") + while i < len(a) and a[i].startswith(bullets): + r.append("
  • %s" % a[i][2:].lstrip()) + i += 1 + r.append("
") + else: + r.append(a[i]) + i += 1 + return "\n".join(r) + + +def format_code(h: str) -> str: + a = h.splitlines() + r = [] + i = 0 + while i < len(a): + if a[i].startswith(" ") or a[i].startswith("```"): + indent = a[i].startswith(" ") + if not indent: + i += 1 + r.append("
")
+            while i < len(a) and (
+                (indent and a[i].startswith("    ")) or (not indent and not a[i].startswith("```"))
+            ):
+                # Undo > and <
+                line = a[i].replace(">", ">").replace("<", "<")
+                if not indent:
+                    line = "    " + line
+                r.append(html.escape(line))
+                i += 1
+            r.append("
") + if not indent and a[i].startswith("```"): + i += 1 + else: + r.append(a[i]) + i += 1 + return "\n".join(r) + + +def convert(src: str) -> str: + h = src + + # Replace < and >. + h = re.sub(r"<", "<", h) + h = re.sub(r">", ">", h) + + # Title + h = re.sub(r"^## (Mypy [0-9.]+)", r"

\1 Released

", h, flags=re.MULTILINE) + + # Subheadings + h = re.sub(r"\n#### ([A-Z`].*)\n", r"\n

\1

\n", h) + + # Sub-subheadings + h = re.sub(r"\n\*\*([A-Z_`].*)\*\*\n", r"\n

\1

\n", h) + h = re.sub(r"\n`\*\*([A-Z_`].*)\*\*\n", r"\n

`\1

\n", h) + + # Translate `**` + h = re.sub(r"`\*\*`", "**", h) + + # Paragraphs + h = re.sub(r"\n([A-Z])", r"\n

\1", h) + + # Bullet lists + h = format_lists(h) + + # Code blocks + h = format_code(h) + + # Code fragments + h = re.sub(r"`([^`]+)`", r"\1", h) + + # Remove **** noise + h = re.sub(r"\*\*\*\*", "", h) + + # Bold text + h = re.sub(r"\*\*([A-Za-z].*?)\*\*", r" \1", h) + + # Emphasized text + h = re.sub(r" \*([A-Za-z].*?)\*", r" \1", h) + + # Remove redundant PR links to avoid double links (they will be generated below) + h = re.sub(r"\[(#[0-9]+)\]\(https://github.com/python/mypy/pull/[0-9]+/?\)", r"\1", h) + + # Issue and PR links + h = re.sub(r"\((#[0-9]+)\) +\(([^)]+)\)", r"(\2, \1)", h) + h = re.sub( + r"fixes #([0-9]+)", + r'fixes issue \1', + h, + ) + h = re.sub(r"#([0-9]+)", r'PR \1', h) + h = re.sub(r"\) \(PR", ", PR", h) + + # Markdown links + h = re.sub(r"\[([^]]*)\]\(([^)]*)\)", r'\1', h) + + # Add random links in case they are missing + h = re.sub( + r"contributors to typeshed:", + 'contributors to typeshed:', + h, + ) + + # Add missing top-level HTML tags + h = '\n\n\n' + h + "\n" + + return h + + +def extract_version(src: str, version: str) -> str: + a = src.splitlines() + i = 0 + heading = f"## Mypy {version}" + while i < len(a): + if a[i].strip() == heading: + break + i += 1 + else: + raise RuntimeError(f"Can't find heading {heading!r}") + j = i + 1 + while not a[j].startswith("## "): + j += 1 + return "\n".join(a[i:j]) + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Generate HTML release blog post based on CHANGELOG.md and write to stdout." + ) + parser.add_argument("version", help="mypy version, in form X.Y or X.Y.Z") + args = parser.parse_args() + version: str = args.version + if not re.match(r"[0-9]+(\.[0-9]+)+$", version): + sys.exit(f"error: Version must be of form X.Y or X.Y.Z, not {version!r}") + changelog_path = os.path.join(os.path.dirname(__file__), os.path.pardir, "CHANGELOG.md") + src = open(changelog_path).read() + src = extract_version(src, version) + dst = convert(src) + sys.stdout.write(dst) + + +if __name__ == "__main__": + main() diff --git a/misc/generate_changelog.py b/misc/generate_changelog.py new file mode 100644 index 000000000000..ebab6c569152 --- /dev/null +++ b/misc/generate_changelog.py @@ -0,0 +1,200 @@ +"""Generate the changelog for a mypy release.""" + +from __future__ import annotations + +import argparse +import re +import subprocess +import sys +from dataclasses import dataclass + + +def find_all_release_branches() -> list[tuple[int, int]]: + result = subprocess.run(["git", "branch", "-r"], text=True, capture_output=True, check=True) + versions = [] + for line in result.stdout.splitlines(): + line = line.strip() + if m := re.match(r"origin/release-([0-9]+)\.([0-9]+)$", line): + major = int(m.group(1)) + minor = int(m.group(2)) + versions.append((major, minor)) + return versions + + +def git_merge_base(rev1: str, rev2: str) -> str: + result = subprocess.run( + ["git", "merge-base", rev1, rev2], text=True, capture_output=True, check=True + ) + return result.stdout.strip() + + +@dataclass +class CommitInfo: + commit: str + author: str + title: str + pr_number: int | None + + +def normalize_author(author: str) -> str: + # Some ad-hoc rules to get more consistent author names. + if author == "AlexWaygood": + return "Alex Waygood" + elif author == "jhance": + return "Jared Hance" + return author + + +def git_commit_log(rev1: str, rev2: str) -> list[CommitInfo]: + result = subprocess.run( + ["git", "log", "--pretty=%H\t%an\t%s", f"{rev1}..{rev2}"], + text=True, + capture_output=True, + check=True, + ) + commits = [] + for line in result.stdout.splitlines(): + commit, author, title = line.strip().split("\t", 2) + pr_number = None + if m := re.match(r".*\(#([0-9]+)\) *$", title): + pr_number = int(m.group(1)) + title = re.sub(r" *\(#[0-9]+\) *$", "", title) + + author = normalize_author(author) + entry = CommitInfo(commit, author, title, pr_number) + commits.append(entry) + return commits + + +def filter_omitted_commits(commits: list[CommitInfo]) -> list[CommitInfo]: + result = [] + for c in commits: + title = c.title + keep = True + if title.startswith("Sync typeshed"): + # Typeshed syncs aren't mentioned in release notes + keep = False + if title.startswith( + ( + "Revert sum literal integer change", + "Remove use of LiteralString in builtins", + "Revert typeshed ctypes change", + ) + ): + # These are generated by a typeshed sync. + keep = False + if re.search(r"(bump|update).*version.*\+dev", title.lower()): + # Version number updates aren't mentioned + keep = False + if "pre-commit autoupdate" in title: + keep = False + if title.startswith(("Update commit hashes", "Update hashes")): + # Internal tool change + keep = False + if keep: + result.append(c) + return result + + +def normalize_title(title: str) -> str: + # We sometimes add a title prefix when cherry-picking commits to a + # release branch. Attempt to remove these prefixes so that we can + # match them to the corresponding master branch. + if m := re.match(r"\[release [0-9.]+\] *", title, flags=re.I): + title = title.replace(m.group(0), "") + return title + + +def filter_out_commits_from_old_release_branch( + new_commits: list[CommitInfo], old_commits: list[CommitInfo] +) -> list[CommitInfo]: + old_titles = {normalize_title(commit.title) for commit in old_commits} + result = [] + for commit in new_commits: + drop = False + if normalize_title(commit.title) in old_titles: + drop = True + if normalize_title(f"{commit.title} (#{commit.pr_number})") in old_titles: + drop = True + if not drop: + result.append(commit) + else: + print(f'NOTE: Drop "{commit.title}", since it was in previous release branch') + return result + + +def find_changes_between_releases(old_branch: str, new_branch: str) -> list[CommitInfo]: + merge_base = git_merge_base(old_branch, new_branch) + print(f"Merge base: {merge_base}") + new_commits = git_commit_log(merge_base, new_branch) + old_commits = git_commit_log(merge_base, old_branch) + + # Filter out some commits that won't be mentioned in release notes. + new_commits = filter_omitted_commits(new_commits) + + # Filter out commits cherry-picked to old branch. + new_commits = filter_out_commits_from_old_release_branch(new_commits, old_commits) + + return new_commits + + +def format_changelog_entry(c: CommitInfo) -> str: + """ + s = f" * {c.commit[:9]} - {c.title}" + if c.pr_number: + s += f" (#{c.pr_number})" + s += f" ({c.author})" + """ + s = f" * {c.title} ({c.author}" + if c.pr_number: + s += f", PR [{c.pr_number}](https://github.com/python/mypy/pull/{c.pr_number})" + s += ")" + + return s + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("version", help="target mypy version (form X.Y)") + parser.add_argument("--local", action="store_true") + args = parser.parse_args() + version: str = args.version + local: bool = args.local + + if not re.match(r"[0-9]+\.[0-9]+$", version): + sys.exit(f"error: Release must be of form X.Y (not {version!r})") + major, minor = (int(component) for component in version.split(".")) + + if not local: + print("Running 'git fetch' to fetch all release branches...") + subprocess.run(["git", "fetch"], check=True) + + if minor > 0: + prev_major = major + prev_minor = minor - 1 + else: + # For a x.0 release, the previous release is the most recent (x-1).y release. + all_releases = sorted(find_all_release_branches()) + if (major, minor) not in all_releases: + sys.exit(f"error: Can't find release branch for {major}.{minor} at origin") + for i in reversed(range(len(all_releases))): + if all_releases[i][0] == major - 1: + prev_major, prev_minor = all_releases[i] + break + else: + sys.exit("error: Could not determine previous release") + print(f"Generating changelog for {major}.{minor}") + print(f"Previous release was {prev_major}.{prev_minor}") + + new_branch = f"origin/release-{major}.{minor}" + old_branch = f"origin/release-{prev_major}.{prev_minor}" + + changes = find_changes_between_releases(old_branch, new_branch) + + print() + for c in changes: + print(format_changelog_entry(c)) + + +if __name__ == "__main__": + main() diff --git a/misc/incremental_checker.py b/misc/incremental_checker.py index 85239b6462b8..4e42aef333bb 100755 --- a/misc/incremental_checker.py +++ b/misc/incremental_checker.py @@ -44,8 +44,8 @@ import textwrap import time from argparse import ArgumentParser, Namespace, RawDescriptionHelpFormatter -from typing import Any, Dict -from typing_extensions import Final, TypeAlias as _TypeAlias +from typing import Any, Dict, Final +from typing_extensions import TypeAlias as _TypeAlias CACHE_PATH: Final = ".incremental_checker_cache.json" MYPY_REPO_URL: Final = "https://github.com/python/mypy.git" diff --git a/misc/remove-eol-whitespace.sh b/misc/remove-eol-whitespace.sh deleted file mode 100644 index 3da6b9de64a5..000000000000 --- a/misc/remove-eol-whitespace.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -# Remove trailing whitespace from all non-binary files in a git repo. - -# From https://gist.github.com/dpaluy/3690668; originally from here: -# http://unix.stackexchange.com/questions/36233/how-to-skip-file-in-sed-if-it-contains-regex/36240#36240 - -git grep -I --name-only -z -e '' | xargs -0 sed -i -e 's/[ \t]\+\(\r\?\)$/\1/' diff --git a/misc/sync-typeshed.py b/misc/sync-typeshed.py index 86b0fd774e0c..22023234710e 100644 --- a/misc/sync-typeshed.py +++ b/misc/sync-typeshed.py @@ -11,6 +11,7 @@ import argparse import functools +import glob import os import re import shutil @@ -50,7 +51,9 @@ def update_typeshed(typeshed_dir: str, commit: str | None) -> str: # Remove existing stubs. shutil.rmtree(stdlib_dir) # Copy new stdlib stubs. - shutil.copytree(os.path.join(typeshed_dir, "stdlib"), stdlib_dir) + shutil.copytree( + os.path.join(typeshed_dir, "stdlib"), stdlib_dir, ignore=shutil.ignore_patterns("@tests") + ) shutil.copy(os.path.join(typeshed_dir, "LICENSE"), os.path.join("mypy", "typeshed")) return commit @@ -148,44 +151,66 @@ def main() -> None: if os.environ.get("GITHUB_TOKEN") is None: raise ValueError("GITHUB_TOKEN environment variable must be set") - branch_name = "mypybot/sync-typeshed" - subprocess.run(["git", "checkout", "-B", branch_name, "origin/master"], check=True) - - if not args.typeshed_dir: - # Clone typeshed repo if no directory given. - with tempfile.TemporaryDirectory() as tempdir: - print(f"Cloning typeshed in {tempdir}...") + with tempfile.TemporaryDirectory() as tmpdir: + # Stash patches before checking out a new branch + typeshed_patches = os.path.join("misc", "typeshed_patches") + tmp_patches = os.path.join(tmpdir, "typeshed_patches") + shutil.copytree(typeshed_patches, tmp_patches) + + branch_name = "mypybot/sync-typeshed" + subprocess.run(["git", "checkout", "-B", branch_name, "origin/master"], check=True) + + # Copy the stashed patches back + shutil.rmtree(typeshed_patches, ignore_errors=True) + shutil.copytree(tmp_patches, typeshed_patches) + if subprocess.run(["git", "diff", "--quiet", "--exit-code"], check=False).returncode != 0: + subprocess.run(["git", "commit", "-am", "Update typeshed patches"], check=True) + + if not args.typeshed_dir: + tmp_typeshed = os.path.join(tmpdir, "typeshed") + os.makedirs(tmp_typeshed) + # Clone typeshed repo if no directory given. + print(f"Cloning typeshed in {tmp_typeshed}...") subprocess.run( - ["git", "clone", "https://github.com/python/typeshed.git"], check=True, cwd=tempdir + ["git", "clone", "https://github.com/python/typeshed.git"], + check=True, + cwd=tmp_typeshed, ) - repo = os.path.join(tempdir, "typeshed") + repo = os.path.join(tmp_typeshed, "typeshed") commit = update_typeshed(repo, args.commit) - else: - commit = update_typeshed(args.typeshed_dir, args.commit) + else: + commit = update_typeshed(args.typeshed_dir, args.commit) - assert commit + assert commit - # Create a commit - message = textwrap.dedent( - f"""\ - Sync typeshed + # Create a commit + message = textwrap.dedent( + f"""\ + Sync typeshed - Source commit: - https://github.com/python/typeshed/commit/{commit} - """ - ) - subprocess.run(["git", "add", "--all", os.path.join("mypy", "typeshed")], check=True) - subprocess.run(["git", "commit", "-m", message], check=True) - print("Created typeshed sync commit.") - - commits_to_cherry_pick = [ - "874afd970", # LiteralString reverts - "3a240111e", # sum reverts - "f968d6ce0", # ctypes reverts - ] - for commit in commits_to_cherry_pick: - subprocess.run(["git", "cherry-pick", commit], check=True) - print(f"Cherry-picked {commit}.") + Source commit: + https://github.com/python/typeshed/commit/{commit} + """ + ) + subprocess.run(["git", "add", "--all", os.path.join("mypy", "typeshed")], check=True) + subprocess.run(["git", "commit", "-m", message], check=True) + print("Created typeshed sync commit.") + + patches = sorted(glob.glob(os.path.join(typeshed_patches, "*.patch"))) + for patch in patches: + cmd = ["git", "am", "--3way", patch] + try: + subprocess.run(cmd, check=True) + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"\n\nFailed to apply patch {patch}\n" + "1. Resolve the conflict, `git add --update`, then run `git am --continue`\n" + "2. Run `git format-patch -1 -o misc/typeshed_patches ` " + "to update the patch file.\n" + "3. Re-run sync-typeshed.py" + ) from e + + print(f"Applied patch {patch}") if args.make_pr: subprocess.run(["git", "push", "--force", "origin", branch_name], check=True) diff --git a/misc/test-stubgenc.sh b/misc/test-stubgenc.sh index 7da135f0bf16..ad66722628d8 100755 --- a/misc/test-stubgenc.sh +++ b/misc/test-stubgenc.sh @@ -3,17 +3,43 @@ set -e set -x -cd "$(dirname $0)/.." +cd "$(dirname "$0")/.." # Install dependencies, demo project and mypy python -m pip install -r test-requirements.txt -python -m pip install ./test-data/pybind11_mypy_demo +python -m pip install ./test-data/pybind11_fixtures python -m pip install . -# Remove expected stubs and generate new inplace -STUBGEN_OUTPUT_FOLDER=./test-data/pybind11_mypy_demo/stubgen -rm -rf $STUBGEN_OUTPUT_FOLDER/* -stubgen -p pybind11_mypy_demo -o $STUBGEN_OUTPUT_FOLDER +EXIT=0 -# Compare generated stubs to expected ones -git diff --exit-code $STUBGEN_OUTPUT_FOLDER +# performs the stubgenc test +# first argument is the test result folder +# everything else is passed to stubgen as its arguments +function stubgenc_test() { + # Remove expected stubs and generate new inplace + STUBGEN_OUTPUT_FOLDER=./test-data/pybind11_fixtures/$1 + rm -rf "${STUBGEN_OUTPUT_FOLDER:?}" + + stubgen -o "$STUBGEN_OUTPUT_FOLDER" "${@:2}" + + # Check if generated stubs can actually be type checked by mypy + if ! mypy "$STUBGEN_OUTPUT_FOLDER"; + then + echo "Stubgen test failed, because generated stubs failed to type check." + EXIT=1 + fi + + # Compare generated stubs to expected ones + if ! git diff --exit-code "$STUBGEN_OUTPUT_FOLDER"; + then + echo "Stubgen test failed, because generated stubs differ from expected outputs." + EXIT=1 + fi +} + +# create stubs without docstrings +stubgenc_test expected_stubs_no_docs -p pybind11_fixtures +# create stubs with docstrings +stubgenc_test expected_stubs_with_docs -p pybind11_fixtures --include-docstrings + +exit $EXIT diff --git a/misc/typeshed_patches/0001-Remove-use-of-LiteralString-in-builtins-13743.patch b/misc/typeshed_patches/0001-Remove-use-of-LiteralString-in-builtins-13743.patch new file mode 100644 index 000000000000..6a0977dfc489 --- /dev/null +++ b/misc/typeshed_patches/0001-Remove-use-of-LiteralString-in-builtins-13743.patch @@ -0,0 +1,182 @@ +From 5c00e362d40aa26e0a22a740f05a52d05edf0f91 Mon Sep 17 00:00:00 2001 +From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> +Date: Mon, 26 Sep 2022 12:55:07 -0700 +Subject: [PATCH] Remove use of LiteralString in builtins (#13743) + +--- + mypy/typeshed/stdlib/builtins.pyi | 88 ------------------------------- + 1 file changed, 88 deletions(-) + +diff --git a/mypy/typeshed/stdlib/builtins.pyi b/mypy/typeshed/stdlib/builtins.pyi +index b4765b26c..99919c64c 100644 +--- a/mypy/typeshed/stdlib/builtins.pyi ++++ b/mypy/typeshed/stdlib/builtins.pyi +@@ -61,7 +61,6 @@ from typing import ( # noqa: Y022 + from typing_extensions import ( # noqa: Y023 + Concatenate, + Literal, +- LiteralString, + ParamSpec, + Self, + TypeAlias, +@@ -434,31 +433,16 @@ class str(Sequence[str]): + def __new__(cls, object: object = ...) -> Self: ... + @overload + def __new__(cls, object: ReadableBuffer, encoding: str = ..., errors: str = ...) -> Self: ... +- @overload +- def capitalize(self: LiteralString) -> LiteralString: ... +- @overload + def capitalize(self) -> str: ... # type: ignore[misc] +- @overload +- def casefold(self: LiteralString) -> LiteralString: ... +- @overload + def casefold(self) -> str: ... # type: ignore[misc] +- @overload +- def center(self: LiteralString, width: SupportsIndex, fillchar: LiteralString = " ", /) -> LiteralString: ... +- @overload + def center(self, width: SupportsIndex, fillchar: str = " ", /) -> str: ... # type: ignore[misc] + def count(self, sub: str, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., /) -> int: ... + def encode(self, encoding: str = "utf-8", errors: str = "strict") -> bytes: ... + def endswith( + self, suffix: str | tuple[str, ...], start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., / + ) -> bool: ... +- @overload +- def expandtabs(self: LiteralString, tabsize: SupportsIndex = 8) -> LiteralString: ... +- @overload + def expandtabs(self, tabsize: SupportsIndex = 8) -> str: ... # type: ignore[misc] + def find(self, sub: str, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., /) -> int: ... +- @overload +- def format(self: LiteralString, *args: LiteralString, **kwargs: LiteralString) -> LiteralString: ... +- @overload + def format(self, *args: object, **kwargs: object) -> str: ... + def format_map(self, map: _FormatMapMapping) -> str: ... + def index(self, sub: str, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., /) -> int: ... +@@ -474,89 +458,32 @@ class str(Sequence[str]): + def isspace(self) -> bool: ... + def istitle(self) -> bool: ... + def isupper(self) -> bool: ... +- @overload +- def join(self: LiteralString, iterable: Iterable[LiteralString], /) -> LiteralString: ... +- @overload + def join(self, iterable: Iterable[str], /) -> str: ... # type: ignore[misc] +- @overload +- def ljust(self: LiteralString, width: SupportsIndex, fillchar: LiteralString = " ", /) -> LiteralString: ... +- @overload + def ljust(self, width: SupportsIndex, fillchar: str = " ", /) -> str: ... # type: ignore[misc] +- @overload +- def lower(self: LiteralString) -> LiteralString: ... +- @overload + def lower(self) -> str: ... # type: ignore[misc] +- @overload +- def lstrip(self: LiteralString, chars: LiteralString | None = None, /) -> LiteralString: ... +- @overload + def lstrip(self, chars: str | None = None, /) -> str: ... # type: ignore[misc] +- @overload +- def partition(self: LiteralString, sep: LiteralString, /) -> tuple[LiteralString, LiteralString, LiteralString]: ... +- @overload + def partition(self, sep: str, /) -> tuple[str, str, str]: ... # type: ignore[misc] +- @overload +- def replace(self: LiteralString, old: LiteralString, new: LiteralString, count: SupportsIndex = -1, /) -> LiteralString: ... +- @overload + def replace(self, old: str, new: str, count: SupportsIndex = -1, /) -> str: ... # type: ignore[misc] + if sys.version_info >= (3, 9): +- @overload +- def removeprefix(self: LiteralString, prefix: LiteralString, /) -> LiteralString: ... +- @overload + def removeprefix(self, prefix: str, /) -> str: ... # type: ignore[misc] +- @overload +- def removesuffix(self: LiteralString, suffix: LiteralString, /) -> LiteralString: ... +- @overload + def removesuffix(self, suffix: str, /) -> str: ... # type: ignore[misc] + + def rfind(self, sub: str, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., /) -> int: ... + def rindex(self, sub: str, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., /) -> int: ... +- @overload +- def rjust(self: LiteralString, width: SupportsIndex, fillchar: LiteralString = " ", /) -> LiteralString: ... +- @overload + def rjust(self, width: SupportsIndex, fillchar: str = " ", /) -> str: ... # type: ignore[misc] +- @overload +- def rpartition(self: LiteralString, sep: LiteralString, /) -> tuple[LiteralString, LiteralString, LiteralString]: ... +- @overload + def rpartition(self, sep: str, /) -> tuple[str, str, str]: ... # type: ignore[misc] +- @overload +- def rsplit(self: LiteralString, sep: LiteralString | None = None, maxsplit: SupportsIndex = -1) -> list[LiteralString]: ... +- @overload + def rsplit(self, sep: str | None = None, maxsplit: SupportsIndex = -1) -> list[str]: ... # type: ignore[misc] +- @overload +- def rstrip(self: LiteralString, chars: LiteralString | None = None, /) -> LiteralString: ... +- @overload + def rstrip(self, chars: str | None = None, /) -> str: ... # type: ignore[misc] +- @overload +- def split(self: LiteralString, sep: LiteralString | None = None, maxsplit: SupportsIndex = -1) -> list[LiteralString]: ... +- @overload + def split(self, sep: str | None = None, maxsplit: SupportsIndex = -1) -> list[str]: ... # type: ignore[misc] +- @overload +- def splitlines(self: LiteralString, keepends: bool = False) -> list[LiteralString]: ... +- @overload + def splitlines(self, keepends: bool = False) -> list[str]: ... # type: ignore[misc] + def startswith( + self, prefix: str | tuple[str, ...], start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., / + ) -> bool: ... +- @overload +- def strip(self: LiteralString, chars: LiteralString | None = None, /) -> LiteralString: ... +- @overload + def strip(self, chars: str | None = None, /) -> str: ... # type: ignore[misc] +- @overload +- def swapcase(self: LiteralString) -> LiteralString: ... +- @overload + def swapcase(self) -> str: ... # type: ignore[misc] +- @overload +- def title(self: LiteralString) -> LiteralString: ... +- @overload + def title(self) -> str: ... # type: ignore[misc] + def translate(self, table: _TranslateTable, /) -> str: ... +- @overload +- def upper(self: LiteralString) -> LiteralString: ... +- @overload + def upper(self) -> str: ... # type: ignore[misc] +- @overload +- def zfill(self: LiteralString, width: SupportsIndex, /) -> LiteralString: ... +- @overload + def zfill(self, width: SupportsIndex, /) -> str: ... # type: ignore[misc] + @staticmethod + @overload +@@ -567,9 +494,6 @@ class str(Sequence[str]): + @staticmethod + @overload + def maketrans(x: str, y: str, z: str, /) -> dict[int, int | None]: ... +- @overload +- def __add__(self: LiteralString, value: LiteralString, /) -> LiteralString: ... +- @overload + def __add__(self, value: str, /) -> str: ... # type: ignore[misc] + # Incompatible with Sequence.__contains__ + def __contains__(self, key: str, /) -> bool: ... # type: ignore[override] +@@ -578,25 +502,13 @@ class str(Sequence[str]): + def __getitem__(self, key: SupportsIndex | slice, /) -> str: ... + def __gt__(self, value: str, /) -> bool: ... + def __hash__(self) -> int: ... +- @overload +- def __iter__(self: LiteralString) -> Iterator[LiteralString]: ... +- @overload + def __iter__(self) -> Iterator[str]: ... # type: ignore[misc] + def __le__(self, value: str, /) -> bool: ... + def __len__(self) -> int: ... + def __lt__(self, value: str, /) -> bool: ... +- @overload +- def __mod__(self: LiteralString, value: LiteralString | tuple[LiteralString, ...], /) -> LiteralString: ... +- @overload + def __mod__(self, value: Any, /) -> str: ... +- @overload +- def __mul__(self: LiteralString, value: SupportsIndex, /) -> LiteralString: ... +- @overload + def __mul__(self, value: SupportsIndex, /) -> str: ... # type: ignore[misc] + def __ne__(self, value: object, /) -> bool: ... +- @overload +- def __rmul__(self: LiteralString, value: SupportsIndex, /) -> LiteralString: ... +- @overload + def __rmul__(self, value: SupportsIndex, /) -> str: ... # type: ignore[misc] + def __getnewargs__(self) -> tuple[str]: ... + +-- +2.39.3 (Apple Git-146) + diff --git a/misc/typeshed_patches/0001-Revert-sum-literal-integer-change-13961.patch b/misc/typeshed_patches/0001-Revert-sum-literal-integer-change-13961.patch new file mode 100644 index 000000000000..044e672bfda5 --- /dev/null +++ b/misc/typeshed_patches/0001-Revert-sum-literal-integer-change-13961.patch @@ -0,0 +1,36 @@ +From 44bc98bd50e7170887f0740b53ed95a8eb04f00e Mon Sep 17 00:00:00 2001 +From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> +Date: Sat, 29 Oct 2022 12:47:21 -0700 +Subject: [PATCH] Revert sum literal integer change (#13961) + +This is allegedly causing large performance problems, see 13821 + +typeshed/8231 had zero hits on mypy_primer, so it's not the worst thing +to undo. Patching this in typeshed also feels weird, since there's a +more general soundness issue. If a typevar has a bound or constraint, we +might not want to solve it to a Literal. + +If we can confirm the performance regression or fix the unsoundness +within mypy, I might pursue upstreaming this in typeshed. + +(Reminder: add this to the sync_typeshed script once merged) +--- + mypy/typeshed/stdlib/builtins.pyi | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/mypy/typeshed/stdlib/builtins.pyi b/mypy/typeshed/stdlib/builtins.pyi +index 99919c64c..680cd5561 100644 +--- a/mypy/typeshed/stdlib/builtins.pyi ++++ b/mypy/typeshed/stdlib/builtins.pyi +@@ -1596,7 +1596,7 @@ _SupportsSumNoDefaultT = TypeVar("_SupportsSumNoDefaultT", bound=_SupportsSumWit + # without creating many false-positive errors (see #7578). + # Instead, we special-case the most common examples of this: bool and literal integers. + @overload +-def sum(iterable: Iterable[bool | _LiteralInteger], /, start: int = 0) -> int: ... # type: ignore[overload-overlap] ++def sum(iterable: Iterable[bool], /, start: int = 0) -> int: ... # type: ignore[overload-overlap] + @overload + def sum(iterable: Iterable[_SupportsSumNoDefaultT], /) -> _SupportsSumNoDefaultT | Literal[0]: ... + @overload +-- +2.39.3 (Apple Git-146) + diff --git a/misc/typeshed_patches/0001-Revert-typeshed-ctypes-change.patch b/misc/typeshed_patches/0001-Revert-typeshed-ctypes-change.patch new file mode 100644 index 000000000000..27066bf3c25b --- /dev/null +++ b/misc/typeshed_patches/0001-Revert-typeshed-ctypes-change.patch @@ -0,0 +1,32 @@ +From 61a490091d7c941780919660dc4fdfa88ae6474a Mon Sep 17 00:00:00 2001 +From: AlexWaygood +Date: Mon, 1 May 2023 20:34:55 +0100 +Subject: [PATCH] Revert typeshed ctypes change Since the plugin provides + superior type checking: + https://github.com/python/mypy/pull/13987#issuecomment-1310863427 A manual + cherry-pick of e437cdf. + +--- + mypy/typeshed/stdlib/_ctypes.pyi | 6 +----- + 1 file changed, 1 insertion(+), 5 deletions(-) + +diff --git a/mypy/typeshed/stdlib/_ctypes.pyi b/mypy/typeshed/stdlib/_ctypes.pyi +index 60bbc51d9..cf9cb81a4 100644 +--- a/mypy/typeshed/stdlib/_ctypes.pyi ++++ b/mypy/typeshed/stdlib/_ctypes.pyi +@@ -169,11 +169,7 @@ class Array(_CData, Generic[_CT]): + def _type_(self) -> type[_CT]: ... + @_type_.setter + def _type_(self, value: type[_CT]) -> None: ... +- # Note: only available if _CT == c_char +- @property +- def raw(self) -> bytes: ... +- @raw.setter +- def raw(self, value: ReadableBuffer) -> None: ... ++ raw: bytes # Note: only available if _CT == c_char + value: Any # Note: bytes if _CT == c_char, str if _CT == c_wchar, unavailable otherwise + # TODO These methods cannot be annotated correctly at the moment. + # All of these "Any"s stand for the array's element type, but it's not possible to use _CT +-- +2.39.3 (Apple Git-146) + diff --git a/mypy-requirements.txt b/mypy-requirements.txt index 9a55446eb05a..f81412be761e 100644 --- a/mypy-requirements.txt +++ b/mypy-requirements.txt @@ -1,5 +1,4 @@ # NOTE: this needs to be kept in sync with the "requires" list in pyproject.toml -typing_extensions>=3.10 +typing_extensions>=4.1.0 mypy_extensions>=1.0.0 -typed_ast>=1.4.0,<2; python_version<'3.8' tomli>=1.1.0; python_version<'3.11' diff --git a/mypy/api.py b/mypy/api.py index 589bfbbfa1a7..e2179dba30ca 100644 --- a/mypy/api.py +++ b/mypy/api.py @@ -47,11 +47,10 @@ import sys from io import StringIO -from typing import Callable, TextIO, cast +from typing import Callable, TextIO def _run(main_wrapper: Callable[[TextIO, TextIO], None]) -> tuple[str, str, int]: - stdout = StringIO() stderr = StringIO() @@ -59,7 +58,8 @@ def _run(main_wrapper: Callable[[TextIO, TextIO], None]) -> tuple[str, str, int] main_wrapper(stdout, stderr) exit_status = 0 except SystemExit as system_exit: - exit_status = cast(int, system_exit.code) + assert isinstance(system_exit.code, int) + exit_status = system_exit.code return stdout.getvalue(), stderr.getvalue(), exit_status diff --git a/mypy/applytype.py b/mypy/applytype.py index a81ed3cd1f16..eecd555bf90d 100644 --- a/mypy/applytype.py +++ b/mypy/applytype.py @@ -3,24 +3,23 @@ from typing import Callable, Sequence import mypy.subtypes -from mypy.expandtype import expand_type, expand_unpack_with_variables -from mypy.nodes import ARG_STAR, Context +from mypy.erasetype import erase_typevars +from mypy.expandtype import expand_type +from mypy.nodes import Context from mypy.types import ( AnyType, CallableType, - Parameters, ParamSpecType, PartialType, - TupleType, Type, TypeVarId, TypeVarLikeType, TypeVarTupleType, TypeVarType, + UninhabitedType, UnpackType, get_proper_type, ) -from mypy.typevartuples import find_unpack_in_list, replace_starargs def get_target_type( @@ -31,13 +30,15 @@ def get_target_type( context: Context, skip_unsatisfied: bool, ) -> Type | None: + p_type = get_proper_type(type) + if isinstance(p_type, UninhabitedType) and tvar.has_default(): + return tvar.default if isinstance(tvar, ParamSpecType): return type if isinstance(tvar, TypeVarTupleType): return type assert isinstance(tvar, TypeVarType) values = tvar.values - p_type = get_proper_type(type) if values: if isinstance(p_type, AnyType): return type @@ -62,6 +63,11 @@ def get_target_type( report_incompatible_typevar_value(callable, type, tvar.name, context) else: upper_bound = tvar.upper_bound + if tvar.name == "Self": + # Internally constructed Self-types contain class type variables in upper bound, + # so we need to erase them to avoid false positives. This is safe because we do + # not support type variables in upper bounds of user defined types. + upper_bound = erase_typevars(upper_bound) if not mypy.subtypes.is_subtype(type, upper_bound): if skip_unsatisfied: return None @@ -75,7 +81,6 @@ def apply_generic_arguments( report_incompatible_typevar_value: Callable[[CallableType, Type, str, Context], None], context: Context, skip_unsatisfied: bool = False, - allow_erased_callables: bool = False, ) -> CallableType: """Apply generic type arguments to a callable type. @@ -88,7 +93,8 @@ def apply_generic_arguments( bound or constraints, instead of giving an error. """ tvars = callable.variables - assert len(tvars) == len(orig_types) + min_arg_count = sum(not tv.has_default() for tv in tvars) + assert min_arg_count <= len(orig_types) <= len(tvars) # Check that inferred type variable values are compatible with allowed # values and bounds. Also, promote subtype values to allowed values. # Create a map from type variable id to target type. @@ -105,80 +111,62 @@ def apply_generic_arguments( if target_type is not None: id_to_type[tvar.id] = target_type + # TODO: validate arg_kinds/arg_names for ParamSpec and TypeVarTuple replacements, + # not just type variable bounds above. param_spec = callable.param_spec() if param_spec is not None: nt = id_to_type.get(param_spec.id) if nt is not None: - nt = get_proper_type(nt) - if isinstance(nt, CallableType) or isinstance(nt, Parameters): - callable = callable.expand_param_spec(nt) + # ParamSpec expansion is special-cased, so we need to always expand callable + # as a whole, not expanding arguments individually. + callable = expand_type(callable, id_to_type) + assert isinstance(callable, CallableType) + return callable.copy_modified( + variables=[tv for tv in tvars if tv.id not in id_to_type] + ) # Apply arguments to argument types. var_arg = callable.var_arg() if var_arg is not None and isinstance(var_arg.typ, UnpackType): - star_index = callable.arg_kinds.index(ARG_STAR) - callable = callable.copy_modified( - arg_types=( - [ - expand_type(at, id_to_type, allow_erased_callables) - for at in callable.arg_types[:star_index] - ] - + [callable.arg_types[star_index]] - + [ - expand_type(at, id_to_type, allow_erased_callables) - for at in callable.arg_types[star_index + 1 :] - ] - ) - ) - - unpacked_type = get_proper_type(var_arg.typ.type) - if isinstance(unpacked_type, TupleType): - # Assuming for now that because we convert prefixes to positional arguments, - # the first argument is always an unpack. - expanded_tuple = expand_type(unpacked_type, id_to_type) - if isinstance(expanded_tuple, TupleType): - # TODO: handle the case where the tuple has an unpack. This will - # hit an assert below. - expanded_unpack = find_unpack_in_list(expanded_tuple.items) - if expanded_unpack is not None: - callable = callable.copy_modified( - arg_types=( - callable.arg_types[:star_index] - + [expanded_tuple] - + callable.arg_types[star_index + 1 :] - ) - ) - else: - callable = replace_starargs(callable, expanded_tuple.items) - else: - # TODO: handle the case for if we get a variable length tuple. - assert False, f"mypy bug: unimplemented case, {expanded_tuple}" - elif isinstance(unpacked_type, TypeVarTupleType): - expanded_tvt = expand_unpack_with_variables(var_arg.typ, id_to_type) - assert isinstance(expanded_tvt, list) - for t in expanded_tvt: - assert not isinstance(t, UnpackType) - callable = replace_starargs(callable, expanded_tvt) - else: - assert False, "mypy bug: unhandled case applying unpack" + # Same as for ParamSpec, callable with variadic types needs to be expanded as a whole. + callable = expand_type(callable, id_to_type) + assert isinstance(callable, CallableType) + return callable.copy_modified(variables=[tv for tv in tvars if tv.id not in id_to_type]) else: callable = callable.copy_modified( - arg_types=[ - expand_type(at, id_to_type, allow_erased_callables) for at in callable.arg_types - ] + arg_types=[expand_type(at, id_to_type) for at in callable.arg_types] ) - # Apply arguments to TypeGuard if any. + # Apply arguments to TypeGuard and TypeIs if any. if callable.type_guard is not None: - type_guard = expand_type(callable.type_guard, id_to_type, allow_erased_callables) + type_guard = expand_type(callable.type_guard, id_to_type) else: type_guard = None + if callable.type_is is not None: + type_is = expand_type(callable.type_is, id_to_type) + else: + type_is = None # The callable may retain some type vars if only some were applied. - remaining_tvars = [tv for tv in tvars if tv.id not in id_to_type] + # TODO: move apply_poly() logic from checkexpr.py here when new inference + # becomes universally used (i.e. in all passes + in unification). + # With this new logic we can actually *add* some new free variables. + remaining_tvars: list[TypeVarLikeType] = [] + for tv in tvars: + if tv.id in id_to_type: + continue + if not tv.has_default(): + remaining_tvars.append(tv) + continue + # TypeVarLike isn't in id_to_type mapping. + # Only expand the TypeVar default here. + typ = expand_type(tv, id_to_type) + assert isinstance(typ, TypeVarLikeType) + remaining_tvars.append(typ) return callable.copy_modified( - ret_type=expand_type(callable.ret_type, id_to_type, allow_erased_callables), + ret_type=expand_type(callable.ret_type, id_to_type), variables=remaining_tvars, type_guard=type_guard, + type_is=type_is, ) diff --git a/mypy/argmap.py b/mypy/argmap.py index ec8463fd0625..e6700c9f1092 100644 --- a/mypy/argmap.py +++ b/mypy/argmap.py @@ -14,6 +14,8 @@ Type, TypedDictType, TypeOfAny, + TypeVarTupleType, + UnpackType, get_proper_type, ) @@ -174,6 +176,7 @@ def expand_actual_type( actual_kind: nodes.ArgKind, formal_name: str | None, formal_kind: nodes.ArgKind, + allow_unpack: bool = False, ) -> Type: """Return the actual (caller) type(s) of a formal argument with the given kinds. @@ -189,6 +192,11 @@ def expand_actual_type( original_actual = actual_type actual_type = get_proper_type(actual_type) if actual_kind == nodes.ARG_STAR: + if isinstance(actual_type, TypeVarTupleType): + # This code path is hit when *Ts is passed to a callable and various + # special-handling didn't catch this. The best thing we can do is to use + # the upper bound. + actual_type = get_proper_type(actual_type.upper_bound) if isinstance(actual_type, Instance) and actual_type.args: from mypy.subtypes import is_subtype @@ -209,7 +217,20 @@ def expand_actual_type( self.tuple_index = 1 else: self.tuple_index += 1 - return actual_type.items[self.tuple_index - 1] + item = actual_type.items[self.tuple_index - 1] + if isinstance(item, UnpackType) and not allow_unpack: + # An upack item that doesn't have special handling, use upper bound as above. + unpacked = get_proper_type(item.type) + if isinstance(unpacked, TypeVarTupleType): + fallback = get_proper_type(unpacked.upper_bound) + else: + fallback = unpacked + assert ( + isinstance(fallback, Instance) + and fallback.type.fullname == "builtins.tuple" + ) + item = fallback.args[0] + return item elif isinstance(actual_type, ParamSpecType): # ParamSpec is valid in *args but it can't be unpacked. return actual_type diff --git a/mypy/binder.py b/mypy/binder.py index d822aecec2f3..9d0a33b54bc2 100644 --- a/mypy/binder.py +++ b/mypy/binder.py @@ -12,12 +12,17 @@ from mypy.subtypes import is_same_type, is_subtype from mypy.types import ( AnyType, + Instance, NoneType, PartialType, + ProperType, + TupleType, Type, TypeOfAny, TypeType, UnionType, + UnpackType, + find_unpack_in_list, get_proper_type, ) from mypy.typevars import fill_typevars_with_any @@ -42,15 +47,11 @@ def __init__(self, id: int, conditional_frame: bool = False) -> None: self.types: dict[Key, Type] = {} self.unreachable = False self.conditional_frame = conditional_frame - - # Should be set only if we're entering a frame where it's not - # possible to accurately determine whether or not contained - # statements will be unreachable or not. - # - # Long-term, we should improve mypy to the point where we no longer - # need this field. self.suppress_unreachable_warnings = False + def __repr__(self) -> str: + return f"Frame({self.id}, {self.types}, {self.unreachable}, {self.conditional_frame})" + Assigns = DefaultDict[Expression, List[Tuple[Type, Optional[Type]]]] @@ -63,7 +64,7 @@ class ConditionalTypeBinder: ``` class A: - a = None # type: Union[int, str] + a: Union[int, str] = None x = A() lst = [x] reveal_type(x.a) # Union[int, str] @@ -171,7 +172,6 @@ def is_unreachable(self) -> bool: return any(f.unreachable for f in self.frames) def is_unreachable_warning_suppressed(self) -> bool: - # TODO: See todo in 'is_unreachable' return any(f.suppress_unreachable_warnings for f in self.frames) def cleanse(self, expr: Expression) -> None: @@ -218,6 +218,24 @@ def update_from_options(self, frames: list[Frame]) -> bool: for other in resulting_values[1:]: assert other is not None type = join_simple(self.declarations[key], type, other) + # Try simplifying resulting type for unions involving variadic tuples. + # Technically, everything is still valid without this step, but if we do + # not do this, this may create long unions after exiting an if check like: + # x: tuple[int, ...] + # if len(x) < 10: + # ... + # We want the type of x to be tuple[int, ...] after this block (if it is + # still equivalent to such type). + if isinstance(type, UnionType): + type = collapse_variadic_union(type) + if isinstance(type, ProperType) and isinstance(type, UnionType): + # Simplify away any extra Any's that were added to the declared + # type when popping a frame. + simplified = UnionType.make_union( + [t for t in type.items if not isinstance(get_proper_type(t), AnyType)] + ) + if simplified == self.declarations[key]: + type = simplified if current_value is None or not is_same_type(type, current_value): self._put(key, type) changed = True @@ -273,7 +291,7 @@ def assign_type( self.type_assignments[expr].append((type, declared_type)) return if not isinstance(expr, (IndexExpr, MemberExpr, NameExpr)): - return None + return if not literal(expr): return self.invalidate_dependencies(expr) @@ -446,6 +464,7 @@ def top_frame_context(self) -> Iterator[Frame]: assert len(self.frames) == 1 yield self.push_frame() self.pop_frame(True, 0) + assert len(self.frames) == 1 def get_declaration(expr: BindableExpression) -> Type | None: @@ -457,3 +476,63 @@ def get_declaration(expr: BindableExpression) -> Type | None: elif isinstance(expr.node, TypeInfo): return TypeType(fill_typevars_with_any(expr.node)) return None + + +def collapse_variadic_union(typ: UnionType) -> Type: + """Simplify a union involving variadic tuple if possible. + + This will collapse a type like e.g. + tuple[X, Z] | tuple[X, Y, Z] | tuple[X, Y, Y, *tuple[Y, ...], Z] + back to + tuple[X, *tuple[Y, ...], Z] + which is equivalent, but much simpler form of the same type. + """ + tuple_items = [] + other_items = [] + for t in typ.items: + p_t = get_proper_type(t) + if isinstance(p_t, TupleType): + tuple_items.append(p_t) + else: + other_items.append(t) + if len(tuple_items) <= 1: + # This type cannot be simplified further. + return typ + tuple_items = sorted(tuple_items, key=lambda t: len(t.items)) + first = tuple_items[0] + last = tuple_items[-1] + unpack_index = find_unpack_in_list(last.items) + if unpack_index is None: + return typ + unpack = last.items[unpack_index] + assert isinstance(unpack, UnpackType) + unpacked = get_proper_type(unpack.type) + if not isinstance(unpacked, Instance): + return typ + assert unpacked.type.fullname == "builtins.tuple" + suffix = last.items[unpack_index + 1 :] + + # Check that first item matches the expected pattern and infer prefix. + if len(first.items) < len(suffix): + return typ + if suffix and first.items[-len(suffix) :] != suffix: + return typ + if suffix: + prefix = first.items[: -len(suffix)] + else: + prefix = first.items + + # Check that all middle types match the expected pattern as well. + arg = unpacked.args[0] + for i, it in enumerate(tuple_items[1:-1]): + if it.items != prefix + [arg] * (i + 1) + suffix: + return typ + + # Check the last item (the one with unpack), and choose an appropriate simplified type. + if last.items != prefix + [arg] * (len(typ.items) - 1) + [unpack] + suffix: + return typ + if len(first.items) == 0: + simplified: Type = unpacked.copy_modified() + else: + simplified = TupleType(prefix + [unpack] + suffix, fallback=last.partial_fallback) + return UnionType.make_union([simplified] + other_items) diff --git a/mypy/build.py b/mypy/build.py index a4817d1866c7..3ceb473f0948 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -8,6 +8,7 @@ The function build() is the main interface to this module. """ + # TODO: More consistent terminology, e.g. path/fnam, module/id, state/file from __future__ import annotations @@ -31,22 +32,21 @@ Callable, ClassVar, Dict, - Iterable, + Final, Iterator, Mapping, NamedTuple, NoReturn, Sequence, TextIO, - TypeVar, ) -from typing_extensions import Final, TypeAlias as _TypeAlias - -from mypy_extensions import TypedDict +from typing_extensions import TypeAlias as _TypeAlias, TypedDict import mypy.semanal_main from mypy.checker import TypeChecker +from mypy.error_formatter import OUTPUT_CHOICES, ErrorFormatter from mypy.errors import CompileError, ErrorInfo, Errors, report_internal_error +from mypy.graph_utils import prepare_sccs, strongly_connected_components, topsort from mypy.indirection import TypeIndirectionVisitor from mypy.messages import MessageBuilder from mypy.nodes import Import, ImportAll, ImportBase, ImportFrom, MypyFile, SymbolTable, TypeInfo @@ -57,7 +57,6 @@ DecodeError, decode_python_encoding, get_mypy_comments, - get_top_two_prefixes, hash_digest, is_stub_package_file, is_sub_path, @@ -93,12 +92,7 @@ from mypy.plugins.default import DefaultPlugin from mypy.renaming import LimitedVariableRenameVisitor, VariableRenameVisitor from mypy.stats import dump_type_stats -from mypy.stubinfo import ( - is_legacy_bundled_package, - legacy_bundled_packages, - non_bundled_packages, - stub_package_name, -) +from mypy.stubinfo import legacy_bundled_packages, non_bundled_packages, stub_distribution_name from mypy.types import Type from mypy.typestate import reset_global_state, type_state from mypy.version import __version__ @@ -116,7 +110,10 @@ "types", "typing_extensions", "mypy_extensions", - "_importlib_modulespec", + "_typeshed", + "_collections_abc", + "collections", + "collections.abc", "sys", "abc", } @@ -150,7 +147,7 @@ def build( sources: list[BuildSource], options: Options, alt_lib_path: str | None = None, - flush_errors: Callable[[list[str], bool], None] | None = None, + flush_errors: Callable[[str | None, list[str], bool], None] | None = None, fscache: FileSystemCache | None = None, stdout: TextIO | None = None, stderr: TextIO | None = None, @@ -182,7 +179,9 @@ def build( # fields for callers that want the traditional API. messages = [] - def default_flush_errors(new_messages: list[str], is_serious: bool) -> None: + def default_flush_errors( + filename: str | None, new_messages: list[str], is_serious: bool + ) -> None: messages.extend(new_messages) flush_errors = flush_errors or default_flush_errors @@ -202,7 +201,7 @@ def default_flush_errors(new_messages: list[str], is_serious: bool) -> None: # Patch it up to contain either none or all none of the messages, # depending on whether we are flushing errors. serious = not e.use_stdout - flush_errors(e.messages, serious) + flush_errors(None, e.messages, serious) e.messages = messages raise @@ -211,7 +210,7 @@ def _build( sources: list[BuildSource], options: Options, alt_lib_path: str | None, - flush_errors: Callable[[list[str], bool], None], + flush_errors: Callable[[str | None, list[str], bool], None], fscache: FileSystemCache | None, stdout: TextIO, stderr: TextIO, @@ -235,17 +234,7 @@ def _build( source_set = BuildSourceSet(sources) cached_read = fscache.read - errors = Errors( - options.show_error_context, - options.show_column_numbers, - options.hide_error_codes, - options.pretty, - options.show_error_end, - lambda path: read_py_file(path, cached_read), - options.show_absolute_path, - options.many_errors_threshold, - options, - ) + errors = Errors(options, read_source=lambda path: read_py_file(path, cached_read)) plugin, snapshot = load_plugins(options, errors, stdout, extra_plugins) # Add catch-all .gitignore to cache dir if we created it @@ -265,12 +254,14 @@ def _build( plugin=plugin, plugins_snapshot=snapshot, errors=errors, + error_formatter=None if options.output is None else OUTPUT_CHOICES.get(options.output), flush_errors=flush_errors, fscache=fscache, stdout=stdout, stderr=stderr, ) - manager.trace(repr(options)) + if manager.verbosity() >= 2: + manager.trace(repr(options)) reset_global_state() try: @@ -351,7 +342,9 @@ class CacheMeta(NamedTuple): # Metadata for the fine-grained dependencies file associated with a module. -FgDepMeta = TypedDict("FgDepMeta", {"path": str, "mtime": int}) +class FgDepMeta(TypedDict): + path: str + mtime: int def cache_meta_from_dict(meta: dict[str, Any], data_json: str) -> CacheMeta: @@ -612,10 +605,11 @@ def __init__( plugin: Plugin, plugins_snapshot: dict[str, str], errors: Errors, - flush_errors: Callable[[list[str], bool], None], + flush_errors: Callable[[str | None, list[str], bool], None], fscache: FileSystemCache, stdout: TextIO, stderr: TextIO, + error_formatter: ErrorFormatter | None = None, ) -> None: self.stats: dict[str, Any] = {} # Values are ints or floats self.stdout = stdout @@ -624,6 +618,7 @@ def __init__( self.data_dir = data_dir self.errors = errors self.errors.set_ignore_prefix(ignore_prefix) + self.error_formatter = error_formatter self.search_paths = search_paths self.source_set = source_set self.reports = reports @@ -669,8 +664,6 @@ def __init__( for module in CORE_BUILTIN_MODULES: if options.use_builtins_fixtures: continue - if module == "_importlib_modulespec": - continue path = self.find_module_cache.find_module(module) if not isinstance(path, str): raise CompileError( @@ -694,9 +687,7 @@ def __init__( # for efficient lookup self.shadow_map: dict[str, str] = {} if self.options.shadow_file is not None: - self.shadow_map = { - source_file: shadow_file for (source_file, shadow_file) in self.options.shadow_file - } + self.shadow_map = dict(self.options.shadow_file) # a mapping from each file being typechecked to its possible shadow file self.shadow_equivalence_map: dict[str, str | None] = {} self.plugin = plugin @@ -705,8 +696,8 @@ def __init__( self.quickstart_state = read_quickstart_file(options, self.stdout) # Fine grained targets (module top levels and top level functions) processed by # the semantic analyzer, used only for testing. Currently used only by the new - # semantic analyzer. - self.processed_targets: list[str] = [] + # semantic analyzer. Tuple of module and target name. + self.processed_targets: list[tuple[str, str]] = [] # Missing stub packages encountered. self.missing_stub_packages: set[str] = set() # Cache for mypy ASTs that have completed semantic analysis @@ -845,6 +836,8 @@ def parse_file( Raise CompileError if there is a parse error. """ t0 = time.time() + if ignore_errors: + self.errors.ignored_files.add(path) tree = parse(source, path, id, self.errors, options=options) tree._fullname = id self.add_stats( @@ -919,7 +912,7 @@ def stats_summary(self) -> Mapping[str, object]: def deps_to_json(x: dict[str, set[str]]) -> str: - return json.dumps({k: list(v) for k, v in x.items()}) + return json.dumps({k: list(v) for k, v in x.items()}, separators=(",", ":")) # File for storing metadata about all the fine-grained dependency caches @@ -987,7 +980,7 @@ def write_deps_cache( meta = {"snapshot": meta_snapshot, "deps_meta": fg_deps_meta} - if not metastore.write(DEPS_META_FILE, json.dumps(meta)): + if not metastore.write(DEPS_META_FILE, json.dumps(meta, separators=(",", ":"))): manager.log(f"Error writing fine-grained deps meta JSON file {DEPS_META_FILE}") error = True @@ -1055,7 +1048,8 @@ def generate_deps_for_cache(manager: BuildManager, graph: Graph) -> dict[str, di def write_plugins_snapshot(manager: BuildManager) -> None: """Write snapshot of versions and hashes of currently active plugins.""" - if not manager.metastore.write(PLUGIN_SNAPSHOT_FILE, json.dumps(manager.plugins_snapshot)): + snapshot = json.dumps(manager.plugins_snapshot, separators=(",", ":")) + if not manager.metastore.write(PLUGIN_SNAPSHOT_FILE, snapshot): manager.errors.set_file(_cache_dir_prefix(manager.options), None, manager.options) manager.errors.report(0, 0, "Error writing plugins snapshot", blocker=True) @@ -1129,7 +1123,7 @@ def read_deps_cache(manager: BuildManager, graph: Graph) -> dict[str, FgDepMeta] module_deps_metas = deps_meta["deps_meta"] assert isinstance(module_deps_metas, dict) if not manager.options.skip_cache_mtime_checks: - for id, meta in module_deps_metas.items(): + for meta in module_deps_metas.values(): try: matched = manager.getmtime(meta["path"]) == meta["mtime"] except FileNotFoundError: @@ -1487,7 +1481,7 @@ def validate_meta( if manager.options.debug_cache: meta_str = json.dumps(meta_dict, indent=2, sort_keys=True) else: - meta_str = json.dumps(meta_dict) + meta_str = json.dumps(meta_dict, separators=(",", ":")) meta_json, _, _ = get_cache_names(id, path, manager.options) manager.log( "Updating mtime for {}: file {}, meta {}, mtime {}".format( @@ -1517,7 +1511,7 @@ def json_dumps(obj: Any, debug_cache: bool) -> str: if debug_cache: return json.dumps(obj, indent=2, sort_keys=True) else: - return json.dumps(obj, sort_keys=True) + return json.dumps(obj, sort_keys=True, separators=(",", ":")) def write_cache( @@ -1738,8 +1732,8 @@ def delete_cache(id: str, path: str, manager: BuildManager) -> None: Now we can execute steps A-C from the first section. Finding SCCs for step A shouldn't be hard; there's a recipe here: -http://code.activestate.com/recipes/578507/. There's also a plethora -of topsort recipes, e.g. http://code.activestate.com/recipes/577413/. +https://code.activestate.com/recipes/578507/. There's also a plethora +of topsort recipes, e.g. https://code.activestate.com/recipes/577413/. For single nodes, processing is simple. If the node was cached, we deserialize the cache data and fix up cross-references. Otherwise, we @@ -1920,7 +1914,7 @@ def __init__( self.caller_state = caller_state self.caller_line = caller_line if caller_state: - self.import_context = caller_state.import_context[:] + self.import_context = caller_state.import_context.copy() self.import_context.append((caller_state.xpath, caller_line)) else: self.import_context = [] @@ -2003,7 +1997,7 @@ def __init__( raise ModuleNotFound # Parse the file (and then some) to get the dependencies. - self.parse_file() + self.parse_file(temporary=temporary) self.compute_dependencies() @property @@ -2102,7 +2096,7 @@ def load_tree(self, temporary: bool = False) -> None: self.meta.data_json, self.manager, "Load tree ", "Could not load tree: " ) if data is None: - return None + return t0 = time.time() # TODO: Assert data file wasn't changed. @@ -2121,7 +2115,7 @@ def fix_cross_refs(self) -> None: # Methods for processing modules from source code. - def parse_file(self) -> None: + def parse_file(self, *, temporary: bool = False) -> None: """Parse file and run first pass of semantic analysis. Everything done here is local to the file. Don't depend on imported @@ -2183,8 +2177,8 @@ def parse_file(self) -> None: self.id, self.xpath, source, - self.ignore_all or self.options.ignore_errors, - self.options, + ignore_errors=self.ignore_all or self.options.ignore_errors, + options=self.options, ) else: @@ -2206,12 +2200,14 @@ def parse_file(self) -> None: else: self.early_errors = manager.ast_cache[self.id][1] - modules[self.id] = self.tree + if not temporary: + modules[self.id] = self.tree if not cached: self.semantic_analysis_pass1() - self.check_blockers() + if not temporary: + self.check_blockers() manager.ast_cache[self.id] = (self.tree, self.early_errors) @@ -2245,6 +2241,7 @@ def semantic_analysis_pass1(self) -> None: analyzer = SemanticAnalyzerPreAnalysis() with self.wrap_context(): analyzer.visit_file(self.tree, self.xpath, self.id, options) + self.manager.errors.set_skipped_lines(self.xpath, self.tree.skipped_lines) # TODO: Do this while constructing the AST? self.tree.names = SymbolTable() if not self.tree.is_stub: @@ -2410,6 +2407,12 @@ def finish_passes(self) -> None: manager.report_file(self.tree, self.type_map(), self.options) self.update_fine_grained_deps(self.manager.fg_deps) + + if manager.options.export_ref_info: + write_undocumented_ref_info( + self, manager.metastore, manager.options, self.type_map() + ) + self.free_state() if not manager.options.fine_grained_incremental and not manager.options.preserve_asts: free_tree(self.tree) @@ -2573,7 +2576,10 @@ def dependency_lines(self) -> list[int]: return [self.dep_line_map.get(dep, 1) for dep in self.dependencies + self.suppressed] def generate_unused_ignore_notes(self) -> None: - if self.options.warn_unused_ignores: + if ( + self.options.warn_unused_ignores + or codes.UNUSED_IGNORE in self.options.enabled_error_codes + ) and codes.UNUSED_IGNORE not in self.options.disabled_error_codes: # If this file was initially loaded from the cache, it may have suppressed # dependencies due to imports with ignores on them. We need to generate # those errors to avoid spuriously flagging them as unused ignores. @@ -2634,7 +2640,7 @@ def find_module_and_diagnose( result.endswith(".pyi") # Stubs are always normal and not options.follow_imports_for_stubs # except when they aren't ) - or id in mypy.semanal_main.core_modules # core is always normal + or id in CORE_BUILTIN_MODULES # core is always normal ): follow_imports = "normal" if skip_diagnose: @@ -2661,14 +2667,18 @@ def find_module_and_diagnose( # search path or the module has not been installed. ignore_missing_imports = options.ignore_missing_imports - top_level, second_level = get_top_two_prefixes(id) + + id_components = id.split(".") # Don't honor a global (not per-module) ignore_missing_imports # setting for modules that used to have bundled stubs, as # otherwise updating mypy can silently result in new false # negatives. (Unless there are stubs but they are incomplete.) global_ignore_missing_imports = manager.options.ignore_missing_imports if ( - (is_legacy_bundled_package(top_level) or is_legacy_bundled_package(second_level)) + any( + ".".join(id_components[:i]) in legacy_bundled_packages + for i in range(len(id_components), 0, -1) + ) and global_ignore_missing_imports and not options.ignore_missing_imports_per_module and result is ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED @@ -2776,16 +2786,29 @@ def module_not_found( else: daemon = manager.options.fine_grained_incremental msg, notes = reason.error_message_templates(daemon) - errors.report(line, 0, msg.format(module=target), code=codes.IMPORT) - top_level, second_level = get_top_two_prefixes(target) - if second_level in legacy_bundled_packages or second_level in non_bundled_packages: - top_level = second_level + if reason == ModuleNotFoundReason.NOT_FOUND: + code = codes.IMPORT_NOT_FOUND + elif ( + reason == ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS + or reason == ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED + ): + code = codes.IMPORT_UNTYPED + else: + code = codes.IMPORT + errors.report(line, 0, msg.format(module=target), code=code) + + components = target.split(".") + for i in range(len(components), 0, -1): + module = ".".join(components[:i]) + if module in legacy_bundled_packages or module in non_bundled_packages: + break + for note in notes: if "{stub_dist}" in note: - note = note.format(stub_dist=stub_package_name(top_level)) - errors.report(line, 0, note, severity="note", only_once=True, code=codes.IMPORT) + note = note.format(stub_dist=stub_distribution_name(module)) + errors.report(line, 0, note, severity="note", only_once=True, code=code) if reason is ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED: - manager.missing_stub_packages.add(stub_package_name(top_level)) + manager.missing_stub_packages.add(stub_distribution_name(module)) errors.set_import_context(save_import_context) @@ -2831,10 +2854,14 @@ def skipping_ancestor(manager: BuildManager, id: str, path: str, ancestor_for: S def log_configuration(manager: BuildManager, sources: list[BuildSource]) -> None: """Output useful configuration information to LOG and TRACE""" + config_file = manager.options.config_file + if config_file: + config_file = os.path.abspath(config_file) + manager.log() configuration_vars = [ ("Mypy Version", __version__), - ("Config File", (manager.options.config_file or "Default")), + ("Config File", (config_file or "Default")), ("Configured Executable", manager.options.python_executable or "None"), ("Current Executable", sys.executable), ("Cache Dir", manager.options.cache_dir), @@ -2941,6 +2968,7 @@ def dispatch(sources: list[BuildSource], manager: BuildManager, stdout: TextIO) dump_all_dependencies( manager.modules, manager.all_types, manager.options.python_version, manager.options ) + return graph @@ -3008,7 +3036,7 @@ def dump_graph(graph: Graph, stdout: TextIO | None = None) -> None: if state.path: try: size = os.path.getsize(state.path) - except os.error: + except OSError: pass node.sizes[mod] = size for dep in state.dependencies: @@ -3072,7 +3100,7 @@ def load_graph( manager.errors.report( -1, -1, - "See https://mypy.readthedocs.io/en/stable/running_mypy.html#mapping-file-paths-to-modules " # noqa: E501 + "See https://mypy.readthedocs.io/en/stable/running_mypy.html#mapping-file-paths-to-modules " "for more info", severity="note", ) @@ -3160,7 +3188,7 @@ def load_graph( manager.errors.report( -1, 0, - "See https://mypy.readthedocs.io/en/stable/running_mypy.html#mapping-file-paths-to-modules " # noqa: E501 + "See https://mypy.readthedocs.io/en/stable/running_mypy.html#mapping-file-paths-to-modules " "for more info", severity="note", ) @@ -3293,7 +3321,7 @@ def process_graph(graph: Graph, manager: BuildManager) -> None: manager.trace(f"Queuing {fresh_msg} SCC ({scc_str})") fresh_scc_queue.append(scc) else: - if len(fresh_scc_queue) > 0: + if fresh_scc_queue: manager.log(f"Processing {len(fresh_scc_queue)} queued fresh SCCs") # Defer processing fresh SCCs until we actually run into a stale SCC # and need the earlier modules to be loaded. @@ -3362,7 +3390,7 @@ def order_ascc(graph: Graph, ascc: AbstractSet[str], pri_max: int = PRI_ALL) -> strongly_connected_components() below for a reference. """ if len(ascc) == 1: - return [s for s in ascc] + return list(ascc) pri_spread = set() for id in ascc: state = graph[id] @@ -3439,7 +3467,8 @@ def process_stale_scc(graph: Graph, scc: list[str], manager: BuildManager) -> No for id in stale: graph[id].transitive_error = True for id in stale: - manager.flush_errors(manager.errors.file_messages(graph[id].xpath), False) + errors = manager.errors.file_messages(graph[id].xpath, formatter=manager.error_formatter) + manager.flush_errors(manager.errors.simplify_path(graph[id].xpath), errors, False) graph[id].write_cache() graph[id].mark_as_rechecked() @@ -3461,15 +3490,8 @@ def sorted_components( edges = {id: deps_filtered(graph, vertices, id, pri_max) for id in vertices} sccs = list(strongly_connected_components(vertices, edges)) # Topsort. - sccsmap = {id: frozenset(scc) for scc in sccs for id in scc} - data: dict[AbstractSet[str], set[AbstractSet[str]]] = {} - for scc in sccs: - deps: set[AbstractSet[str]] = set() - for id in scc: - deps.update(sccsmap[x] for x in deps_filtered(graph, vertices, id, pri_max)) - data[frozenset(scc)] = deps res = [] - for ready in topsort(data): + for ready in topsort(prepare_sccs(sccs, edges)): # Sort the sets in ready by reversed smallest State.order. Examples: # # - If ready is [{x}, {y}], x.order == 1, y.order == 2, we get @@ -3494,100 +3516,6 @@ def deps_filtered(graph: Graph, vertices: AbstractSet[str], id: str, pri_max: in ] -def strongly_connected_components( - vertices: AbstractSet[str], edges: dict[str, list[str]] -) -> Iterator[set[str]]: - """Compute Strongly Connected Components of a directed graph. - - Args: - vertices: the labels for the vertices - edges: for each vertex, gives the target vertices of its outgoing edges - - Returns: - An iterator yielding strongly connected components, each - represented as a set of vertices. Each input vertex will occur - exactly once; vertices not part of a SCC are returned as - singleton sets. - - From http://code.activestate.com/recipes/578507/. - """ - identified: set[str] = set() - stack: list[str] = [] - index: dict[str, int] = {} - boundaries: list[int] = [] - - def dfs(v: str) -> Iterator[set[str]]: - index[v] = len(stack) - stack.append(v) - boundaries.append(index[v]) - - for w in edges[v]: - if w not in index: - yield from dfs(w) - elif w not in identified: - while index[w] < boundaries[-1]: - boundaries.pop() - - if boundaries[-1] == index[v]: - boundaries.pop() - scc = set(stack[index[v] :]) - del stack[index[v] :] - identified.update(scc) - yield scc - - for v in vertices: - if v not in index: - yield from dfs(v) - - -T = TypeVar("T") - - -def topsort(data: dict[T, set[T]]) -> Iterable[set[T]]: - """Topological sort. - - Args: - data: A map from vertices to all vertices that it has an edge - connecting it to. NOTE: This data structure - is modified in place -- for normalization purposes, - self-dependencies are removed and entries representing - orphans are added. - - Returns: - An iterator yielding sets of vertices that have an equivalent - ordering. - - Example: - Suppose the input has the following structure: - - {A: {B, C}, B: {D}, C: {D}} - - This is normalized to: - - {A: {B, C}, B: {D}, C: {D}, D: {}} - - The algorithm will yield the following values: - - {D} - {B, C} - {A} - - From http://code.activestate.com/recipes/577413/. - """ - # TODO: Use a faster algorithm? - for k, v in data.items(): - v.discard(k) # Ignore self dependencies. - for item in set.union(*data.values()) - set(data.keys()): - data[item] = set() - while True: - ready = {item for item, dep in data.items() if not dep} - if not ready: - break - yield ready - data = {item: (dep - ready) for item, dep in data.items() if item not in ready} - assert not data, f"A cyclic dependency exists amongst {data!r}" - - def missing_stubs_file(cache_dir: str) -> str: return os.path.join(cache_dir, "missing_stubs") @@ -3616,3 +3544,24 @@ def is_silent_import_module(manager: BuildManager, path: str) -> bool: is_sub_path(path, dir) for dir in manager.search_paths.package_path + manager.search_paths.typeshed_path ) + + +def write_undocumented_ref_info( + state: State, metastore: MetadataStore, options: Options, type_map: dict[Expression, Type] +) -> None: + # This exports some dependency information in a rather ad-hoc fashion, which + # can be helpful for some tools. This is all highly experimental and could be + # removed at any time. + + from mypy.refinfo import get_undocumented_ref_info_json + + if not state.tree: + # We need a full AST for this. + return + + _, data_file, _ = get_cache_names(state.id, state.xpath, options) + ref_info_file = ".".join(data_file.split(".")[:-2]) + ".refs.json" + assert not ref_info_file.startswith(".") + + deps_json = get_undocumented_ref_info_json(state.tree, type_map) + metastore.write(ref_info_file, json.dumps(deps_json, separators=(",", ":"))) diff --git a/mypy/checker.py b/mypy/checker.py index 4bf009f74092..3daf64daaac4 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4,11 +4,12 @@ import itertools from collections import defaultdict -from contextlib import contextmanager, nullcontext +from contextlib import ExitStack, contextmanager from typing import ( AbstractSet, Callable, Dict, + Final, Generic, Iterable, Iterator, @@ -22,11 +23,11 @@ cast, overload, ) -from typing_extensions import Final, TypeAlias as _TypeAlias +from typing_extensions import TypeAlias as _TypeAlias import mypy.checkexpr from mypy import errorcodes as codes, message_registry, nodes, operators -from mypy.binder import ConditionalTypeBinder, get_declaration +from mypy.binder import ConditionalTypeBinder, Frame, get_declaration from mypy.checkmember import ( MemberContext, analyze_decorator_or_funcbase_access, @@ -40,10 +41,9 @@ from mypy.errorcodes import TYPE_VAR, UNUSED_AWAITABLE, UNUSED_COROUTINE, ErrorCode from mypy.errors import Errors, ErrorWatcher, report_internal_error from mypy.expandtype import expand_self_type, expand_type, expand_type_by_instance -from mypy.join import join_types -from mypy.literals import Key, literal, literal_hash +from mypy.literals import Key, extract_var_from_literal_hash, literal, literal_hash from mypy.maptype import map_instance_to_supertype -from mypy.meet import is_overlapping_erased_types, is_overlapping_types +from mypy.meet import is_overlapping_erased_types, is_overlapping_types, meet_types from mypy.message_registry import ErrorMessage from mypy.messages import ( SUGGESTED_TEST_FIXTURES, @@ -131,10 +131,14 @@ Var, WhileStmt, WithStmt, + YieldExpr, is_final_node, ) -from mypy.options import Options +from mypy.operators import flip_ops, int_op_to_method, neg_ops +from mypy.options import PRECISE_TUPLE_TYPES, Options +from mypy.patterns import AsPattern, StarredPattern from mypy.plugin import CheckerPluginInterface, Plugin +from mypy.plugins import dataclasses as dataclasses_plugin from mypy.scope import Scope from mypy.semanal import is_trivial_body, refers_to_fullname, set_callable_name from mypy.semanal_enum import ENUM_BASES, ENUM_SPECIAL_PROPS @@ -142,6 +146,7 @@ from mypy.state import state from mypy.subtypes import ( find_member, + infer_class_variances, is_callable_compatible, is_equivalent, is_more_precise, @@ -151,7 +156,7 @@ restrict_subtype_away, unify_generic_callable, ) -from mypy.traverser import all_return_statements, has_return_statement +from mypy.traverser import TraverserVisitor, all_return_statements, has_return_statement from mypy.treetransform import TransformVisitor from mypy.typeanal import check_for_explicit_any, has_any_from_unimported_type, make_optional_type from mypy.typeops import ( @@ -202,29 +207,32 @@ TypeType, TypeVarId, TypeVarLikeType, + TypeVarTupleType, TypeVarType, UnboundType, UninhabitedType, UnionType, + UnpackType, + find_unpack_in_list, flatten_nested_unions, get_proper_type, get_proper_types, is_literal_type, is_named_instance, - is_optional, - remove_optional, - store_argument_type, - strip_type, ) +from mypy.types_utils import is_overlapping_none, remove_optional, store_argument_type, strip_type from mypy.typetraverser import TypeTraverserVisitor from mypy.typevars import fill_typevars, fill_typevars_with_any, has_no_typevars -from mypy.util import is_dunder, is_sunder, is_typeshed_file +from mypy.util import is_dunder, is_sunder from mypy.visitor import NodeVisitor T = TypeVar("T") DEFAULT_LAST_PASS: Final = 1 # Pass numbers start at 0 +# Maximum length of fixed tuple types inferred when narrowing from variadic tuples. +MAX_PRECISE_TUPLE_SIZE: Final = 8 + DeferredNodeType: _TypeAlias = Union[FuncDef, LambdaExpr, OverloadedFuncDef, Decorator] FineGrainedDeferredNodeType: _TypeAlias = Union[FuncDef, MypyFile, OverloadedFuncDef] @@ -393,7 +401,7 @@ def __init__( self.pass_num = 0 self.current_node_deferred = False self.is_stub = tree.is_stub - self.is_typeshed_stub = is_typeshed_file(options.abs_custom_typeshed_dir, path) + self.is_typeshed_stub = tree.is_typeshed_file(options) self.inferred_attribute_types = None # If True, process function definitions. If False, don't. This is used @@ -419,7 +427,7 @@ def __init__( self.expr_checker = mypy.checkexpr.ExpressionChecker( self, self.msg, self.plugin, per_line_checking_time_ns ) - self.pattern_checker = PatternChecker(self, self.msg, self.plugin) + self.pattern_checker = PatternChecker(self, self.msg, self.plugin, options) @property def type_context(self) -> list[Type | None]: @@ -464,14 +472,14 @@ def check_first_pass(self) -> None: with self.tscope.module_scope(self.tree.fullname): with self.enter_partial_types(), self.binder.top_frame_context(): for d in self.tree.defs: - if ( - self.binder.is_unreachable() - and self.should_report_unreachable_issues() - and not self.is_raising_or_empty(d) - ): - self.msg.unreachable_statement(d) - break - self.accept(d) + if self.binder.is_unreachable(): + if not self.should_report_unreachable_issues(): + break + if not self.is_noop_for_reachability(d): + self.msg.unreachable_statement(d) + break + else: + self.accept(d) assert not self.current_node_deferred @@ -483,7 +491,9 @@ def check_first_pass(self) -> None: "typing.Sequence", [self.named_type("builtins.str")] ) if not is_subtype(all_.type, seq_str): - str_seq_s, all_s = format_type_distinctly(seq_str, all_.type) + str_seq_s, all_s = format_type_distinctly( + seq_str, all_.type, options=self.options + ) self.fail( message_registry.ALL_MUST_BE_SEQ_STR.format(str_seq_s, all_s), all_node ) @@ -517,13 +527,11 @@ def check_second_pass( # print("XXX in pass %d, class %s, function %s" % # (self.pass_num, type_name, node.fullname or node.name)) done.add(node) - with self.tscope.class_scope( - active_typeinfo - ) if active_typeinfo else nullcontext(): - with self.scope.push_class( - active_typeinfo - ) if active_typeinfo else nullcontext(): - self.check_partial(node) + with ExitStack() as stack: + if active_typeinfo: + stack.enter_context(self.tscope.class_scope(active_typeinfo)) + stack.enter_context(self.scope.push_class(active_typeinfo)) + self.check_partial(node) return True def check_partial(self, node: DeferredNodeType | FineGrainedDeferredNodeType) -> None: @@ -623,34 +631,85 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: if not defn.items: # In this case we have already complained about none of these being # valid overloads. - return None + return if len(defn.items) == 1: self.fail(message_registry.MULTIPLE_OVERLOADS_REQUIRED, defn) if defn.is_property: # HACK: Infer the type of the property. - self.visit_decorator(cast(Decorator, defn.items[0])) + assert isinstance(defn.items[0], Decorator) + self.visit_decorator(defn.items[0]) for fdef in defn.items: assert isinstance(fdef, Decorator) - self.check_func_item(fdef.func, name=fdef.func.name, allow_empty=True) + if defn.is_property: + self.check_func_item(fdef.func, name=fdef.func.name, allow_empty=True) + else: + # Perform full check for real overloads to infer type of all decorated + # overload variants. + self.visit_decorator_inner(fdef, allow_empty=True) if fdef.func.abstract_status in (IS_ABSTRACT, IMPLICITLY_ABSTRACT): num_abstract += 1 if num_abstract not in (0, len(defn.items)): self.fail(message_registry.INCONSISTENT_ABSTRACT_OVERLOAD, defn) if defn.impl: defn.impl.accept(self) - if defn.info: - self.check_method_override(defn) - self.check_inplace_operator_method(defn) if not defn.is_property: self.check_overlapping_overloads(defn) - return None + if defn.type is None: + item_types = [] + for item in defn.items: + assert isinstance(item, Decorator) + item_type = self.extract_callable_type(item.var.type, item) + if item_type is not None: + item_types.append(item_type) + if item_types: + defn.type = Overloaded(item_types) + # Check override validity after we analyzed current definition. + if defn.info: + found_method_base_classes = self.check_method_override(defn) + if ( + defn.is_explicit_override + and not found_method_base_classes + and found_method_base_classes is not None + ): + self.msg.no_overridable_method(defn.name, defn) + self.check_explicit_override_decorator(defn, found_method_base_classes, defn.impl) + self.check_inplace_operator_method(defn) + + def extract_callable_type(self, inner_type: Type | None, ctx: Context) -> CallableType | None: + """Get type as seen by an overload item caller.""" + inner_type = get_proper_type(inner_type) + outer_type: CallableType | None = None + if inner_type is not None and not isinstance(inner_type, AnyType): + if isinstance(inner_type, CallableType): + outer_type = inner_type + elif isinstance(inner_type, Instance): + inner_call = get_proper_type( + analyze_member_access( + name="__call__", + typ=inner_type, + context=ctx, + is_lvalue=False, + is_super=False, + is_operator=True, + msg=self.msg, + original_type=inner_type, + chk=self, + ) + ) + if isinstance(inner_call, CallableType): + outer_type = inner_call + if outer_type is None: + self.msg.not_callable(inner_type, ctx) + return outer_type def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: # At this point we should have set the impl already, and all remaining # items are decorators - if self.msg.errors.file in self.msg.errors.ignored_files: + if self.msg.errors.file in self.msg.errors.ignored_files or ( + self.is_typeshed_stub and self.options.test_env + ): # This is a little hacky, however, the quadratic check here is really expensive, this # method has no side effects, so we should skip it if we aren't going to report # anything. In some other places we swallow errors in stubs, but this error is very @@ -669,40 +728,20 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: # This can happen if we've got an overload with a different # decorator or if the implementation is untyped -- we gave up on the types. - inner_type = get_proper_type(inner_type) - if inner_type is not None and not isinstance(inner_type, AnyType): - if isinstance(inner_type, CallableType): - impl_type = inner_type - elif isinstance(inner_type, Instance): - inner_call = get_proper_type( - analyze_member_access( - name="__call__", - typ=inner_type, - context=defn.impl, - is_lvalue=False, - is_super=False, - is_operator=True, - msg=self.msg, - original_type=inner_type, - chk=self, - ) - ) - if isinstance(inner_call, CallableType): - impl_type = inner_call - if impl_type is None: - self.msg.not_callable(inner_type, defn.impl) + impl_type = self.extract_callable_type(inner_type, defn.impl) is_descriptor_get = defn.info and defn.name == "__get__" for i, item in enumerate(defn.items): - # TODO overloads involving decorators assert isinstance(item, Decorator) - sig1 = self.function_type(item.func) - assert isinstance(sig1, CallableType) + sig1 = self.extract_callable_type(item.var.type, item) + if sig1 is None: + continue for j, item2 in enumerate(defn.items[i + 1 :]): assert isinstance(item2, Decorator) - sig2 = self.function_type(item2.func) - assert isinstance(sig2, CallableType) + sig2 = self.extract_callable_type(item2.var.type, item2) + if sig2 is None: + continue if not are_argument_counts_overlapping(sig1, sig2): continue @@ -723,8 +762,10 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: # def foo(x: str) -> str: ... # # See Python 2's map function for a concrete example of this kind of overload. + current_class = self.scope.active_class() + type_vars = current_class.defn.type_vars if current_class else [] with state.strict_optional_set(True): - if is_unsafe_overlapping_overload_signatures(sig1, sig2): + if is_unsafe_overlapping_overload_signatures(sig1, sig2, type_vars): self.msg.overloaded_signatures_overlap(i + 1, i + j + 2, item.func) if impl_type is not None: @@ -759,7 +800,7 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: # Is the overload alternative's arguments subtypes of the implementation's? if not is_callable_compatible( - impl, sig1, is_compat=is_subtype, ignore_return=True + impl, sig1, is_compat=is_subtype, is_proper_subtype=False, ignore_return=True ): self.msg.overloaded_signatures_arg_specific(i + 1, defn.impl) @@ -949,8 +990,9 @@ def get_generator_return_type(self, return_type: Type, is_coroutine: bool) -> Ty # AwaitableGenerator, Generator: tr is args[2]. return return_type.args[2] else: - # Supertype of Generator (Iterator, Iterable, object): tr is any. - return AnyType(TypeOfAny.special_form) + # We have a supertype of Generator (Iterator, Iterable, object) + # Treat `Iterator[X]` as a shorthand for `Generator[X, Any, None]`. + return NoneType() def visit_func_def(self, defn: FuncDef) -> None: if not self.recurse_into_functions: @@ -967,7 +1009,8 @@ def _visit_func_def(self, defn: FuncDef) -> None: # overload, the legality of the override has already # been typechecked, and decorated methods will be # checked when the decorator is. - self.check_method_override(defn) + found_method_base_classes = self.check_method_override(defn) + self.check_explicit_override_decorator(defn, found_method_base_classes) self.check_inplace_operator_method(defn) if defn.original_def: # Override previous definition. @@ -1041,6 +1084,11 @@ def check_func_item( if name == "__exit__": self.check__exit__return_type(defn) + # TODO: the following logic should move to the dataclasses plugin + # https://github.com/python/mypy/issues/15515 + if name == "__post_init__": + if dataclasses_plugin.is_processed_dataclass(defn.info): + dataclasses_plugin.check_post_init(self, defn, defn.info) @contextmanager def enter_attribute_inference_context(self) -> Iterator[None]: @@ -1055,6 +1103,7 @@ def check_func_def( """Type check a function definition.""" # Expand type variables with value restrictions to ordinary types. expanded = self.expand_typevars(defn, typ) + original_typ = typ for item, typ in expanded: old_binder = self.binder self.binder = ConditionalTypeBinder() @@ -1112,6 +1161,12 @@ def check_func_def( message_registry.RETURN_TYPE_CANNOT_BE_CONTRAVARIANT, typ.ret_type ) self.check_unbound_return_typevar(typ) + elif ( + isinstance(original_typ.ret_type, TypeVarType) and original_typ.ret_type.values + ): + # Since type vars with values are expanded, the return type is changed + # to a raw value. This is a hack to get it back. + self.check_unbound_return_typevar(original_typ) # Check that Generator functions have the appropriate return type. if defn.is_generator: @@ -1145,50 +1200,71 @@ def check_func_def( # Push return type. self.return_types.append(typ.ret_type) + with self.scope.push_function(defn): + # We temporary push the definition to get the self type as + # visible from *inside* of this function/method. + ref_type: Type | None = self.scope.active_self_type() + + if typ.type_is: + arg_index = 0 + # For methods and classmethods, we want the second parameter + if ref_type is not None and (not defn.is_static or defn.name == "__new__"): + arg_index = 1 + if arg_index < len(typ.arg_types) and not is_subtype( + typ.type_is, typ.arg_types[arg_index] + ): + self.fail( + message_registry.NARROWED_TYPE_NOT_SUBTYPE.format( + format_type(typ.type_is, self.options), + format_type(typ.arg_types[arg_index], self.options), + ), + item, + ) + # Store argument types. for i in range(len(typ.arg_types)): arg_type = typ.arg_types[i] - with self.scope.push_function(defn): - # We temporary push the definition to get the self type as - # visible from *inside* of this function/method. - ref_type: Type | None = self.scope.active_self_type() if ( isinstance(defn, FuncDef) and ref_type is not None and i == 0 - and not defn.is_static + and (not defn.is_static or defn.name == "__new__") and typ.arg_kinds[0] not in [nodes.ARG_STAR, nodes.ARG_STAR2] ): - isclass = defn.is_class or defn.name in ("__new__", "__init_subclass__") - if isclass: + if defn.is_class or defn.name == "__new__": ref_type = mypy.types.TypeType.make_normalized(ref_type) - erased = get_proper_type(erase_to_bound(arg_type)) - if not is_subtype(ref_type, erased, ignore_type_params=True): - if ( - isinstance(erased, Instance) - and erased.type.is_protocol - or isinstance(erased, TypeType) - and isinstance(erased.item, Instance) - and erased.item.type.is_protocol - ): - # We allow the explicit self-type to be not a supertype of - # the current class if it is a protocol. For such cases - # the consistency check will be performed at call sites. - msg = None - elif typ.arg_names[i] in {"self", "cls"}: - msg = message_registry.ERASED_SELF_TYPE_NOT_SUPERTYPE.format( - erased, ref_type - ) - else: - msg = message_registry.MISSING_OR_INVALID_SELF_TYPE - if msg: - self.fail(msg, defn) + if not is_same_type(arg_type, ref_type): + # This level of erasure matches the one in checkmember.check_self_arg(), + # better keep these two checks consistent. + erased = get_proper_type(erase_typevars(erase_to_bound(arg_type))) + if not is_subtype(ref_type, erased, ignore_type_params=True): + if ( + isinstance(erased, Instance) + and erased.type.is_protocol + or isinstance(erased, TypeType) + and isinstance(erased.item, Instance) + and erased.item.type.is_protocol + ): + # We allow the explicit self-type to be not a supertype of + # the current class if it is a protocol. For such cases + # the consistency check will be performed at call sites. + msg = None + elif typ.arg_names[i] in {"self", "cls"}: + msg = message_registry.ERASED_SELF_TYPE_NOT_SUPERTYPE.format( + erased.str_with_options(self.options), + ref_type.str_with_options(self.options), + ) + else: + msg = message_registry.MISSING_OR_INVALID_SELF_TYPE + if msg: + self.fail(msg, defn) elif isinstance(arg_type, TypeVarType): # Refuse covariant parameter type variables # TODO: check recursively for inner type variables - if arg_type.variance == COVARIANT and defn.name not in ( - "__init__", - "__new__", + if ( + arg_type.variance == COVARIANT + and defn.name not in ("__init__", "__new__", "__post_init__") + and not is_private(defn.name) # private methods are not inherited ): ctx: Context = arg_type if ctx.line < 0: @@ -1203,17 +1279,38 @@ def check_func_def( # Type check body in a new scope. with self.binder.top_frame_context(): + # Copy some type narrowings from an outer function when it seems safe enough + # (i.e. we can't find an assignment that might change the type of the + # variable afterwards). + new_frame: Frame | None = None + for frame in old_binder.frames: + for key, narrowed_type in frame.types.items(): + key_var = extract_var_from_literal_hash(key) + if key_var is not None and not self.is_var_redefined_in_outer_context( + key_var, defn.line + ): + # It seems safe to propagate the type narrowing to a nested scope. + if new_frame is None: + new_frame = self.binder.push_frame() + new_frame.types[key] = narrowed_type + self.binder.declarations[key] = old_binder.declarations[key] with self.scope.push_function(defn): - # We suppress reachability warnings when we use TypeVars with value + # We suppress reachability warnings for empty generator functions + # (return; yield) which have a "yield" that's unreachable by definition + # since it's only there to promote the function into a generator function. + # + # We also suppress reachability warnings when we use TypeVars with value # restrictions: we only want to report a warning if a certain statement is # marked as being suppressed in *all* of the expansions, but we currently # have no good way of doing this. # # TODO: Find a way of working around this limitation - if len(expanded) >= 2: + if _is_empty_generator_function(item) or len(expanded) >= 2: self.binder.suppress_unreachable_warnings() self.accept(item.body) unreachable = self.binder.is_unreachable() + if new_frame is not None: + self.binder.pop_frame(True, 0) if not unreachable: if defn.is_generator or is_named_instance( @@ -1306,6 +1403,23 @@ def check_func_def( self.binder = old_binder + def is_var_redefined_in_outer_context(self, v: Var, after_line: int) -> bool: + """Can the variable be assigned to at module top level or outer function? + + Note that this doesn't do a full CFG analysis but uses a line number based + heuristic that isn't correct in some (rare) cases. + """ + outers = self.tscope.outer_functions() + if not outers: + # Top-level function -- outer context is top level, and we can't reason about + # globals + return True + for outer in outers: + if isinstance(outer, FuncDef): + if find_last_var_assignment_line(outer.body, v) >= after_line: + return True + return False + def check_unbound_return_typevar(self, typ: CallableType) -> None: """Fails when the return typevar is not defined in arguments.""" if isinstance(typ.ret_type, TypeVarType) and typ.ret_type in typ.variables: @@ -1322,7 +1436,7 @@ def check_unbound_return_typevar(self, typ: CallableType) -> None: ): self.note( "Consider using the upper bound " - f"{format_type(typ.ret_type.upper_bound)} instead", + f"{format_type(typ.ret_type.upper_bound, self.options)} instead", context=typ.ret_type, ) @@ -1429,7 +1543,9 @@ def check___new___signature(self, fdef: FuncDef, typ: CallableType) -> None: get_proper_type(bound_type.ret_type), (AnyType, Instance, TupleType, UninhabitedType) ): self.fail( - message_registry.NON_INSTANCE_NEW_TYPE.format(format_type(bound_type.ret_type)), + message_registry.NON_INSTANCE_NEW_TYPE.format( + format_type(bound_type.ret_type, self.options) + ), fdef, ) else: @@ -1501,9 +1617,20 @@ def check_reverse_op_method( opt_meta = item.type.metaclass_type if opt_meta is not None: forward_inst = opt_meta + + def has_readable_member(typ: UnionType | Instance, name: str) -> bool: + # TODO: Deal with attributes of TupleType etc. + if isinstance(typ, Instance): + return typ.type.has_readable_member(name) + return all( + (isinstance(x, UnionType) and has_readable_member(x, name)) + or (isinstance(x, Instance) and x.type.has_readable_member(name)) + for x in get_proper_types(typ.relevant_items()) + ) + if not ( isinstance(forward_inst, (Instance, UnionType)) - and forward_inst.has_readable_member(forward_name) + and has_readable_member(forward_inst, forward_name) ): return forward_base = reverse_type.arg_types[1] @@ -1628,7 +1755,9 @@ def is_unsafe_overlapping_op( first = forward_tweaked second = reverse_tweaked - return is_unsafe_overlapping_overload_signatures(first, second) + current_class = self.scope.active_class() + type_vars = current_class.defn.type_vars if current_class else [] + return is_unsafe_overlapping_overload_signatures(first, second, type_vars) def check_inplace_operator_method(self, defn: FuncBase) -> None: """Check an inplace operator method such as __iadd__. @@ -1724,7 +1853,7 @@ def check_match_args(self, var: Var, typ: Type, context: Context) -> None: return typ = get_proper_type(typ) if not isinstance(typ, TupleType) or not all( - [is_string_literal(item) for item in typ.items] + is_string_literal(item) for item in typ.items ): self.msg.note( "__match_args__ must be a tuple containing string literals for checking " @@ -1742,7 +1871,6 @@ def expand_typevars( if defn.info: # Class type variables tvars += defn.info.defn.type_vars or [] - # TODO(PEP612): audit for paramspec for tvar in tvars: if isinstance(tvar, TypeVarType) and tvar.values: subst.append([(tvar.id, value) for value in tvar.values]) @@ -1753,48 +1881,77 @@ def expand_typevars( result: list[tuple[FuncItem, CallableType]] = [] for substitutions in itertools.product(*subst): mapping = dict(substitutions) - expanded = cast(CallableType, expand_type(typ, mapping)) - result.append((expand_func(defn, mapping), expanded)) + result.append((expand_func(defn, mapping), expand_type(typ, mapping))) return result else: return [(defn, typ)] - def check_method_override(self, defn: FuncDef | OverloadedFuncDef | Decorator) -> None: + def check_explicit_override_decorator( + self, + defn: FuncDef | OverloadedFuncDef, + found_method_base_classes: list[TypeInfo] | None, + context: Context | None = None, + ) -> None: + if ( + found_method_base_classes + and not defn.is_explicit_override + and defn.name not in ("__init__", "__new__") + and not is_private(defn.name) + ): + self.msg.explicit_override_decorator_missing( + defn.name, found_method_base_classes[0].fullname, context or defn + ) + + def check_method_override( + self, defn: FuncDef | OverloadedFuncDef | Decorator + ) -> list[TypeInfo] | None: """Check if function definition is compatible with base classes. This may defer the method if a signature is not available in at least one base class. + Return ``None`` if that happens. + + Return a list of base classes which contain an attribute with the method name. """ # Check against definitions in base classes. + found_method_base_classes: list[TypeInfo] = [] for base in defn.info.mro[1:]: - if self.check_method_or_accessor_override_for_base(defn, base): + result = self.check_method_or_accessor_override_for_base(defn, base) + if result is None: # Node was deferred, we will have another attempt later. - return + return None + if result: + found_method_base_classes.append(base) + return found_method_base_classes def check_method_or_accessor_override_for_base( self, defn: FuncDef | OverloadedFuncDef | Decorator, base: TypeInfo - ) -> bool: + ) -> bool | None: """Check if method definition is compatible with a base class. - Return True if the node was deferred because one of the corresponding + Return ``None`` if the node was deferred because one of the corresponding superclass nodes is not ready. + + Return ``True`` if an attribute with the method name was found in the base class. """ + found_base_method = False if base: name = defn.name base_attr = base.names.get(name) if base_attr: # First, check if we override a final (always an error, even with Any types). - if is_final_node(base_attr.node): + if is_final_node(base_attr.node) and not is_private(name): self.msg.cant_override_final(name, base.name, defn) # Second, final can't override anything writeable independently of types. if defn.is_final: self.check_if_final_var_override_writable(name, base_attr.node, defn) + found_base_method = True # Check the type of override. - if name not in ("__init__", "__new__", "__init_subclass__"): + if name not in ("__init__", "__new__", "__init_subclass__", "__post_init__"): # Check method override # (__init__, __new__, __init_subclass__ are special). if self.check_method_override_for_base_with_name(defn, name, base): - return True + return None if name in operators.inplace_operator_methods: # Figure out the name of the corresponding operator method. method = "__" + name[3:] @@ -1802,8 +1959,9 @@ def check_method_or_accessor_override_for_base( # always introduced safely if a base class defined __add__. # TODO can't come up with an example where this is # necessary; now it's "just in case" - return self.check_method_override_for_base_with_name(defn, method, base) - return False + if self.check_method_override_for_base_with_name(defn, method, base): + return None + return found_base_method def check_method_override_for_base_with_name( self, defn: FuncDef | OverloadedFuncDef | Decorator, name: str, base: TypeInfo @@ -1876,49 +2034,38 @@ def check_method_override_for_base_with_name( original_class_or_static = False # a variable can't be class or static if isinstance(original_type, FunctionLike): - active_self_type = self.scope.active_self_type() - if isinstance(original_type, Overloaded) and active_self_type: - # If we have an overload, filter to overloads that match the self type. - # This avoids false positives for concrete subclasses of generic classes, - # see testSelfTypeOverrideCompatibility for an example. - # It's possible we might want to do this as part of bind_and_map_method - filtered_items = [ - item - for item in original_type.items - if not item.arg_types or is_subtype(active_self_type, item.arg_types[0]) - ] - # If we don't have any filtered_items, maybe it's always a valid override - # of the superclass? However if you get to that point you're in murky type - # territory anyway, so we just preserve the type and have the behaviour match - # that of older versions of mypy. - if filtered_items: - original_type = Overloaded(filtered_items) original_type = self.bind_and_map_method(base_attr, original_type, defn.info, base) if original_node and is_property(original_node): original_type = get_property_type(original_type) - if isinstance(typ, FunctionLike) and is_property(defn): - typ = get_property_type(typ) - if ( - isinstance(original_node, Var) - and not original_node.is_final - and (not original_node.is_property or original_node.is_settable_property) - and isinstance(defn, Decorator) - ): - # We only give an error where no other similar errors will be given. - if not isinstance(original_type, AnyType): - self.msg.fail( - "Cannot override writeable attribute with read-only property", - # Give an error on function line to match old behaviour. - defn.func, - code=codes.OVERRIDE, - ) + if is_property(defn): + inner: FunctionLike | None + if isinstance(typ, FunctionLike): + inner = typ + else: + inner = self.extract_callable_type(typ, context) + if inner is not None: + typ = inner + typ = get_property_type(typ) + if ( + isinstance(original_node, Var) + and not original_node.is_final + and (not original_node.is_property or original_node.is_settable_property) + and isinstance(defn, Decorator) + ): + # We only give an error where no other similar errors will be given. + if not isinstance(original_type, AnyType): + self.msg.fail( + "Cannot override writeable attribute with read-only property", + # Give an error on function line to match old behaviour. + defn.func, + code=codes.OVERRIDE, + ) if isinstance(original_type, AnyType) or isinstance(typ, AnyType): pass elif isinstance(original_type, FunctionLike) and isinstance(typ, FunctionLike): # Check that the types are compatible. - # TODO overloaded signatures self.check_override( typ, original_type, @@ -1933,7 +2080,6 @@ def check_method_override_for_base_with_name( # Assume invariance for a non-callable attribute here. Note # that this doesn't affect read-only properties which can have # covariant overrides. - # pass elif ( original_node @@ -1943,7 +2089,9 @@ def check_method_override_for_base_with_name( # If the attribute is read-only, allow covariance pass else: - self.msg.signature_incompatible_with_supertype(defn.name, name, base.name, context) + self.msg.signature_incompatible_with_supertype( + defn.name, name, base.name, context, original=original_type, override=typ + ) return False def bind_and_map_method( @@ -1964,10 +2112,32 @@ def bind_and_map_method( is_class_method = sym.node.func.is_class else: is_class_method = sym.node.is_class - bound = bind_self(typ, self.scope.active_self_type(), is_class_method) + + mapped_typ = cast(FunctionLike, map_type_from_supertype(typ, sub_info, super_info)) + active_self_type = self.scope.active_self_type() + if isinstance(mapped_typ, Overloaded) and active_self_type: + # If we have an overload, filter to overloads that match the self type. + # This avoids false positives for concrete subclasses of generic classes, + # see testSelfTypeOverrideCompatibility for an example. + filtered_items = [] + for item in mapped_typ.items: + if not item.arg_types: + filtered_items.append(item) + item_arg = item.arg_types[0] + if isinstance(item_arg, TypeVarType): + item_arg = item_arg.upper_bound + if is_subtype(active_self_type, item_arg): + filtered_items.append(item) + # If we don't have any filtered_items, maybe it's always a valid override + # of the superclass? However if you get to that point you're in murky type + # territory anyway, so we just preserve the type and have the behaviour match + # that of older versions of mypy. + if filtered_items: + mapped_typ = Overloaded(filtered_items) + + return bind_self(mapped_typ, active_self_type, is_class_method) else: - bound = typ - return cast(FunctionLike, map_type_from_supertype(bound, sub_info, super_info)) + return cast(FunctionLike, map_type_from_supertype(typ, sub_info, super_info)) def get_op_other_domain(self, tp: FunctionLike) -> Type | None: if isinstance(tp, CallableType): @@ -1997,11 +2167,18 @@ def check_override( """Check a method override with given signatures. Arguments: - override: The signature of the overriding method. - original: The signature of the original supertype method. - name: The name of the subtype. This and the next argument are - only used for generating error messages. - supertype: The name of the supertype. + override: The signature of the overriding method. + original: The signature of the original supertype method. + name: The name of the overriding method. + Used primarily for generating error messages. + name_in_super: The name of the overridden in the superclass. + Used for generating error messages only. + supertype: The name of the supertype. + original_class_or_static: Indicates whether the original method (from the superclass) + is either a class method or a static method. + override_class_or_static: Indicates whether the overriding method (from the subclass) + is either a class method or a static method. + node: Context node. """ # Use boolean variable to clarify code. fail = False @@ -2026,6 +2203,8 @@ def check_override( elif isinstance(original, CallableType) and isinstance(override, CallableType): if original.type_guard is not None and override.type_guard is None: fail = True + if original.type_is is not None and override.type_is is None: + fail = True if is_private(name): fail = False @@ -2067,7 +2246,6 @@ def erase_override(t: Type) -> Type: if not is_subtype( original.arg_types[i], erase_override(override.arg_types[i]) ): - arg_type_in_super = original.arg_types[i] if isinstance(node, FuncDef): @@ -2197,7 +2375,7 @@ def visit_class_def(self, defn: ClassDef) -> None: self.allow_abstract_call = old_allow_abstract_call # TODO: Apply the sig to the actual TypeInfo so we can handle decorators # that completely swap out the type. (e.g. Callable[[Type[A]], Type[B]]) - if typ.defn.type_vars: + if typ.defn.type_vars and typ.defn.type_args is None: for base_inst in typ.bases: for base_tvar, base_decl_tvar in zip( base_inst.args, base_inst.type.defn.type_vars @@ -2219,6 +2397,7 @@ def visit_class_def(self, defn: ClassDef) -> None: self.check_protocol_variance(defn) if not defn.has_incompatible_baseclass and defn.info.is_enum: self.check_enum(defn) + infer_class_variances(defn.info) def check_final_deletable(self, typ: TypeInfo) -> None: # These checks are only for mypyc. Only perform some checks that are easier @@ -2347,7 +2526,10 @@ class Baz(int, Foo, Bar, enum.Flag): ... enum_base = base continue elif enum_base is not None and not base.type.is_enum: - self.fail(f'No non-enum mixin classes are allowed after "{enum_base}"', defn) + self.fail( + f'No non-enum mixin classes are allowed after "{enum_base.str_with_options(self.options)}"', + defn, + ) break def check_enum_new(self, defn: ClassDef) -> None: @@ -2372,7 +2554,7 @@ def has_new_method(info: TypeInfo) -> bool: if candidate and has_new: self.fail( "Only a single data type mixin is allowed for Enum subtypes, " - 'found extra "{}"'.format(base), + 'found extra "{}"'.format(base.str_with_options(self.options)), defn, ) elif candidate: @@ -2386,10 +2568,16 @@ def check_protocol_variance(self, defn: ClassDef) -> None: if they are actually covariant/contravariant, since this may break transitivity of subtyping, see PEP 544. """ + if defn.type_args is not None: + # Using new-style syntax (PEP 695), so variance will be inferred + return info = defn.info object_type = Instance(info.mro[-1], []) tvars = info.defn.type_vars for i, tvar in enumerate(tvars): + if not isinstance(tvar, TypeVarType): + # Variance of TypeVarTuple and ParamSpec is underspecified by PEPs. + continue up_args: list[Type] = [ object_type if i == j else AnyType(TypeOfAny.special_form) for j, _ in enumerate(tvars) @@ -2406,7 +2594,7 @@ def check_protocol_variance(self, defn: ClassDef) -> None: expected = CONTRAVARIANT else: expected = INVARIANT - if isinstance(tvar, TypeVarType) and expected != tvar.variance: + if expected != tvar.variance: self.msg.bad_proto_variance(tvar.variance, tvar.name, expected, defn) def check_multiple_inheritance(self, typ: TypeInfo) -> None: @@ -2484,7 +2672,17 @@ class C(B, A[int]): ... # this is unsafe because... first_type = get_proper_type(self.determine_type_of_member(first)) second_type = get_proper_type(self.determine_type_of_member(second)) - if isinstance(first_type, FunctionLike) and isinstance(second_type, FunctionLike): + # TODO: use more principled logic to decide is_subtype() vs is_equivalent(). + # We should rely on mutability of superclass node, not on types being Callable. + + # start with the special case that Instance can be a subtype of FunctionLike + call = None + if isinstance(first_type, Instance): + call = find_member("__call__", first_type, first_type, is_operator=True) + if call and isinstance(second_type, FunctionLike): + second_sig = self.bind_and_map_method(second, second_type, ctx, base2) + ok = is_subtype(call, second_sig, ignore_pos_arg_names=True) + elif isinstance(first_type, FunctionLike) and isinstance(second_type, FunctionLike): if first_type.is_type_obj() and second_type.is_type_obj(): # For class objects only check the subtype relationship of the classes, # since we allow incompatible overrides of '__init__'/'__new__' @@ -2520,7 +2718,7 @@ class C(B, A[int]): ... # this is unsafe because... ok = True # Final attributes can never be overridden, but can override # non-final read-only attributes. - if is_final_node(second.node): + if is_final_node(second.node) and not is_private(name): self.msg.cant_override_final(name, base2.name, ctx) if is_final_node(first.node): self.check_if_final_var_override_writable(name, second.node, ctx) @@ -2576,9 +2774,8 @@ def check_import(self, node: ImportBase) -> None: if lvalue_type is None: # TODO: This is broken. lvalue_type = AnyType(TypeOfAny.special_form) - message = message_registry.INCOMPATIBLE_IMPORT_OF.format( - cast(NameExpr, assign.rvalue).name - ) + assert isinstance(assign.rvalue, NameExpr) + message = message_registry.INCOMPATIBLE_IMPORT_OF.format(assign.rvalue.name) self.check_simple_assignment( lvalue_type, assign.rvalue, @@ -2601,10 +2798,13 @@ def visit_block(self, b: Block) -> None: return for s in b.body: if self.binder.is_unreachable(): - if self.should_report_unreachable_issues() and not self.is_raising_or_empty(s): + if not self.should_report_unreachable_issues(): + break + if not self.is_noop_for_reachability(s): self.msg.unreachable_statement(s) - break - self.accept(s) + break + else: + self.accept(s) def should_report_unreachable_issues(self) -> bool: return ( @@ -2614,11 +2814,11 @@ def should_report_unreachable_issues(self) -> bool: and not self.binder.is_unreachable_warning_suppressed() ) - def is_raising_or_empty(self, s: Statement) -> bool: + def is_noop_for_reachability(self, s: Statement) -> bool: """Returns 'true' if the given statement either throws an error of some kind or is a no-op. - We use this function mostly while handling the '--warn-unreachable' flag. When + We use this function while handling the '--warn-unreachable' flag. When that flag is present, we normally report an error on any unreachable statement. But if that statement is just something like a 'pass' or a just-in-case 'assert False', reporting an error would be annoying. @@ -2706,7 +2906,7 @@ def check_assignment( new_syntax: bool = False, ) -> None: """Type check a single assignment: lvalue = rvalue.""" - if isinstance(lvalue, TupleExpr) or isinstance(lvalue, ListExpr): + if isinstance(lvalue, (TupleExpr, ListExpr)): self.check_assignment_to_multiple_lvalues( lvalue.items, rvalue, rvalue, infer_lvalue_type ) @@ -2735,6 +2935,10 @@ def check_assignment( if name == "__match_args__" and inferred is not None: typ = self.expr_checker.accept(rvalue) self.check_match_args(inferred, typ, lvalue) + if name == "__post_init__": + active_class = self.scope.active_class() + if active_class and dataclasses_plugin.is_processed_dataclass(active_class): + self.fail(message_registry.DATACLASS_POST_INIT_MUST_BE_A_FUNCTION, rvalue) # Defer PartialType's super type checking. if ( @@ -2832,7 +3036,7 @@ def check_assignment( p_rvalue_type = get_proper_type(rvalue_type) p_lvalue_type = get_proper_type(lvalue_type) if ( - isinstance(p_rvalue_type, CallableType) + isinstance(p_rvalue_type, FunctionLike) and p_rvalue_type.is_type_obj() and ( p_rvalue_type.type_object().is_abstract @@ -2947,7 +3151,6 @@ def check_compatibility_all_supers( and lvalue.kind in (MDEF, None) and len(lvalue_node.info.bases) > 0 # None for Vars defined via self ): - for base in lvalue_node.info.mro[1:]: tnode = base.names.get(lvalue_node.name) if tnode is not None: @@ -3047,7 +3250,7 @@ def check_compatibility_super( if base_static and compare_static: lvalue_node.is_staticmethod = True - return self.check_subtype( + ok = self.check_subtype( compare_type, base_type, rvalue, @@ -3055,6 +3258,20 @@ def check_compatibility_super( "expression has type", f'base class "{base.name}" defined the type as', ) + if ( + ok + and codes.MUTABLE_OVERRIDE in self.options.enabled_error_codes + and self.is_writable_attribute(base_node) + ): + ok = self.check_subtype( + base_type, + compare_type, + rvalue, + message_registry.COVARIANT_OVERRIDE_OF_MUTABLE_ATTRIBUTE, + f'base class "{base.name}" defined the type as', + "expression has type", + ) + return ok return True def lvalue_type_from_base( @@ -3129,6 +3346,8 @@ def check_compatibility_final_super( """ if not isinstance(base_node, (Var, FuncBase, Decorator)): return True + if is_private(node.name): + return True if base_node.is_final and (node.is_final or not isinstance(base_node, Var)): # Give this error only for explicit override attempt with `Final`, or # if we are overriding a final method with variable. @@ -3198,8 +3417,8 @@ def check_final(self, s: AssignmentStmt | OperatorAssignmentStmt | AssignmentExp if ( lv.node.final_unset_in_class and not lv.node.final_set_in_init - and not self.is_stub - and # It is OK to skip initializer in stub files. + and not self.is_stub # It is OK to skip initializer in stub files. + and # Avoid extra error messages, if there is no type in Final[...], # then we already reported the error about missing r.h.s. isinstance(s, AssignmentStmt) @@ -3277,6 +3496,37 @@ def is_assignable_slot(self, lvalue: Lvalue, typ: Type | None) -> bool: return all(self.is_assignable_slot(lvalue, u) for u in typ.items) return False + def flatten_rvalues(self, rvalues: list[Expression]) -> list[Expression]: + """Flatten expression list by expanding those * items that have tuple type. + + For each regular type item in the tuple type use a TempNode(), for an Unpack + item use a corresponding StarExpr(TempNode()). + """ + new_rvalues = [] + for rv in rvalues: + if not isinstance(rv, StarExpr): + new_rvalues.append(rv) + continue + typ = get_proper_type(self.expr_checker.accept(rv.expr)) + if not isinstance(typ, TupleType): + new_rvalues.append(rv) + continue + for t in typ.items: + if not isinstance(t, UnpackType): + new_rvalues.append(TempNode(t)) + else: + unpacked = get_proper_type(t.type) + if isinstance(unpacked, TypeVarTupleType): + fallback = unpacked.upper_bound + else: + assert ( + isinstance(unpacked, Instance) + and unpacked.type.fullname == "builtins.tuple" + ) + fallback = unpacked + new_rvalues.append(StarExpr(TempNode(fallback))) + return new_rvalues + def check_assignment_to_multiple_lvalues( self, lvalues: list[Lvalue], @@ -3284,29 +3534,27 @@ def check_assignment_to_multiple_lvalues( context: Context, infer_lvalue_type: bool = True, ) -> None: - if isinstance(rvalue, TupleExpr) or isinstance(rvalue, ListExpr): + if isinstance(rvalue, (TupleExpr, ListExpr)): # Recursively go into Tuple or List expression rhs instead of - # using the type of rhs, because this allowed more fine grained + # using the type of rhs, because this allows more fine-grained # control in cases like: a, b = [int, str] where rhs would get # type List[object] rvalues: list[Expression] = [] iterable_type: Type | None = None last_idx: int | None = None - for idx_rval, rval in enumerate(rvalue.items): + for idx_rval, rval in enumerate(self.flatten_rvalues(rvalue.items)): if isinstance(rval, StarExpr): typs = get_proper_type(self.expr_checker.accept(rval.expr)) - if isinstance(typs, TupleType): - rvalues.extend([TempNode(typ) for typ in typs.items]) - elif self.type_is_iterable(typs) and isinstance(typs, Instance): + if self.type_is_iterable(typs) and isinstance(typs, Instance): if iterable_type is not None and iterable_type != self.iterable_item_type( - typs + typs, rvalue ): self.fail(message_registry.CONTIGUOUS_ITERABLE_EXPECTED, context) else: if last_idx is None or last_idx + 1 == idx_rval: rvalues.append(rval) last_idx = idx_rval - iterable_type = self.iterable_item_type(typs) + iterable_type = self.iterable_item_type(typs, rvalue) else: self.fail(message_registry.CONTIGUOUS_ITERABLE_EXPECTED, context) else: @@ -3364,8 +3612,32 @@ def check_assignment_to_multiple_lvalues( self.check_multi_assignment(lvalues, rvalue, context, infer_lvalue_type) def check_rvalue_count_in_assignment( - self, lvalues: list[Lvalue], rvalue_count: int, context: Context + self, + lvalues: list[Lvalue], + rvalue_count: int, + context: Context, + rvalue_unpack: int | None = None, ) -> bool: + if rvalue_unpack is not None: + if not any(isinstance(e, StarExpr) for e in lvalues): + self.fail("Variadic tuple unpacking requires a star target", context) + return False + if len(lvalues) > rvalue_count: + self.fail(message_registry.TOO_MANY_TARGETS_FOR_VARIADIC_UNPACK, context) + return False + left_star_index = next(i for i, lv in enumerate(lvalues) if isinstance(lv, StarExpr)) + left_prefix = left_star_index + left_suffix = len(lvalues) - left_star_index - 1 + right_prefix = rvalue_unpack + right_suffix = rvalue_count - rvalue_unpack - 1 + if left_suffix > right_suffix or left_prefix > right_prefix: + # Case of asymmetric unpack like: + # rv: tuple[int, *Ts, int, int] + # x, y, *xs, z = rv + # it is technically valid, but is tricky to reason about. + # TODO: support this (at least if the r.h.s. unpack is a homogeneous tuple). + self.fail(message_registry.TOO_MANY_TARGETS_FOR_VARIADIC_UNPACK, context) + return True if any(isinstance(lvalue, StarExpr) for lvalue in lvalues): if len(lvalues) - 1 > rvalue_count: self.msg.wrong_number_values_to_unpack(rvalue_count, len(lvalues) - 1, context) @@ -3399,6 +3671,13 @@ def check_multi_assignment( if len(relevant_items) == 1: rvalue_type = get_proper_type(relevant_items[0]) + if ( + isinstance(rvalue_type, TupleType) + and find_unpack_in_list(rvalue_type.items) is not None + ): + # Normalize for consistent handling with "old-style" homogeneous tuples. + rvalue_type = expand_type(rvalue_type, {}) + if isinstance(rvalue_type, AnyType): for lv in lvalues: if isinstance(lv, StarExpr): @@ -3510,7 +3789,10 @@ def check_multi_assignment_from_tuple( undefined_rvalue: bool, infer_lvalue_type: bool = True, ) -> None: - if self.check_rvalue_count_in_assignment(lvalues, len(rvalue_type.items), context): + rvalue_unpack = find_unpack_in_list(rvalue_type.items) + if self.check_rvalue_count_in_assignment( + lvalues, len(rvalue_type.items), context, rvalue_unpack=rvalue_unpack + ): star_index = next( (i for i, lv in enumerate(lvalues) if isinstance(lv, StarExpr)), len(lvalues) ) @@ -3555,12 +3837,39 @@ def check_multi_assignment_from_tuple( self.check_assignment(lv, self.temp_node(rv_type, context), infer_lvalue_type) if star_lv: list_expr = ListExpr( - [self.temp_node(rv_type, context) for rv_type in star_rv_types] + [ + ( + self.temp_node(rv_type, context) + if not isinstance(rv_type, UnpackType) + else StarExpr(self.temp_node(rv_type.type, context)) + ) + for rv_type in star_rv_types + ] ) list_expr.set_line(context) self.check_assignment(star_lv.expr, list_expr, infer_lvalue_type) for lv, rv_type in zip(right_lvs, right_rv_types): self.check_assignment(lv, self.temp_node(rv_type, context), infer_lvalue_type) + else: + # Store meaningful Any types for lvalues, errors are already given + # by check_rvalue_count_in_assignment() + if infer_lvalue_type: + for lv in lvalues: + if ( + isinstance(lv, NameExpr) + and isinstance(lv.node, Var) + and lv.node.type is None + ): + lv.node.type = AnyType(TypeOfAny.from_error) + elif isinstance(lv, StarExpr): + if ( + isinstance(lv.expr, NameExpr) + and isinstance(lv.expr.node, Var) + and lv.expr.node.type is None + ): + lv.expr.node.type = self.named_generic_type( + "builtins.list", [AnyType(TypeOfAny.from_error)] + ) def lvalue_type_for_inference(self, lvalues: list[Lvalue], rvalue_type: TupleType) -> Type: star_index = next( @@ -3618,7 +3927,7 @@ def split_around_star( def type_is_iterable(self, type: Type) -> bool: type = get_proper_type(type) - if isinstance(type, CallableType) and type.is_type_obj(): + if isinstance(type, FunctionLike) and type.is_type_obj(): type = type.fallback return is_subtype( type, self.named_generic_type("typing.Iterable", [AnyType(TypeOfAny.special_form)]) @@ -3632,8 +3941,10 @@ def check_multi_assignment_from_iterable( infer_lvalue_type: bool = True, ) -> None: rvalue_type = get_proper_type(rvalue_type) - if self.type_is_iterable(rvalue_type) and isinstance(rvalue_type, Instance): - item_type = self.iterable_item_type(rvalue_type) + if self.type_is_iterable(rvalue_type) and isinstance( + rvalue_type, (Instance, CallableType, TypeType, Overloaded) + ): + item_type = self.iterable_item_type(rvalue_type, context) for lv in lvalues: if isinstance(lv, StarExpr): items_type = self.named_generic_type("builtins.list", [item_type]) @@ -3656,8 +3967,8 @@ def check_lvalue(self, lvalue: Lvalue) -> tuple[Type | None, IndexExpr | None, V not isinstance(lvalue, NameExpr) or isinstance(lvalue.node, Var) ): if isinstance(lvalue, NameExpr): - inferred = cast(Var, lvalue.node) - assert isinstance(inferred, Var) + assert isinstance(lvalue.node, Var) + inferred = lvalue.node else: assert isinstance(lvalue, MemberExpr) self.expr_checker.accept(lvalue.expr) @@ -3670,7 +3981,7 @@ def check_lvalue(self, lvalue: Lvalue) -> tuple[Type | None, IndexExpr | None, V elif isinstance(lvalue, NameExpr): lvalue_type = self.expr_checker.analyze_ref_expr(lvalue, lvalue=True) self.store_type(lvalue, lvalue_type) - elif isinstance(lvalue, TupleExpr) or isinstance(lvalue, ListExpr): + elif isinstance(lvalue, (TupleExpr, ListExpr)): types = [ self.check_lvalue(sub_expr)[0] or # This type will be used as a context for further inference of rvalue, @@ -3783,7 +4094,7 @@ def is_valid_defaultdict_partial_value_type(self, t: ProperType) -> bool: Examples: * t is 'int' --> True - * t is 'list[]' --> True + * t is 'list[Never]' --> True * t is 'dict[...]' --> False (only generic types with a single type argument supported) """ @@ -3793,11 +4104,12 @@ def is_valid_defaultdict_partial_value_type(self, t: ProperType) -> bool: return True if len(t.args) == 1: arg = get_proper_type(t.args[0]) - # TODO: This is too permissive -- we only allow TypeVarType since - # they leak in cases like defaultdict(list) due to a bug. - # This can result in incorrect types being inferred, but only - # in rare cases. - if isinstance(arg, (TypeVarType, UninhabitedType, NoneType)): + if self.options.old_type_inference: + # Allow leaked TypeVars for legacy inference logic. + allowed = isinstance(arg, (UninhabitedType, NoneType, TypeVarType)) + else: + allowed = isinstance(arg, (UninhabitedType, NoneType)) + if allowed: return True return False @@ -3828,7 +4140,7 @@ def set_inference_error_fallback_type(self, var: Var, lvalue: Lvalue, type: Type x = [] # type: ignore x.append(1) # Should be ok! - We implement this here by giving x a valid type (replacing inferred with Any). + We implement this here by giving x a valid type (replacing inferred Never with Any). """ fallback = self.inference_error_fallback_type(type) self.set_inferred_type(var, lvalue, fallback) @@ -3967,7 +4279,12 @@ def check_member_assignment( dunder_set = attribute_type.type.get_method("__set__") if dunder_set is None: - self.fail(message_registry.DESCRIPTOR_SET_NOT_CALLABLE.format(attribute_type), context) + self.fail( + message_registry.DESCRIPTOR_SET_NOT_CALLABLE.format( + attribute_type.str_with_options(self.options) + ), + context, + ) return AnyType(TypeOfAny.from_error), get_type, False bound_method = analyze_decorator_or_funcbase_access( @@ -4047,7 +4364,7 @@ def check_indexed_assignment( ) lvalue.method_type = method_type - self.expr_checker.check_method_call( + res_type, _ = self.expr_checker.check_method_call( "__setitem__", basetype, method_type, @@ -4055,6 +4372,9 @@ def check_indexed_assignment( [nodes.ARG_POS, nodes.ARG_POS], context, ) + res_type = get_proper_type(res_type) + if isinstance(res_type, UninhabitedType) and not res_type.ambiguous: + self.binder.unreachable() def try_infer_partial_type_from_indexed_assignment( self, lvalue: IndexExpr, rvalue: Expression @@ -4118,7 +4438,9 @@ def visit_expression_stmt(self, s: ExpressionStmt) -> None: if error_note_and_code: error_note, code = error_note_and_code self.fail( - message_registry.TYPE_MUST_BE_USED.format(format_type(expr_type)), s, code=code + message_registry.TYPE_MUST_BE_USED.format(format_type(expr_type, self.options)), + s, + code=code, ) self.note(error_note, s, code=code) @@ -4140,12 +4462,14 @@ def check_return_stmt(self, s: ReturnStmt) -> None: return_type = self.return_types[-1] return_type = get_proper_type(return_type) + is_lambda = isinstance(self.scope.top_function(), LambdaExpr) if isinstance(return_type, UninhabitedType): - self.fail(message_registry.NO_RETURN_EXPECTED, s) - return + # Avoid extra error messages for failed inference in lambdas + if not is_lambda or not return_type.ambiguous: + self.fail(message_registry.NO_RETURN_EXPECTED, s) + return if s.expr: - is_lambda = isinstance(self.scope.top_function(), LambdaExpr) declared_none_return = isinstance(return_type, NoneType) declared_any_return = isinstance(return_type, AnyType) @@ -4182,6 +4506,7 @@ def check_return_stmt(self, s: ReturnStmt) -> None: isinstance(return_type, Instance) and return_type.type.fullname == "builtins.object" ) + and not is_lambda ): self.msg.incorrectly_returning_any(return_type, s) return @@ -4488,42 +4813,32 @@ def analyze_async_iterable_item_type(self, expr: Expression) -> tuple[Type, Type def analyze_iterable_item_type(self, expr: Expression) -> tuple[Type, Type]: """Analyse iterable expression and return iterator and iterator item types.""" - echk = self.expr_checker - iterable = get_proper_type(echk.accept(expr)) - iterator = echk.check_method_call_by_name("__iter__", iterable, [], [], expr)[0] - + iterator, iterable = self.analyze_iterable_item_type_without_expression( + self.expr_checker.accept(expr), context=expr + ) int_type = self.analyze_range_native_int_type(expr) if int_type: return iterator, int_type - - if isinstance(iterable, TupleType): - joined: Type = UninhabitedType() - for item in iterable.items: - joined = join_types(joined, item) - return iterator, joined - else: - # Non-tuple iterable. - return iterator, echk.check_method_call_by_name("__next__", iterator, [], [], expr)[0] + return iterator, iterable def analyze_iterable_item_type_without_expression( self, type: Type, context: Context ) -> tuple[Type, Type]: """Analyse iterable type and return iterator and iterator item types.""" echk = self.expr_checker + iterable: Type iterable = get_proper_type(type) iterator = echk.check_method_call_by_name("__iter__", iterable, [], [], context)[0] - if isinstance(iterable, TupleType): - joined: Type = UninhabitedType() - for item in iterable.items: - joined = join_types(joined, item) - return iterator, joined + if ( + isinstance(iterable, TupleType) + and iterable.partial_fallback.type.fullname == "builtins.tuple" + ): + return iterator, tuple_fallback(iterable).args[0] else: # Non-tuple iterable. - return ( - iterator, - echk.check_method_call_by_name("__next__", iterator, [], [], context)[0], - ) + iterable = echk.check_method_call_by_name("__next__", iterator, [], [], context)[0] + return iterator, iterable def analyze_range_native_int_type(self, expr: Expression) -> Type | None: """Try to infer native int item type from arguments to range(...). @@ -4605,17 +4920,20 @@ def visit_decorator(self, e: Decorator) -> None: e.var.type = AnyType(TypeOfAny.special_form) e.var.is_ready = True return + self.visit_decorator_inner(e) + def visit_decorator_inner(self, e: Decorator, allow_empty: bool = False) -> None: if self.recurse_into_functions: with self.tscope.function_scope(e.func): - self.check_func_item(e.func, name=e.func.name) + self.check_func_item(e.func, name=e.func.name, allow_empty=allow_empty) # Process decorators from the inside out to determine decorated signature, which # may be different from the declared signature. sig: Type = self.function_type(e.func) for d in reversed(e.decorators): if refers_to_fullname(d, OVERLOAD_NAMES): - self.fail(message_registry.MULTIPLE_OVERLOADS_REQUIRED, e) + if not allow_empty: + self.fail(message_registry.MULTIPLE_OVERLOADS_REQUIRED, e) continue dec = self.expr_checker.accept(d) temp = self.temp_node(sig, context=e) @@ -4642,8 +4960,17 @@ def visit_decorator(self, e: Decorator) -> None: self.msg.fail("Too many arguments for property", e) self.check_incompatible_property_override(e) # For overloaded functions we already checked override for overload as a whole. + if allow_empty: + return if e.func.info and not e.func.is_dynamic() and not e.is_overload: - self.check_method_override(e) + found_method_base_classes = self.check_method_override(e) + if ( + e.func.is_explicit_override + and not found_method_base_classes + and found_method_base_classes is not None + ): + self.msg.no_overridable_method(e.func.name, e.func) + self.check_explicit_override_decorator(e.func, found_method_base_classes) if e.func.info and e.func.name in ("__init__", "__new__"): if e.type and not isinstance(get_proper_type(e.type), (FunctionLike, AnyType)): @@ -4755,9 +5082,22 @@ def visit_break_stmt(self, s: BreakStmt) -> None: def visit_continue_stmt(self, s: ContinueStmt) -> None: self.binder.handle_continue() - return None + return def visit_match_stmt(self, s: MatchStmt) -> None: + named_subject: Expression + if isinstance(s.subject, CallExpr): + # Create a dummy subject expression to handle cases where a match statement's subject + # is not a literal value. This lets us correctly narrow types and check exhaustivity + # This is hack! + id = s.subject.callee.fullname if isinstance(s.subject.callee, RefExpr) else "" + name = "dummy-match-" + id + v = Var(name) + named_subject = NameExpr(name) + named_subject.node = v + else: + named_subject = s.subject + with self.binder.frame_context(can_skip=False, fall_through=0): subject_type = get_proper_type(self.expr_checker.accept(s.subject)) @@ -4776,7 +5116,7 @@ def visit_match_stmt(self, s: MatchStmt) -> None: # The second pass narrows down the types and type checks bodies. for p, g, b in zip(s.patterns, s.guards, s.bodies): current_subject_type = self.expr_checker.narrow_type_from_binder( - s.subject, subject_type + named_subject, subject_type ) pattern_type = self.pattern_checker.accept(p, current_subject_type) with self.binder.frame_context(can_skip=True, fall_through=2): @@ -4787,13 +5127,16 @@ def visit_match_stmt(self, s: MatchStmt) -> None: else_map: TypeMap = {} else: pattern_map, else_map = conditional_types_to_typemaps( - s.subject, pattern_type.type, pattern_type.rest_type + named_subject, pattern_type.type, pattern_type.rest_type ) self.remove_capture_conflicts(pattern_type.captures, inferred_types) self.push_type_map(pattern_map) + if pattern_map: + for expr, typ in pattern_map.items(): + self.push_type_map(self._get_recursive_sub_patterns_map(expr, typ)) self.push_type_map(pattern_type.captures) if g is not None: - with self.binder.frame_context(can_skip=True, fall_through=3): + with self.binder.frame_context(can_skip=False, fall_through=3): gt = get_proper_type(self.expr_checker.accept(g)) if isinstance(gt, DeletedType): @@ -4802,6 +5145,21 @@ def visit_match_stmt(self, s: MatchStmt) -> None: guard_map, guard_else_map = self.find_isinstance_check(g) else_map = or_conditional_maps(else_map, guard_else_map) + # If the guard narrowed the subject, copy the narrowed types over + if isinstance(p, AsPattern): + case_target = p.pattern or p.name + if isinstance(case_target, NameExpr): + for type_map in (guard_map, else_map): + if not type_map: + continue + for expr in list(type_map): + if not ( + isinstance(expr, NameExpr) + and expr.fullname == case_target.fullname + ): + continue + type_map[named_subject] = type_map[expr] + self.push_type_map(guard_map) self.accept(b) else: @@ -4813,6 +5171,20 @@ def visit_match_stmt(self, s: MatchStmt) -> None: with self.binder.frame_context(can_skip=False, fall_through=2): pass + def _get_recursive_sub_patterns_map( + self, expr: Expression, typ: Type + ) -> dict[Expression, Type]: + sub_patterns_map: dict[Expression, Type] = {} + typ_ = get_proper_type(typ) + if isinstance(expr, TupleExpr) and isinstance(typ_, TupleType): + # When matching a tuple expression with a sequence pattern, narrow individual tuple items + assert len(expr.items) == len(typ_.items) + for item_expr, item_typ in zip(expr.items, typ_.items): + sub_patterns_map[item_expr] = item_typ + sub_patterns_map.update(self._get_recursive_sub_patterns_map(item_expr, item_typ)) + + return sub_patterns_map + def infer_variable_types_from_type_maps(self, type_maps: list[TypeMap]) -> dict[Var, Type]: all_captures: dict[Var, list[tuple[NameExpr, Type]]] = defaultdict(list) for tm in type_maps: @@ -4948,7 +5320,18 @@ def _make_fake_typeinfo_and_full_name( # We use the pretty_names_list for error messages but can't # use it for the real name that goes into the symbol table # because it can have dots in it. - pretty_names_list = pretty_seq(format_type_distinctly(*base_classes, bare=True), "and") + pretty_names_list = pretty_seq( + format_type_distinctly(*base_classes, options=self.options, bare=True), "and" + ) + + new_errors = [] + for base in base_classes: + if base.type.is_final: + new_errors.append((pretty_names_list, f'"{base.type.name}" is final')) + if new_errors: + errors.extend(new_errors) + return None + try: info, full_name = _make_fake_typeinfo_and_full_name(base_classes, curr_module) with self.msg.filter_errors() as local_errors: @@ -4961,10 +5344,10 @@ def _make_fake_typeinfo_and_full_name( self.check_multiple_inheritance(info) info.is_intersection = True except MroError: - errors.append((pretty_names_list, "inconsistent method resolution order")) + errors.append((pretty_names_list, "would have inconsistent method resolution order")) return None if local_errors.has_new_errors(): - errors.append((pretty_names_list, "incompatible method signatures")) + errors.append((pretty_names_list, "would have incompatible method signatures")) return None curr_module.names[full_name] = SymbolTableNode(GDEF, info) @@ -4980,11 +5363,12 @@ def intersect_instance_callable(self, typ: Instance, callable_type: CallableType # In order for this to work in incremental mode, the type we generate needs to # have a valid fullname and a corresponding entry in a symbol table. We generate # a unique name inside the symbol table of the current module. - cur_module = cast(MypyFile, self.scope.stack[0]) + cur_module = self.scope.stack[0] + assert isinstance(cur_module, MypyFile) gen_name = gen_unique_name(f"", cur_module.names) # Synthesize a fake TypeInfo - short_name = format_type_bare(typ) + short_name = format_type_bare(typ, self.options) cdef, info = self.make_fake_typeinfo(cur_module.fullname, gen_name, short_name, [typ]) # Build up a fake FuncDef so we can populate the symbol table. @@ -5031,7 +5415,7 @@ def partition_by_callable( """ typ = get_proper_type(typ) - if isinstance(typ, FunctionLike) or isinstance(typ, TypeType): + if isinstance(typ, (FunctionLike, TypeType)): return [typ], [] if isinstance(typ, AnyType): @@ -5065,7 +5449,7 @@ def partition_by_callable( callables, uncallables = self.partition_by_callable( erase_to_union_or_bound(typ), unsound_partition ) - uncallables = [typ] if len(uncallables) else [] + uncallables = [typ] if uncallables else [] return callables, uncallables # A TupleType is callable if its fallback is, but needs special handling @@ -5080,7 +5464,7 @@ def partition_by_callable( callables, uncallables = self.partition_by_callable( method.type, unsound_partition=False ) - if len(callables) and not len(uncallables): + if callables and not uncallables: # Only consider the type callable if its __call__ method is # definitely callable. return [typ], [] @@ -5116,14 +5500,12 @@ def conditional_callable_type_map( callables, uncallables = self.partition_by_callable(current_type, unsound_partition=False) - if len(callables) and len(uncallables): - callable_map = {expr: UnionType.make_union(callables)} if len(callables) else None - uncallable_map = ( - {expr: UnionType.make_union(uncallables)} if len(uncallables) else None - ) + if callables and uncallables: + callable_map = {expr: UnionType.make_union(callables)} if callables else None + uncallable_map = {expr: UnionType.make_union(uncallables)} if uncallables else None return callable_map, uncallable_map - elif len(callables): + elif callables: return {}, None return None, {} @@ -5192,7 +5574,7 @@ def _check_for_truthy_type(self, t: Type, expr: Expression) -> None: return def format_expr_type() -> str: - typ = format_type(t) + typ = format_type(t, self.options) if isinstance(expr, MemberExpr): return f'Member "{expr.name}" has type {typ}' elif isinstance(expr, RefExpr) and expr.fullname: @@ -5206,15 +5588,22 @@ def format_expr_type() -> str: else: return f"Expression has type {typ}" + def get_expr_name() -> str: + if isinstance(expr, (NameExpr, MemberExpr)): + return f'"{expr.name}"' + else: + # return type if expr has no name + return format_type(t, self.options) + if isinstance(t, FunctionLike): - self.fail(message_registry.FUNCTION_ALWAYS_TRUE.format(format_type(t)), expr) + self.fail(message_registry.FUNCTION_ALWAYS_TRUE.format(get_expr_name()), expr) elif isinstance(t, UnionType): self.fail(message_registry.TYPE_ALWAYS_TRUE_UNIONTYPE.format(format_expr_type()), expr) elif isinstance(t, Instance) and t.type.fullname == "typing.Iterable": _, info = self.make_fake_typeinfo("typing", "Collection", "Collection", []) self.fail( message_registry.ITERABLE_ALWAYS_TRUE.format( - format_expr_type(), format_type(Instance(info, t.args)) + format_expr_type(), format_type(Instance(info, t.args), self.options) ), expr, ) @@ -5302,7 +5691,7 @@ def combine_maps(list_maps: list[TypeMap]) -> TypeMap: def find_isinstance_check(self, node: Expression) -> tuple[TypeMap, TypeMap]: """Find any isinstance checks (within a chain of ands). Includes implicit and explicit checks for None and calls to callable. - Also includes TypeGuard functions. + Also includes TypeGuard and TypeIs functions. Return value is a map of variables to their types if the condition is true and a map of variables to their types if the condition is false. @@ -5354,34 +5743,57 @@ def find_isinstance_check_helper(self, node: Expression) -> tuple[TypeMap, TypeM if literal(expr) == LITERAL_TYPE and attr and len(attr) == 1: return self.hasattr_type_maps(expr, self.lookup_type(expr), attr[0]) elif isinstance(node.callee, RefExpr): - if node.callee.type_guard is not None: + if node.callee.type_guard is not None or node.callee.type_is is not None: # TODO: Follow *args, **kwargs if node.arg_kinds[0] != nodes.ARG_POS: # the first argument might be used as a kwarg called_type = get_proper_type(self.lookup_type(node.callee)) - assert isinstance(called_type, (CallableType, Overloaded)) + + # TODO: there are some more cases in check_call() to handle. + if isinstance(called_type, Instance): + call = find_member( + "__call__", called_type, called_type, is_operator=True + ) + if call is not None: + called_type = get_proper_type(call) # *assuming* the overloaded function is correct, there's a couple cases: # 1) The first argument has different names, but is pos-only. We don't # care about this case, the argument must be passed positionally. # 2) The first argument allows keyword reference, therefore must be the # same between overloads. - name = called_type.items[0].arg_names[0] - - if name in node.arg_names: - idx = node.arg_names.index(name) - # we want the idx-th variable to be narrowed - expr = collapse_walrus(node.args[idx]) - else: - self.fail(message_registry.TYPE_GUARD_POS_ARG_REQUIRED, node) - return {}, {} + if isinstance(called_type, (CallableType, Overloaded)): + name = called_type.items[0].arg_names[0] + if name in node.arg_names: + idx = node.arg_names.index(name) + # we want the idx-th variable to be narrowed + expr = collapse_walrus(node.args[idx]) + else: + kind = ( + "guard" if node.callee.type_guard is not None else "narrower" + ) + self.fail( + message_registry.TYPE_GUARD_POS_ARG_REQUIRED.format(kind), node + ) + return {}, {} if literal(expr) == LITERAL_TYPE: # Note: we wrap the target type, so that we can special case later. # Namely, for isinstance() we use a normal meet, while TypeGuard is # considered "always right" (i.e. even if the types are not overlapping). # Also note that a care must be taken to unwrap this back at read places # where we use this to narrow down declared type. - return {expr: TypeGuardedType(node.callee.type_guard)}, {} + if node.callee.type_guard is not None: + return {expr: TypeGuardedType(node.callee.type_guard)}, {} + else: + assert node.callee.type_is is not None + return conditional_types_to_typemaps( + expr, + *self.conditional_types_with_intersection( + self.lookup_type(expr), + [TypeRange(node.callee.type_is, is_upper_bound=False)], + expr, + ), + ) elif isinstance(node, ComparisonExpr): # Step 1: Obtain the types of each operand and whether or not we can # narrow their types. (For example, we shouldn't try narrowing the @@ -5506,13 +5918,13 @@ def has_no_custom_eq_checks(t: Type) -> bool: if left_index in narrowable_operand_index_to_hash: # We only try and narrow away 'None' for now - if is_optional(item_type): + if is_overlapping_none(item_type): collection_item_type = get_proper_type( builtin_item_type(iterable_type) ) if ( collection_item_type is not None - and not is_optional(collection_item_type) + and not is_overlapping_none(collection_item_type) and not ( isinstance(collection_item_type, Instance) and collection_item_type.type.fullname == "builtins.object" @@ -5544,7 +5956,15 @@ def has_no_custom_eq_checks(t: Type) -> bool: partial_type_maps.append((if_map, else_map)) - return reduce_conditional_maps(partial_type_maps) + # If we have found non-trivial restrictions from the regular comparisons, + # then return soon. Otherwise try to infer restrictions involving `len(x)`. + # TODO: support regular and len() narrowing in the same chain. + if any(m != ({}, {}) for m in partial_type_maps): + return reduce_conditional_maps(partial_type_maps) + else: + # Use meet for `and` maps to get correct results for chained checks + # like `if 1 < len(x) < 4: ...` + return reduce_conditional_maps(self.find_tuple_len_narrowing(node), use_meet=True) elif isinstance(node, AssignmentExpr): if_map = {} else_map = {} @@ -5575,7 +5995,10 @@ def has_no_custom_eq_checks(t: Type) -> bool: # and false if at least one of e1 and e2 is false. return ( and_conditional_maps(left_if_vars, right_if_vars), - or_conditional_maps(left_else_vars, right_else_vars), + # Note that if left else type is Any, we can't add any additional + # types to it, since the right maps were computed assuming + # the left is True, which may be not the case in the else branch. + or_conditional_maps(left_else_vars, right_else_vars, coalesce_any=True), ) elif isinstance(node, OpExpr) and node.op == "or": left_if_vars, left_else_vars = self.find_isinstance_check(node.left) @@ -5590,6 +6013,27 @@ def has_no_custom_eq_checks(t: Type) -> bool: elif isinstance(node, UnaryExpr) and node.op == "not": left, right = self.find_isinstance_check(node.expr) return right, left + elif ( + literal(node) == LITERAL_TYPE + and self.has_type(node) + and self.can_be_narrowed_with_len(self.lookup_type(node)) + # Only translate `if x` to `if len(x) > 0` when possible. + and not custom_special_method(self.lookup_type(node), "__bool__") + and self.options.strict_optional + ): + # Combine a `len(x) > 0` check with the default logic below. + yes_type, no_type = self.narrow_with_len(self.lookup_type(node), ">", 0) + if yes_type is not None: + yes_type = true_only(yes_type) + else: + yes_type = UninhabitedType() + if no_type is not None: + no_type = false_only(no_type) + else: + no_type = UninhabitedType() + if_map = {node: yes_type} if not isinstance(yes_type, UninhabitedType) else None + else_map = {node: no_type} if not isinstance(no_type, UninhabitedType) else None + return if_map, else_map # Restrict the type of the variable to True-ish/False-ish in the if and else branches # respectively @@ -5803,7 +6247,14 @@ def refine_identity_comparison_expression( """ should_coerce = True if coerce_only_in_literal_context: - should_coerce = any(is_literal_type_like(operand_types[i]) for i in chain_indices) + + def should_coerce_inner(typ: Type) -> bool: + typ = get_proper_type(typ) + return is_literal_type_like(typ) or ( + isinstance(typ, Instance) and typ.type.is_enum + ) + + should_coerce = any(should_coerce_inner(operand_types[i]) for i in chain_indices) target: Type | None = None possible_target_indices = [] @@ -5912,7 +6363,7 @@ def refine_away_none_in_comparison( non_optional_types = [] for i in chain_indices: typ = operand_types[i] - if not is_optional(typ): + if not is_overlapping_none(typ): non_optional_types.append(typ) # Make sure we have a mixture of optional and non-optional types. @@ -5922,13 +6373,294 @@ def refine_away_none_in_comparison( if_map = {} for i in narrowable_operand_indices: expr_type = operand_types[i] - if not is_optional(expr_type): + if not is_overlapping_none(expr_type): continue if any(is_overlapping_erased_types(expr_type, t) for t in non_optional_types): if_map[operands[i]] = remove_optional(expr_type) return if_map, {} + def is_len_of_tuple(self, expr: Expression) -> bool: + """Is this expression a `len(x)` call where x is a tuple or union of tuples?""" + if not isinstance(expr, CallExpr): + return False + if not refers_to_fullname(expr.callee, "builtins.len"): + return False + if len(expr.args) != 1: + return False + expr = expr.args[0] + if literal(expr) != LITERAL_TYPE: + return False + if not self.has_type(expr): + return False + return self.can_be_narrowed_with_len(self.lookup_type(expr)) + + def can_be_narrowed_with_len(self, typ: Type) -> bool: + """Is this a type that can benefit from length check type restrictions? + + Currently supported types are TupleTypes, Instances of builtins.tuple, and + unions involving such types. + """ + if custom_special_method(typ, "__len__"): + # If user overrides builtin behavior, we can't do anything. + return False + p_typ = get_proper_type(typ) + # Note: we are conservative about tuple subclasses, because some code may rely on + # the fact that tuple_type of fallback TypeInfo matches the original TupleType. + if isinstance(p_typ, TupleType): + if any(isinstance(t, UnpackType) for t in p_typ.items): + return p_typ.partial_fallback.type.fullname == "builtins.tuple" + return True + if isinstance(p_typ, Instance): + return p_typ.type.has_base("builtins.tuple") + if isinstance(p_typ, UnionType): + return any(self.can_be_narrowed_with_len(t) for t in p_typ.items) + return False + + def literal_int_expr(self, expr: Expression) -> int | None: + """Is this expression an int literal, or a reference to an int constant? + + If yes, return the corresponding int value, otherwise return None. + """ + if not self.has_type(expr): + return None + expr_type = self.lookup_type(expr) + expr_type = coerce_to_literal(expr_type) + proper_type = get_proper_type(expr_type) + if not isinstance(proper_type, LiteralType): + return None + if not isinstance(proper_type.value, int): + return None + return proper_type.value + + def find_tuple_len_narrowing(self, node: ComparisonExpr) -> list[tuple[TypeMap, TypeMap]]: + """Top-level logic to find type restrictions from a length check on tuples. + + We try to detect `if` checks like the following: + x: tuple[int, int] | tuple[int, int, int] + y: tuple[int, int] | tuple[int, int, int] + if len(x) == len(y) == 2: + a, b = x # OK + c, d = y # OK + + z: tuple[int, ...] + if 1 < len(z) < 4: + x = z # OK + and report corresponding type restrictions to the binder. + """ + # First step: group consecutive `is` and `==` comparisons together. + # This is essentially a simplified version of group_comparison_operands(), + # tuned to the len()-like checks. Note that we don't propagate indirect + # restrictions like e.g. `len(x) > foo() > 1` yet, since it is tricky. + # TODO: propagate indirect len() comparison restrictions. + chained = [] + last_group = set() + for op, left, right in node.pairwise(): + if isinstance(left, AssignmentExpr): + left = left.value + if isinstance(right, AssignmentExpr): + right = right.value + if op in ("is", "=="): + last_group.add(left) + last_group.add(right) + else: + if last_group: + chained.append(("==", list(last_group))) + last_group = set() + if op in {"is not", "!=", "<", "<=", ">", ">="}: + chained.append((op, [left, right])) + if last_group: + chained.append(("==", list(last_group))) + + # Second step: infer type restrictions from each group found above. + type_maps = [] + for op, items in chained: + # TODO: support unions of literal types as len() comparison targets. + if not any(self.literal_int_expr(it) is not None for it in items): + continue + if not any(self.is_len_of_tuple(it) for it in items): + continue + + # At this step we know there is at least one len(x) and one literal in the group. + if op in ("is", "=="): + literal_values = set() + tuples = [] + for it in items: + lit = self.literal_int_expr(it) + if lit is not None: + literal_values.add(lit) + continue + if self.is_len_of_tuple(it): + assert isinstance(it, CallExpr) + tuples.append(it.args[0]) + if len(literal_values) > 1: + # More than one different literal value found, like 1 == len(x) == 2, + # so the corresponding branch is unreachable. + return [(None, {})] + size = literal_values.pop() + if size > MAX_PRECISE_TUPLE_SIZE: + # Avoid creating huge tuples from checks like if len(x) == 300. + continue + for tpl in tuples: + yes_type, no_type = self.narrow_with_len(self.lookup_type(tpl), op, size) + yes_map = None if yes_type is None else {tpl: yes_type} + no_map = None if no_type is None else {tpl: no_type} + type_maps.append((yes_map, no_map)) + else: + left, right = items + if self.is_len_of_tuple(right): + # Normalize `1 < len(x)` and similar as `len(x) > 1`. + left, right = right, left + op = flip_ops.get(op, op) + r_size = self.literal_int_expr(right) + assert r_size is not None + if r_size > MAX_PRECISE_TUPLE_SIZE: + # Avoid creating huge unions from checks like if len(x) > 300. + continue + assert isinstance(left, CallExpr) + yes_type, no_type = self.narrow_with_len( + self.lookup_type(left.args[0]), op, r_size + ) + yes_map = None if yes_type is None else {left.args[0]: yes_type} + no_map = None if no_type is None else {left.args[0]: no_type} + type_maps.append((yes_map, no_map)) + return type_maps + + def narrow_with_len(self, typ: Type, op: str, size: int) -> tuple[Type | None, Type | None]: + """Dispatch tuple type narrowing logic depending on the kind of type we got.""" + typ = get_proper_type(typ) + if isinstance(typ, TupleType): + return self.refine_tuple_type_with_len(typ, op, size) + elif isinstance(typ, Instance): + return self.refine_instance_type_with_len(typ, op, size) + elif isinstance(typ, UnionType): + yes_types = [] + no_types = [] + other_types = [] + for t in typ.items: + if not self.can_be_narrowed_with_len(t): + other_types.append(t) + continue + yt, nt = self.narrow_with_len(t, op, size) + if yt is not None: + yes_types.append(yt) + if nt is not None: + no_types.append(nt) + yes_types += other_types + no_types += other_types + if yes_types: + yes_type = make_simplified_union(yes_types) + else: + yes_type = None + if no_types: + no_type = make_simplified_union(no_types) + else: + no_type = None + return yes_type, no_type + else: + assert False, "Unsupported type for len narrowing" + + def refine_tuple_type_with_len( + self, typ: TupleType, op: str, size: int + ) -> tuple[Type | None, Type | None]: + """Narrow a TupleType using length restrictions.""" + unpack_index = find_unpack_in_list(typ.items) + if unpack_index is None: + # For fixed length tuple situation is trivial, it is either reachable or not, + # depending on the current length, expected length, and the comparison op. + method = int_op_to_method[op] + if method(typ.length(), size): + return typ, None + return None, typ + unpack = typ.items[unpack_index] + assert isinstance(unpack, UnpackType) + unpacked = get_proper_type(unpack.type) + if isinstance(unpacked, TypeVarTupleType): + # For tuples involving TypeVarTuple unpack we can't do much except + # inferring reachability, and recording the restrictions on TypeVarTuple + # for further "manual" use elsewhere. + min_len = typ.length() - 1 + unpacked.min_len + if op in ("==", "is"): + if min_len <= size: + return typ, typ + return None, typ + elif op in ("<", "<="): + if op == "<=": + size += 1 + if min_len < size: + prefix = typ.items[:unpack_index] + suffix = typ.items[unpack_index + 1 :] + # TODO: also record max_len to avoid false negatives? + unpack = UnpackType(unpacked.copy_modified(min_len=size - typ.length() + 1)) + return typ, typ.copy_modified(items=prefix + [unpack] + suffix) + return None, typ + else: + yes_type, no_type = self.refine_tuple_type_with_len(typ, neg_ops[op], size) + return no_type, yes_type + # Homogeneous variadic item is the case where we are most flexible. Essentially, + # we adjust the variadic item by "eating away" from it to satisfy the restriction. + assert isinstance(unpacked, Instance) and unpacked.type.fullname == "builtins.tuple" + min_len = typ.length() - 1 + arg = unpacked.args[0] + prefix = typ.items[:unpack_index] + suffix = typ.items[unpack_index + 1 :] + if op in ("==", "is"): + if min_len <= size: + # TODO: return fixed union + prefixed variadic tuple for no_type? + return typ.copy_modified(items=prefix + [arg] * (size - min_len) + suffix), typ + return None, typ + elif op in ("<", "<="): + if op == "<=": + size += 1 + if min_len < size: + # Note: there is some ambiguity w.r.t. to where to put the additional + # items: before or after the unpack. However, such types are equivalent, + # so we always put them before for consistency. + no_type = typ.copy_modified( + items=prefix + [arg] * (size - min_len) + [unpack] + suffix + ) + yes_items = [] + for n in range(size - min_len): + yes_items.append(typ.copy_modified(items=prefix + [arg] * n + suffix)) + return UnionType.make_union(yes_items, typ.line, typ.column), no_type + return None, typ + else: + yes_type, no_type = self.refine_tuple_type_with_len(typ, neg_ops[op], size) + return no_type, yes_type + + def refine_instance_type_with_len( + self, typ: Instance, op: str, size: int + ) -> tuple[Type | None, Type | None]: + """Narrow a homogeneous tuple using length restrictions.""" + base = map_instance_to_supertype(typ, self.lookup_typeinfo("builtins.tuple")) + arg = base.args[0] + # Again, we are conservative about subclasses until we gain more confidence. + allow_precise = ( + PRECISE_TUPLE_TYPES in self.options.enable_incomplete_feature + ) and typ.type.fullname == "builtins.tuple" + if op in ("==", "is"): + # TODO: return fixed union + prefixed variadic tuple for no_type? + return TupleType(items=[arg] * size, fallback=typ), typ + elif op in ("<", "<="): + if op == "<=": + size += 1 + if allow_precise: + unpack = UnpackType(self.named_generic_type("builtins.tuple", [arg])) + no_type: Type | None = TupleType(items=[arg] * size + [unpack], fallback=typ) + else: + no_type = typ + if allow_precise: + items = [] + for n in range(size): + items.append(TupleType([arg] * n, fallback=typ)) + yes_type: Type | None = UnionType.make_union(items, typ.line, typ.column) + else: + yes_type = typ + return yes_type, no_type + else: + yes_type, no_type = self.refine_instance_type_with_len(typ, neg_ops[op], size) + return no_type, yes_type + # # Helpers # @@ -5945,8 +6677,7 @@ def check_subtype( notes: list[str] | None = None, code: ErrorCode | None = None, outer_context: Context | None = None, - ) -> bool: - ... + ) -> bool: ... @overload def check_subtype( @@ -5960,8 +6691,7 @@ def check_subtype( *, notes: list[str] | None = None, outer_context: Context | None = None, - ) -> bool: - ... + ) -> bool: ... def check_subtype( self, @@ -5999,7 +6729,9 @@ def check_subtype( note_msg = "" notes = notes or [] if subtype_label is not None or supertype_label is not None: - subtype_str, supertype_str = format_type_distinctly(orig_subtype, orig_supertype) + subtype_str, supertype_str = format_type_distinctly( + orig_subtype, orig_supertype, options=self.options + ) if subtype_label is not None: extra_info.append(subtype_label + " " + subtype_str) if supertype_label is not None: @@ -6034,7 +6766,7 @@ def check_subtype( assert call is not None if not is_subtype(subtype, call, options=self.options): self.msg.note_call(supertype, call, context, code=msg.code) - self.check_possible_missing_await(subtype, supertype, context) + self.check_possible_missing_await(subtype, supertype, context, code=msg.code) return False def get_precise_awaitable_type(self, typ: Type, local_errors: ErrorWatcher) -> Type | None: @@ -6068,7 +6800,7 @@ def checking_await_set(self) -> Iterator[None]: self.checking_missing_await = False def check_possible_missing_await( - self, subtype: Type, supertype: Type, context: Context + self, subtype: Type, supertype: Type, context: Context, code: ErrorCode | None ) -> None: """Check if the given type becomes a subtype when awaited.""" if self.checking_missing_await: @@ -6082,20 +6814,7 @@ def check_possible_missing_await( aw_type, supertype, context, msg=message_registry.INCOMPATIBLE_TYPES ): return - self.msg.possible_missing_await(context) - - def contains_none(self, t: Type) -> bool: - t = get_proper_type(t) - return ( - isinstance(t, NoneType) - or (isinstance(t, UnionType) and any(self.contains_none(ut) for ut in t.items)) - or (isinstance(t, TupleType) and any(self.contains_none(tt) for tt in t.items)) - or ( - isinstance(t, Instance) - and bool(t.args) - and any(self.contains_none(it) for it in t.args) - ) - ) + self.msg.possible_missing_await(context, code) def named_type(self, name: str) -> Instance: """Return an instance type with given name and implicit Any type args. @@ -6192,7 +6911,8 @@ def lookup(self, name: str) -> SymbolTableNode: else: b = self.globals.get("__builtins__", None) if b: - table = cast(MypyFile, b.node).names + assert isinstance(b.node, MypyFile) + table = b.node.names if name in table: return table[name] raise KeyError(f"Failed lookup: {name}") @@ -6206,7 +6926,8 @@ def lookup_qualified(self, name: str) -> SymbolTableNode: for i in range(1, len(parts) - 1): sym = n.names.get(parts[i]) assert sym is not None, "Internal error: attempted lookup of unknown name" - n = cast(MypyFile, sym.node) + assert isinstance(sym.node, MypyFile) + n = sym.node last = parts[-1] if last in n.names: return n.names[last] @@ -6384,23 +7105,18 @@ def note( return self.msg.note(msg, context, offset=offset, code=code) - def iterable_item_type(self, instance: Instance) -> Type: - iterable = map_instance_to_supertype(instance, self.lookup_typeinfo("typing.Iterable")) - item_type = iterable.args[0] - if not isinstance(get_proper_type(item_type), AnyType): - # This relies on 'map_instance_to_supertype' returning 'Iterable[Any]' - # in case there is no explicit base class. - return item_type + def iterable_item_type( + self, it: Instance | CallableType | TypeType | Overloaded, context: Context + ) -> Type: + if isinstance(it, Instance): + iterable = map_instance_to_supertype(it, self.lookup_typeinfo("typing.Iterable")) + item_type = iterable.args[0] + if not isinstance(get_proper_type(item_type), AnyType): + # This relies on 'map_instance_to_supertype' returning 'Iterable[Any]' + # in case there is no explicit base class. + return item_type # Try also structural typing. - iter_type = get_proper_type(find_member("__iter__", instance, instance, is_operator=True)) - if iter_type and isinstance(iter_type, CallableType): - ret_type = get_proper_type(iter_type.ret_type) - if isinstance(ret_type, Instance): - iterator = map_instance_to_supertype( - ret_type, self.lookup_typeinfo("typing.Iterator") - ) - item_type = iterator.args[0] - return item_type + return self.analyze_iterable_item_type_without_expression(it, context)[1] def function_type(self, func: FuncBase) -> FunctionLike: return function_type(func, self.named_type("builtins.function")) @@ -6449,14 +7165,12 @@ def conditional_types_with_intersection( type_ranges: list[TypeRange] | None, ctx: Context, default: None = None, - ) -> tuple[Type | None, Type | None]: - ... + ) -> tuple[Type | None, Type | None]: ... @overload def conditional_types_with_intersection( self, expr_type: Type, type_ranges: list[TypeRange] | None, ctx: Context, default: Type - ) -> tuple[Type, Type]: - ... + ) -> tuple[Type, Type]: ... def conditional_types_with_intersection( self, @@ -6487,9 +7201,10 @@ def conditional_types_with_intersection( possible_target_types = [] for tr in type_ranges: item = get_proper_type(tr.item) - if not isinstance(item, Instance) or tr.is_upper_bound: - return yes_type, no_type - possible_target_types.append(item) + if isinstance(item, (Instance, NoneType)): + possible_target_types.append(item) + if not possible_target_types: + return yes_type, no_type out = [] errors: list[tuple[str, str]] = [] @@ -6497,11 +7212,14 @@ def conditional_types_with_intersection( if not isinstance(v, Instance): return yes_type, no_type for t in possible_target_types: + if isinstance(t, NoneType): + errors.append((f'"{v.type.name}" and "NoneType"', '"NoneType" is final')) + continue intersection = self.intersect_instances((v, t), errors) if intersection is None: continue out.append(intersection) - if len(out) == 0: + if not out: # Only report errors if no element in the union worked. if self.should_report_unreachable_issues(): for types, reason in errors: @@ -6517,7 +7235,8 @@ def is_writable_attribute(self, node: Node) -> bool: return False return True elif isinstance(node, OverloadedFuncDef) and node.is_property: - first_item = cast(Decorator, node.items[0]) + first_item = node.items[0] + assert isinstance(first_item, Decorator) return first_item.var.is_settable_property return False @@ -6539,7 +7258,11 @@ def get_isinstance_type(self, expr: Expression) -> list[TypeRange] | None: elif isinstance(typ, TypeType): # Type[A] means "any type that is a subtype of A" rather than "precisely type A" # we indicate this by setting is_upper_bound flag - types.append(TypeRange(typ.item, is_upper_bound=True)) + is_upper_bound = True + if isinstance(typ.item, NoneType): + # except for Type[None], because "'NoneType' is not an acceptable base type" + is_upper_bound = False + types.append(TypeRange(typ.item, is_upper_bound=is_upper_bound)) elif isinstance(typ, Instance) and typ.type.fullname == "builtins.type": object_type = Instance(typ.type.mro[-1], []) types.append(TypeRange(object_type, is_upper_bound=True)) @@ -6688,6 +7411,9 @@ def has_valid_attribute(self, typ: Type, name: str) -> bool: ) return not watcher.has_new_errors() + def get_expression_type(self, node: Expression, type_context: Type | None = None) -> Type: + return self.expr_checker.accept(node, type_context=type_context) + class CollectArgTypeVarTypes(TypeTraverserVisitor): """Collects the non-nested argument types in a set.""" @@ -6702,15 +7428,13 @@ def visit_type_var(self, t: TypeVarType) -> None: @overload def conditional_types( current_type: Type, proposed_type_ranges: list[TypeRange] | None, default: None = None -) -> tuple[Type | None, Type | None]: - ... +) -> tuple[Type | None, Type | None]: ... @overload def conditional_types( current_type: Type, proposed_type_ranges: list[TypeRange] | None, default: Type -) -> tuple[Type, Type]: - ... +) -> tuple[Type, Type]: ... def conditional_types( @@ -6768,6 +7492,7 @@ def conditional_types( def conditional_types_to_typemaps( expr: Expression, yes_type: Type | None, no_type: Type | None ) -> tuple[TypeMap, TypeMap]: + expr = collapse_walrus(expr) maps: list[TypeMap] = [] for typ in (yes_type, no_type): proper_type = get_proper_type(typ) @@ -6811,6 +7536,22 @@ def is_literal_not_implemented(n: Expression) -> bool: return isinstance(n, NameExpr) and n.fullname == "builtins.NotImplemented" +def _is_empty_generator_function(func: FuncItem) -> bool: + """ + Checks whether a function's body is 'return; yield' (the yield being added only + to promote the function into a generator function). + """ + body = func.body.body + return ( + len(body) == 2 + and isinstance(ret_stmt := body[0], ReturnStmt) + and (ret_stmt.expr is None or is_literal_none(ret_stmt.expr)) + and isinstance(expr_stmt := body[1], ExpressionStmt) + and isinstance(yield_expr := expr_stmt.expr, YieldExpr) + and (yield_expr.expr is None or is_literal_none(yield_expr.expr)) + ) + + def builtin_item_type(tp: Type) -> Type | None: """Get the item type of a builtin container. @@ -6842,10 +7583,22 @@ def builtin_item_type(tp: Type) -> Type | None: return None if not isinstance(get_proper_type(tp.args[0]), AnyType): return tp.args[0] - elif isinstance(tp, TupleType) and all( - not isinstance(it, AnyType) for it in get_proper_types(tp.items) - ): - return make_simplified_union(tp.items) # this type is not externally visible + elif isinstance(tp, TupleType): + normalized_items = [] + for it in tp.items: + # This use case is probably rare, but not handling unpacks here can cause crashes. + if isinstance(it, UnpackType): + unpacked = get_proper_type(it.type) + if isinstance(unpacked, TypeVarTupleType): + unpacked = get_proper_type(unpacked.upper_bound) + assert ( + isinstance(unpacked, Instance) and unpacked.type.fullname == "builtins.tuple" + ) + normalized_items.append(unpacked.args[0]) + else: + normalized_items.append(it) + if all(not isinstance(it, AnyType) for it in get_proper_types(normalized_items)): + return make_simplified_union(normalized_items) # this type is not externally visible elif isinstance(tp, TypedDictType): # TypedDict always has non-optional string keys. Find the key type from the Mapping # base class. @@ -6856,7 +7609,7 @@ def builtin_item_type(tp: Type) -> Type | None: return None -def and_conditional_maps(m1: TypeMap, m2: TypeMap) -> TypeMap: +def and_conditional_maps(m1: TypeMap, m2: TypeMap, use_meet: bool = False) -> TypeMap: """Calculate what information we can learn from the truth of (e1 and e2) in terms of the information that we can learn from the truth of e1 and the truth of e2. @@ -6866,22 +7619,31 @@ def and_conditional_maps(m1: TypeMap, m2: TypeMap) -> TypeMap: # One of the conditions can never be true. return None # Both conditions can be true; combine the information. Anything - # we learn from either conditions's truth is valid. If the same + # we learn from either conditions' truth is valid. If the same # expression's type is refined by both conditions, we somewhat - # arbitrarily give precedence to m2. (In the future, we could use - # an intersection type.) + # arbitrarily give precedence to m2 unless m1 value is Any. + # In the future, we could use an intersection type or meet_types(). result = m2.copy() m2_keys = {literal_hash(n2) for n2 in m2} for n1 in m1: - if literal_hash(n1) not in m2_keys: + if literal_hash(n1) not in m2_keys or isinstance(get_proper_type(m1[n1]), AnyType): result[n1] = m1[n1] + if use_meet: + # For now, meet common keys only if specifically requested. + # This is currently used for tuple types narrowing, where having + # a precise result is important. + for n1 in m1: + for n2 in m2: + if literal_hash(n1) == literal_hash(n2): + result[n1] = meet_types(m1[n1], m2[n2]) return result -def or_conditional_maps(m1: TypeMap, m2: TypeMap) -> TypeMap: +def or_conditional_maps(m1: TypeMap, m2: TypeMap, coalesce_any: bool = False) -> TypeMap: """Calculate what information we can learn from the truth of (e1 or e2) in terms of the information that we can learn from the truth of e1 and - the truth of e2. + the truth of e2. If coalesce_any is True, consider Any a supertype when + joining restrictions. """ if m1 is None: @@ -6896,11 +7658,16 @@ def or_conditional_maps(m1: TypeMap, m2: TypeMap) -> TypeMap: for n1 in m1: for n2 in m2: if literal_hash(n1) == literal_hash(n2): - result[n1] = make_simplified_union([m1[n1], m2[n2]]) + if coalesce_any and isinstance(get_proper_type(m1[n1]), AnyType): + result[n1] = m1[n1] + else: + result[n1] = make_simplified_union([m1[n1], m2[n2]]) return result -def reduce_conditional_maps(type_maps: list[tuple[TypeMap, TypeMap]]) -> tuple[TypeMap, TypeMap]: +def reduce_conditional_maps( + type_maps: list[tuple[TypeMap, TypeMap]], use_meet: bool = False +) -> tuple[TypeMap, TypeMap]: """Reduces a list containing pairs of if/else TypeMaps into a single pair. We "and" together all of the if TypeMaps and "or" together the else TypeMaps. So @@ -6931,7 +7698,7 @@ def reduce_conditional_maps(type_maps: list[tuple[TypeMap, TypeMap]]) -> tuple[T else: final_if_map, final_else_map = type_maps[0] for if_map, else_map in type_maps[1:]: - final_if_map = and_conditional_maps(final_if_map, if_map) + final_if_map = and_conditional_maps(final_if_map, if_map, use_meet=use_meet) final_else_map = or_conditional_maps(final_else_map, else_map) return final_if_map, final_else_map @@ -6946,7 +7713,7 @@ def convert_to_typetype(type_map: TypeMap) -> TypeMap: if isinstance(t, TypeVarType): t = t.upper_bound # TODO: should we only allow unions of instances as per PEP 484? - if not isinstance(get_proper_type(t), (UnionType, Instance)): + if not isinstance(get_proper_type(t), (UnionType, Instance, NoneType)): # unknown type; error was likely reported earlier return {} converted_type_map[expr] = TypeType.make_normalized(typ) @@ -6955,7 +7722,7 @@ def convert_to_typetype(type_map: TypeMap) -> TypeMap: def flatten(t: Expression) -> list[Expression]: """Flatten a nested sequence of tuples/lists into one list of nodes.""" - if isinstance(t, TupleExpr) or isinstance(t, ListExpr): + if isinstance(t, (TupleExpr, ListExpr)): return [b for a in t.items for b in flatten(a)] elif isinstance(t, StarExpr): return flatten(t.expr) @@ -6968,6 +7735,8 @@ def flatten_types(t: Type) -> list[Type]: t = get_proper_type(t) if isinstance(t, TupleType): return [b for a in t.items for b in flatten_types(a)] + elif is_named_instance(t, "builtins.tuple"): + return [t.args[0]] else: return [t] @@ -6996,7 +7765,7 @@ def are_argument_counts_overlapping(t: CallableType, s: CallableType) -> bool: def is_unsafe_overlapping_overload_signatures( - signature: CallableType, other: CallableType + signature: CallableType, other: CallableType, class_type_vars: list[TypeVarLikeType] ) -> bool: """Check if two overloaded signatures are unsafely overlapping or partially overlapping. @@ -7015,8 +7784,8 @@ def is_unsafe_overlapping_overload_signatures( # This lets us identify cases where the two signatures use completely # incompatible types -- e.g. see the testOverloadingInferUnionReturnWithMixedTypevars # test case. - signature = detach_callable(signature) - other = detach_callable(other) + signature = detach_callable(signature, class_type_vars) + other = detach_callable(other, class_type_vars) # Note: We repeat this check twice in both directions due to a slight # asymmetry in 'is_callable_compatible'. When checking for partial overlaps, @@ -7028,26 +7797,38 @@ def is_unsafe_overlapping_overload_signatures( # # This discrepancy is unfortunately difficult to get rid of, so we repeat the # checks twice in both directions for now. + # + # Note that we ignore possible overlap between type variables and None. This + # is technically unsafe, but unsafety is tiny and this prevents some common + # use cases like: + # @overload + # def foo(x: None) -> None: .. + # @overload + # def foo(x: T) -> Foo[T]: ... return is_callable_compatible( signature, other, - is_compat=is_overlapping_types_no_promote_no_uninhabited, + is_compat=is_overlapping_types_no_promote_no_uninhabited_no_none, + is_proper_subtype=False, is_compat_return=lambda l, r: not is_subtype_no_promote(l, r), ignore_return=False, check_args_covariantly=True, allow_partial_overlap=True, + no_unify_none=True, ) or is_callable_compatible( other, signature, - is_compat=is_overlapping_types_no_promote_no_uninhabited, + is_compat=is_overlapping_types_no_promote_no_uninhabited_no_none, + is_proper_subtype=False, is_compat_return=lambda l, r: not is_subtype_no_promote(r, l), ignore_return=False, check_args_covariantly=False, allow_partial_overlap=True, + no_unify_none=True, ) -def detach_callable(typ: CallableType) -> CallableType: +def detach_callable(typ: CallableType, class_type_vars: list[TypeVarLikeType]) -> CallableType: """Ensures that the callable's type variables are 'detached' and independent of the context. A callable normally keeps track of the type variables it uses within its 'variables' field. @@ -7057,41 +7838,17 @@ def detach_callable(typ: CallableType) -> CallableType: This function will traverse the callable and find all used type vars and add them to the variables field if it isn't already present. - The caller can then unify on all type variables whether or not the callable is originally - from a class or not.""" - type_list = typ.arg_types + [typ.ret_type] - - appear_map: dict[str, list[int]] = {} - for i, inner_type in enumerate(type_list): - typevars_available = get_type_vars(inner_type) - for var in typevars_available: - if var.fullname not in appear_map: - appear_map[var.fullname] = [] - appear_map[var.fullname].append(i) - - used_type_var_names = set() - for var_name, appearances in appear_map.items(): - used_type_var_names.add(var_name) - - all_type_vars = get_type_vars(typ) - new_variables = [] - for var in set(all_type_vars): - if var.fullname not in used_type_var_names: - continue - new_variables.append( - TypeVarType( - name=var.name, - fullname=var.fullname, - id=var.id, - values=var.values, - upper_bound=var.upper_bound, - variance=var.variance, - ) - ) - out = typ.copy_modified( - variables=new_variables, arg_types=type_list[:-1], ret_type=type_list[-1] + The caller can then unify on all type variables whether the callable is originally from + the class or not.""" + if not class_type_vars: + # Fast path, nothing to update. + return typ + seen_type_vars = set() + for t in typ.arg_types + [typ.ret_type]: + seen_type_vars |= set(get_type_vars(t)) + return typ.copy_modified( + variables=list(typ.variables) + [tv for tv in class_type_vars if tv in seen_type_vars] ) - return out def overload_can_never_match(signature: CallableType, other: CallableType) -> bool: @@ -7112,9 +7869,8 @@ def overload_can_never_match(signature: CallableType, other: CallableType) -> bo exp_signature = expand_type( signature, {tvar.id: erase_def_to_union_or_bound(tvar) for tvar in signature.variables} ) - assert isinstance(exp_signature, CallableType) return is_callable_compatible( - exp_signature, other, is_compat=is_more_precise, ignore_return=True + exp_signature, other, is_compat=is_more_precise, is_proper_subtype=True, ignore_return=True ) @@ -7124,7 +7880,9 @@ def is_more_general_arg_prefix(t: FunctionLike, s: FunctionLike) -> bool: # general than one with fewer items (or just one item)? if isinstance(t, CallableType): if isinstance(s, CallableType): - return is_callable_compatible(t, s, is_compat=is_proper_subtype, ignore_return=True) + return is_callable_compatible( + t, s, is_compat=is_proper_subtype, is_proper_subtype=True, ignore_return=True + ) elif isinstance(t, FunctionLike): if isinstance(s, FunctionLike): if len(t.items) == len(s.items): @@ -7139,6 +7897,7 @@ def is_same_arg_prefix(t: CallableType, s: CallableType) -> bool: t, s, is_compat=is_same_type, + is_proper_subtype=True, ignore_return=True, check_args_covariantly=True, ignore_pos_arg_names=True, @@ -7153,14 +7912,25 @@ def infer_operator_assignment_method(typ: Type, operator: str) -> tuple[bool, st """ typ = get_proper_type(typ) method = operators.op_methods[operator] + existing_method = None if isinstance(typ, Instance): - if operator in operators.ops_with_inplace_method: - inplace_method = "__i" + method[2:] - if typ.type.has_readable_member(inplace_method): - return True, inplace_method + existing_method = _find_inplace_method(typ, method, operator) + elif isinstance(typ, TypedDictType): + existing_method = _find_inplace_method(typ.fallback, method, operator) + + if existing_method is not None: + return True, existing_method return False, method +def _find_inplace_method(inst: Instance, method: str, operator: str) -> str | None: + if operator in operators.ops_with_inplace_method: + inplace_method = "__i" + method[2:] + if inst.type.has_readable_member(inplace_method): + return inplace_method + return None + + def is_valid_inferred_type(typ: Type, is_lvalue_final: bool = False) -> bool: """Is an inferred type valid and needs no further refinement? @@ -7189,7 +7959,7 @@ def is_valid_inferred_type(typ: Type, is_lvalue_final: bool = False) -> bool: class InvalidInferredTypes(BoolTypeQuery): """Find type components that are not valid for an inferred type. - These include type, and any types resulting from failed + These include type, and any uninhabited types resulting from failed (ambiguous) type inference. """ @@ -7203,9 +7973,14 @@ def visit_erased_type(self, t: ErasedType) -> bool: # This can happen inside a lambda. return True + def visit_type_var(self, t: TypeVarType) -> bool: + # This is needed to prevent leaking into partial types during + # multi-step type inference. + return t.id.is_meta_var() + class SetNothingToAny(TypeTranslator): - """Replace all ambiguous types with Any (to avoid spurious extra errors).""" + """Replace all ambiguous Uninhabited types with Any (to avoid spurious extra errors).""" def visit_uninhabited_type(self, t: UninhabitedType) -> Type: if t.ambiguous: @@ -7213,7 +7988,7 @@ def visit_uninhabited_type(self, t: UninhabitedType) -> Type: return t def visit_type_alias_type(self, t: TypeAliasType) -> Type: - # Target of the alias cannot be an ambiguous , so we just + # Target of the alias cannot be an ambiguous UninhabitedType, so we just # replace the arguments. return t.copy_modified(args=[a.accept(self) for a in t.args]) @@ -7340,7 +8115,7 @@ def add_mapping(self, keys: set[TKey], values: set[TValue]) -> None: Note that the given set of keys must be non-empty -- otherwise, nothing happens. """ - if len(keys) == 0: + if not keys: return subtree_roots = [self._lookup_or_make_root_id(key) for key in keys] @@ -7451,7 +8226,7 @@ def group_comparison_operands( if current_indices and (operator != last_operator or operator not in operators_to_group): # If some of the operands in the chain are assignable, defer adding it: we might # end up needing to merge it with other chains that appear later. - if len(current_hashes) == 0: + if not current_hashes: simplified_operator_list.append((last_operator, sorted(current_indices))) else: groups[last_operator].add_mapping(current_hashes, current_indices) @@ -7474,7 +8249,7 @@ def group_comparison_operands( current_hashes.add(right_hash) if last_operator is not None: - if len(current_hashes) == 0: + if not current_hashes: simplified_operator_list.append((last_operator, sorted(current_indices))) else: groups[last_operator].add_mapping(current_hashes, current_indices) @@ -7554,12 +8329,18 @@ def is_subtype_no_promote(left: Type, right: Type) -> bool: return is_subtype(left, right, ignore_promotions=True) -def is_overlapping_types_no_promote_no_uninhabited(left: Type, right: Type) -> bool: - # For the purpose of unsafe overload checks we consider list[] and list[int] +def is_overlapping_types_no_promote_no_uninhabited_no_none(left: Type, right: Type) -> bool: + # For the purpose of unsafe overload checks we consider list[Never] and list[int] # non-overlapping. This is consistent with how we treat list[int] and list[str] as # non-overlapping, despite [] belongs to both. Also this will prevent false positives # for failed type inference during unification. - return is_overlapping_types(left, right, ignore_promotions=True, ignore_uninhabited=True) + return is_overlapping_types( + left, + right, + ignore_promotions=True, + ignore_uninhabited=True, + prohibit_none_typevar_overlap=True, + ) def is_private(node_name: str) -> bool: @@ -7590,3 +8371,80 @@ def collapse_walrus(e: Expression) -> Expression: if isinstance(e, AssignmentExpr): return e.target return e + + +def find_last_var_assignment_line(n: Node, v: Var) -> int: + """Find the highest line number of a potential assignment to variable within node. + + This supports local and global variables. + + Return -1 if no assignment was found. + """ + visitor = VarAssignVisitor(v) + n.accept(visitor) + return visitor.last_line + + +class VarAssignVisitor(TraverserVisitor): + def __init__(self, v: Var) -> None: + self.last_line = -1 + self.lvalue = False + self.var_node = v + + def visit_assignment_stmt(self, s: AssignmentStmt) -> None: + self.lvalue = True + for lv in s.lvalues: + lv.accept(self) + self.lvalue = False + + def visit_name_expr(self, e: NameExpr) -> None: + if self.lvalue and e.node is self.var_node: + self.last_line = max(self.last_line, e.line) + + def visit_member_expr(self, e: MemberExpr) -> None: + old_lvalue = self.lvalue + self.lvalue = False + super().visit_member_expr(e) + self.lvalue = old_lvalue + + def visit_index_expr(self, e: IndexExpr) -> None: + old_lvalue = self.lvalue + self.lvalue = False + super().visit_index_expr(e) + self.lvalue = old_lvalue + + def visit_with_stmt(self, s: WithStmt) -> None: + self.lvalue = True + for lv in s.target: + if lv is not None: + lv.accept(self) + self.lvalue = False + s.body.accept(self) + + def visit_for_stmt(self, s: ForStmt) -> None: + self.lvalue = True + s.index.accept(self) + self.lvalue = False + s.body.accept(self) + if s.else_body: + s.else_body.accept(self) + + def visit_assignment_expr(self, e: AssignmentExpr) -> None: + self.lvalue = True + e.target.accept(self) + self.lvalue = False + e.value.accept(self) + + def visit_as_pattern(self, p: AsPattern) -> None: + if p.pattern is not None: + p.pattern.accept(self) + if p.name is not None: + self.lvalue = True + p.name.accept(self) + self.lvalue = False + + def visit_starred_pattern(self, p: StarredPattern) -> None: + if p.capture is not None: + self.lvalue = True + p.capture.accept(self) + self.lvalue = False diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 38b5c2419d95..e8a2e501a452 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2,27 +2,34 @@ from __future__ import annotations +import enum import itertools import time +from collections import defaultdict from contextlib import contextmanager -from typing import Callable, ClassVar, Iterator, List, Optional, Sequence, cast -from typing_extensions import Final, TypeAlias as _TypeAlias, overload +from typing import Callable, ClassVar, Final, Iterable, Iterator, List, Optional, Sequence, cast +from typing_extensions import TypeAlias as _TypeAlias, assert_never, overload import mypy.checker import mypy.errorcodes as codes from mypy import applytype, erasetype, join, message_registry, nodes, operators, types from mypy.argmap import ArgTypeExpander, map_actuals_to_formals, map_formals_to_actuals -from mypy.checkmember import analyze_member_access, type_object_type +from mypy.checkmember import analyze_member_access, freeze_all_type_vars, type_object_type from mypy.checkstrformat import StringFormatterChecker from mypy.erasetype import erase_type, remove_instance_last_known_values, replace_meta_vars from mypy.errors import ErrorWatcher, report_internal_error -from mypy.expandtype import expand_type, expand_type_by_instance, freshen_function_type_vars +from mypy.expandtype import ( + expand_type, + expand_type_by_instance, + freshen_all_functions_type_vars, + freshen_function_type_vars, +) from mypy.infer import ArgumentInferContext, infer_function_type_arguments, infer_type_arguments from mypy.literals import literal from mypy.maptype import map_instance_to_supertype from mypy.meet import is_overlapping_types, narrow_declared_type from mypy.message_registry import ErrorMessage -from mypy.messages import MessageBuilder +from mypy.messages import MessageBuilder, format_type from mypy.nodes import ( ARG_NAMED, ARG_POS, @@ -30,6 +37,7 @@ ARG_STAR2, IMPLICITLY_ABSTRACT, LITERAL_TYPE, + REVEAL_LOCALS, REVEAL_TYPE, ArgKind, AssertTypeExpr, @@ -89,6 +97,7 @@ YieldExpr, YieldFromExpr, ) +from mypy.options import PRECISE_TUPLE_TYPES from mypy.plugin import ( FunctionContext, FunctionSigContext, @@ -98,14 +107,23 @@ ) from mypy.semanal_enum import ENUM_BASES from mypy.state import state -from mypy.subtypes import is_equivalent, is_same_type, is_subtype, non_method_protocol_members +from mypy.subtypes import ( + find_member, + is_equivalent, + is_same_type, + is_subtype, + non_method_protocol_members, +) from mypy.traverser import has_await_expression +from mypy.type_visitor import TypeTranslator from mypy.typeanal import ( check_for_explicit_any, - expand_type_alias, + fix_instance, has_any_from_unimported_type, + instantiate_type_alias, make_optional_type, set_any_tvars, + validate_instance, ) from mypy.typeops import ( callable_type, @@ -114,6 +132,8 @@ false_only, fixup_partial_type, function_type, + get_all_type_vars, + get_type_vars, is_literal_type_like, make_simplified_union, simple_literal_type, @@ -136,6 +156,7 @@ LiteralValue, NoneType, Overloaded, + Parameters, ParamSpecFlavor, ParamSpecType, PartialType, @@ -146,31 +167,38 @@ TypedDictType, TypeOfAny, TypeType, + TypeVarLikeType, TypeVarTupleType, TypeVarType, + UnboundType, UninhabitedType, UnionType, UnpackType, + find_unpack_in_list, + flatten_nested_tuples, flatten_nested_unions, get_proper_type, get_proper_types, has_recursive_types, - is_generic_instance, is_named_instance, - is_optional, + remove_dups, + split_with_prefix_and_suffix, +) +from mypy.types_utils import ( + is_generic_instance, + is_overlapping_none, is_self_type_like, remove_optional, ) from mypy.typestate import type_state from mypy.typevars import fill_typevars -from mypy.typevartuples import find_unpack_in_list from mypy.util import split_module_names from mypy.visitor import ExpressionVisitor # Type of callback user for checking individual function arguments. See # check_args() below for details. ArgChecker: _TypeAlias = Callable[ - [Type, Type, ArgKind, Type, int, int, CallableType, Optional[Type], Context, Context], None, + [Type, Type, ArgKind, Type, int, int, CallableType, Optional[Type], Context, Context], None ] # Maximum nesting level for math union in overloads, setting this to large values @@ -250,6 +278,20 @@ class Finished(Exception): """Raised if we can terminate overload argument check early (no match).""" +@enum.unique +class UseReverse(enum.Enum): + """Used in `visit_op_expr` to enable or disable reverse method checks.""" + + DEFAULT = 0 + ALWAYS = 1 + NEVER = 2 + + +USE_REVERSE_DEFAULT: Final = UseReverse.DEFAULT +USE_REVERSE_ALWAYS: Final = UseReverse.ALWAYS +USE_REVERSE_NEVER: Final = UseReverse.NEVER + + class ExpressionChecker(ExpressionVisitor[Type]): """Expression type checker. @@ -301,6 +343,7 @@ def __init__( # on whether current expression is a callee, to give better error messages # related to type context. self.is_callee = False + type_state.infer_polymorphic = not self.chk.options.old_type_inference def reset(self) -> None: self.resolved_type = {} @@ -330,17 +373,21 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: elif isinstance(node, FuncDef): # Reference to a global function. result = function_type(node, self.named_type("builtins.function")) - elif isinstance(node, OverloadedFuncDef) and node.type is not None: - # node.type is None when there are multiple definitions of a function - # and it's decorated by something that is not typing.overload - # TODO: use a dummy Overloaded instead of AnyType in this case - # like we do in mypy.types.function_type()? - result = node.type + elif isinstance(node, OverloadedFuncDef): + if node.type is None: + if self.chk.in_checked_function() and node.items: + self.chk.handle_cannot_determine_type(node.name, e) + result = AnyType(TypeOfAny.from_error) + else: + result = node.type elif isinstance(node, TypeInfo): # Reference to a type object. if node.typeddict_type: # We special-case TypedDict, because they don't define any constructor. result = self.typeddict_callable(node) + elif node.fullname == "types.NoneType": + # We special case NoneType, because its stub definition is not related to None. + result = TypeType(NoneType()) else: result = type_object_type(node, self.named_type) if isinstance(result, CallableType) and isinstance( # type: ignore[misc] @@ -366,7 +413,7 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: result = self.alias_type_in_runtime_context( node, ctx=e, alias_definition=e.is_alias_rvalue or lvalue ) - elif isinstance(node, (TypeVarExpr, ParamSpecExpr)): + elif isinstance(node, (TypeVarExpr, ParamSpecExpr, TypeVarTupleExpr)): result = self.object_type() else: if isinstance(node, PlaceholderNode): @@ -467,13 +514,13 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) -> if is_expr_literal_type(typ): self.msg.cannot_use_function_with_type(e.callee.name, "Literal", e) continue - if ( - node - and isinstance(node.node, TypeAlias) - and isinstance(get_proper_type(node.node.target), AnyType) - ): - self.msg.cannot_use_function_with_type(e.callee.name, "Any", e) - continue + if node and isinstance(node.node, TypeAlias): + target = get_proper_type(node.node.target) + if isinstance(target, AnyType): + self.msg.cannot_use_function_with_type(e.callee.name, "Any", e) + continue + if isinstance(target, NoneType): + continue if ( isinstance(typ, IndexExpr) and isinstance(typ.analyzed, (TypeApplication, TypeAliasExpr)) @@ -514,13 +561,6 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) -> callee_type = get_proper_type( self.accept(e.callee, type_context, always_allow_any=True, is_callee=True) ) - if ( - self.chk.options.disallow_untyped_calls - and self.chk.in_checked_function() - and isinstance(callee_type, CallableType) - and callee_type.implicit - ): - self.msg.untyped_function_call(callee_type, e) # Figure out the full name of the callee for plugin lookup. object_type = None @@ -546,6 +586,22 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) -> ): member = e.callee.name object_type = self.chk.lookup_type(e.callee.expr) + + if ( + self.chk.options.disallow_untyped_calls + and self.chk.in_checked_function() + and isinstance(callee_type, CallableType) + and callee_type.implicit + ): + if fullname is None and member is not None: + assert object_type is not None + fullname = self.method_fullname(object_type, member) + if not fullname or not any( + fullname == p or fullname.startswith(f"{p}.") + for p in self.chk.options.untyped_calls_exclude + ): + self.msg.untyped_function_call(callee_type, e) + ret_type = self.check_call_expr_with_callee_type( callee_type, e, fullname, object_type, member ) @@ -580,7 +636,17 @@ def check_str_format_call(self, e: CallExpr) -> None: if isinstance(e.callee.expr, StrExpr): format_value = e.callee.expr.value elif self.chk.has_type(e.callee.expr): - base_typ = try_getting_literal(self.chk.lookup_type(e.callee.expr)) + typ = get_proper_type(self.chk.lookup_type(e.callee.expr)) + if ( + isinstance(typ, Instance) + and typ.type.is_enum + and isinstance(typ.last_known_value, LiteralType) + and isinstance(typ.last_known_value.value, str) + ): + value_type = typ.type.names[typ.last_known_value.value].type + if isinstance(value_type, Type): + typ = get_proper_type(value_type) + base_typ = try_getting_literal(typ) if isinstance(base_typ, LiteralType) and isinstance(base_typ.value, str): format_value = base_typ.value if format_value is not None: @@ -662,7 +728,7 @@ def check_runtime_protocol_test(self, e: CallExpr) -> None: for expr in mypy.checker.flatten(e.args[1]): tp = get_proper_type(self.chk.lookup_type(expr)) if ( - isinstance(tp, CallableType) + isinstance(tp, FunctionLike) and tp.is_type_obj() and tp.type_object().is_protocol and not tp.type_object().runtime_protocol @@ -672,7 +738,7 @@ def check_runtime_protocol_test(self, e: CallExpr) -> None: def check_protocol_issubclass(self, e: CallExpr) -> None: for expr in mypy.checker.flatten(e.args[1]): tp = get_proper_type(self.chk.lookup_type(expr)) - if isinstance(tp, CallableType) and tp.is_type_obj() and tp.type_object().is_protocol: + if isinstance(tp, FunctionLike) and tp.is_type_obj() and tp.type_object().is_protocol: attr_members = non_method_protocol_members(tp.type_object()) if attr_members: self.chk.msg.report_non_method_protocol(tp.type_object(), attr_members, e) @@ -686,74 +752,182 @@ def check_typeddict_call( context: Context, orig_callee: Type | None, ) -> Type: - if len(args) >= 1 and all([ak == ARG_NAMED for ak in arg_kinds]): - # ex: Point(x=42, y=1337) - assert all(arg_name is not None for arg_name in arg_names) - item_names = cast(List[str], arg_names) - item_args = args - return self.check_typeddict_call_with_kwargs( - callee, dict(zip(item_names, item_args)), context, orig_callee - ) + if args and all(ak in (ARG_NAMED, ARG_STAR2) for ak in arg_kinds): + # ex: Point(x=42, y=1337, **extras) + # This is a bit ugly, but this is a price for supporting all possible syntax + # variants for TypedDict constructors. + kwargs = zip([StrExpr(n) if n is not None else None for n in arg_names], args) + result = self.validate_typeddict_kwargs(kwargs=kwargs, callee=callee) + if result is not None: + validated_kwargs, always_present_keys = result + return self.check_typeddict_call_with_kwargs( + callee, validated_kwargs, context, orig_callee, always_present_keys + ) + return AnyType(TypeOfAny.from_error) if len(args) == 1 and arg_kinds[0] == ARG_POS: unique_arg = args[0] if isinstance(unique_arg, DictExpr): - # ex: Point({'x': 42, 'y': 1337}) + # ex: Point({'x': 42, 'y': 1337, **extras}) return self.check_typeddict_call_with_dict( - callee, unique_arg, context, orig_callee + callee, unique_arg.items, context, orig_callee ) if isinstance(unique_arg, CallExpr) and isinstance(unique_arg.analyzed, DictExpr): - # ex: Point(dict(x=42, y=1337)) + # ex: Point(dict(x=42, y=1337, **extras)) return self.check_typeddict_call_with_dict( - callee, unique_arg.analyzed, context, orig_callee + callee, unique_arg.analyzed.items, context, orig_callee ) - if len(args) == 0: + if not args: # ex: EmptyDict() - return self.check_typeddict_call_with_kwargs(callee, {}, context, orig_callee) + return self.check_typeddict_call_with_kwargs(callee, {}, context, orig_callee, set()) self.chk.fail(message_registry.INVALID_TYPEDDICT_ARGS, context) return AnyType(TypeOfAny.from_error) - def validate_typeddict_kwargs(self, kwargs: DictExpr) -> dict[str, Expression] | None: - item_args = [item[1] for item in kwargs.items] - - item_names = [] # List[str] - for item_name_expr, item_arg in kwargs.items: - literal_value = None + def validate_typeddict_kwargs( + self, kwargs: Iterable[tuple[Expression | None, Expression]], callee: TypedDictType + ) -> tuple[dict[str, list[Expression]], set[str]] | None: + # All (actual or mapped from ** unpacks) expressions that can match given key. + result = defaultdict(list) + # Keys that are guaranteed to be present no matter what (e.g. for all items of a union) + always_present_keys = set() + # Indicates latest encountered ** unpack among items. + last_star_found = None + + for item_name_expr, item_arg in kwargs: if item_name_expr: key_type = self.accept(item_name_expr) values = try_getting_str_literals(item_name_expr, key_type) + literal_value = None if values and len(values) == 1: literal_value = values[0] - if literal_value is None: - key_context = item_name_expr or item_arg - self.chk.fail( - message_registry.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, - key_context, - code=codes.LITERAL_REQ, - ) - return None + if literal_value is None: + key_context = item_name_expr or item_arg + self.chk.fail( + message_registry.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, + key_context, + code=codes.LITERAL_REQ, + ) + return None + else: + # A directly present key unconditionally shadows all previously found + # values from ** items. + # TODO: for duplicate keys, type-check all values. + result[literal_value] = [item_arg] + always_present_keys.add(literal_value) + else: + last_star_found = item_arg + if not self.validate_star_typeddict_item( + item_arg, callee, result, always_present_keys + ): + return None + if self.chk.options.extra_checks and last_star_found is not None: + absent_keys = [] + for key in callee.items: + if key not in callee.required_keys and key not in result: + absent_keys.append(key) + if absent_keys: + # Having an optional key not explicitly declared by a ** unpacked + # TypedDict is unsafe, it may be an (incompatible) subtype at runtime. + # TODO: catch the cases where a declared key is overridden by a subsequent + # ** item without it (and not again overriden with complete ** item). + self.msg.non_required_keys_absent_with_star(absent_keys, last_star_found) + return result, always_present_keys + + def validate_star_typeddict_item( + self, + item_arg: Expression, + callee: TypedDictType, + result: dict[str, list[Expression]], + always_present_keys: set[str], + ) -> bool: + """Update keys/expressions from a ** expression in TypedDict constructor. + + Note `result` and `always_present_keys` are updated in place. Return true if the + expression `item_arg` may valid in `callee` TypedDict context. + """ + inferred = get_proper_type(self.accept(item_arg, type_context=callee)) + possible_tds = [] + if isinstance(inferred, TypedDictType): + possible_tds = [inferred] + elif isinstance(inferred, UnionType): + for item in get_proper_types(inferred.relevant_items()): + if isinstance(item, TypedDictType): + possible_tds.append(item) + elif not self.valid_unpack_fallback_item(item): + self.msg.unsupported_target_for_star_typeddict(item, item_arg) + return False + elif not self.valid_unpack_fallback_item(inferred): + self.msg.unsupported_target_for_star_typeddict(inferred, item_arg) + return False + all_keys: set[str] = set() + for td in possible_tds: + all_keys |= td.items.keys() + for key in all_keys: + arg = TempNode( + UnionType.make_union([td.items[key] for td in possible_tds if key in td.items]) + ) + arg.set_line(item_arg) + if all(key in td.required_keys for td in possible_tds): + always_present_keys.add(key) + # Always present keys override previously found values. This is done + # to support use cases like `Config({**defaults, **overrides})`, where + # some `overrides` types are narrower that types in `defaults`, and + # former are too wide for `Config`. + if result[key]: + first = result[key][0] + if not isinstance(first, TempNode): + # We must always preserve any non-synthetic values, so that + # we will accept them even if they are shadowed. + result[key] = [first, arg] + else: + result[key] = [arg] + else: + result[key] = [arg] else: - item_names.append(literal_value) - return dict(zip(item_names, item_args)) + # If this key is not required at least in some item of a union + # it may not shadow previous item, so we need to type check both. + result[key].append(arg) + return True + + def valid_unpack_fallback_item(self, typ: ProperType) -> bool: + if isinstance(typ, AnyType): + return True + if not isinstance(typ, Instance) or not typ.type.has_base("typing.Mapping"): + return False + mapped = map_instance_to_supertype(typ, self.chk.lookup_typeinfo("typing.Mapping")) + return all(isinstance(a, AnyType) for a in get_proper_types(mapped.args)) def match_typeddict_call_with_dict( - self, callee: TypedDictType, kwargs: DictExpr, context: Context + self, + callee: TypedDictType, + kwargs: list[tuple[Expression | None, Expression]], + context: Context, ) -> bool: - validated_kwargs = self.validate_typeddict_kwargs(kwargs=kwargs) - if validated_kwargs is not None: + result = self.validate_typeddict_kwargs(kwargs=kwargs, callee=callee) + if result is not None: + validated_kwargs, _ = result return callee.required_keys <= set(validated_kwargs.keys()) <= set(callee.items.keys()) else: return False def check_typeddict_call_with_dict( - self, callee: TypedDictType, kwargs: DictExpr, context: Context, orig_callee: Type | None + self, + callee: TypedDictType, + kwargs: list[tuple[Expression | None, Expression]], + context: Context, + orig_callee: Type | None, ) -> Type: - validated_kwargs = self.validate_typeddict_kwargs(kwargs=kwargs) - if validated_kwargs is not None: + result = self.validate_typeddict_kwargs(kwargs=kwargs, callee=callee) + if result is not None: + validated_kwargs, always_present_keys = result return self.check_typeddict_call_with_kwargs( - callee, kwargs=validated_kwargs, context=context, orig_callee=orig_callee + callee, + kwargs=validated_kwargs, + context=context, + orig_callee=orig_callee, + always_present_keys=always_present_keys, ) else: return AnyType(TypeOfAny.from_error) @@ -785,7 +959,10 @@ def typeddict_callable(self, info: TypeInfo) -> CallableType: def typeddict_callable_from_context(self, callee: TypedDictType) -> CallableType: return CallableType( list(callee.items.values()), - [ArgKind.ARG_NAMED] * len(callee.items), + [ + ArgKind.ARG_NAMED if name in callee.required_keys else ArgKind.ARG_NAMED_OPT + for name in callee.items + ], list(callee.items.keys()), callee, self.named_type("builtins.type"), @@ -794,20 +971,37 @@ def typeddict_callable_from_context(self, callee: TypedDictType) -> CallableType def check_typeddict_call_with_kwargs( self, callee: TypedDictType, - kwargs: dict[str, Expression], + kwargs: dict[str, list[Expression]], context: Context, orig_callee: Type | None, + always_present_keys: set[str], ) -> Type: actual_keys = kwargs.keys() - if not (callee.required_keys <= actual_keys <= callee.items.keys()): - expected_keys = [ - key - for key in callee.items.keys() - if key in callee.required_keys or key in actual_keys - ] - self.msg.unexpected_typeddict_keys( - callee, expected_keys=expected_keys, actual_keys=list(actual_keys), context=context - ) + if not ( + callee.required_keys <= always_present_keys and actual_keys <= callee.items.keys() + ): + if not (actual_keys <= callee.items.keys()): + self.msg.unexpected_typeddict_keys( + callee, + expected_keys=[ + key + for key in callee.items.keys() + if key in callee.required_keys or key in actual_keys + ], + actual_keys=list(actual_keys), + context=context, + ) + if not (callee.required_keys <= always_present_keys): + self.msg.unexpected_typeddict_keys( + callee, + expected_keys=[ + key for key in callee.items.keys() if key in callee.required_keys + ], + actual_keys=[ + key for key in always_present_keys if key in callee.required_keys + ], + context=context, + ) if callee.required_keys > actual_keys: # found_set is a sub-set of the required_keys # This means we're missing some keys and as such, we can't @@ -830,7 +1024,10 @@ def check_typeddict_call_with_kwargs( with self.msg.filter_errors(), self.chk.local_type_map(): orig_ret_type, _ = self.check_callable_call( infer_callee, - list(kwargs.values()), + # We use first expression for each key to infer type variables of a generic + # TypedDict. This is a bit arbitrary, but in most cases will work better than + # trying to infer a union or a join. + [args[0] for args in kwargs.values()], [ArgKind.ARG_NAMED] * len(kwargs), context, list(kwargs.keys()), @@ -845,19 +1042,20 @@ def check_typeddict_call_with_kwargs( # this may give a better error message. ret_type = callee - for (item_name, item_expected_type) in ret_type.items.items(): + for item_name, item_expected_type in ret_type.items.items(): if item_name in kwargs: - item_value = kwargs[item_name] - self.chk.check_simple_assignment( - lvalue_type=item_expected_type, - rvalue=item_value, - context=item_value, - msg=ErrorMessage( - message_registry.INCOMPATIBLE_TYPES.value, code=codes.TYPEDDICT_ITEM - ), - lvalue_name=f'TypedDict item "{item_name}"', - rvalue_name="expression", - ) + item_values = kwargs[item_name] + for item_value in item_values: + self.chk.check_simple_assignment( + lvalue_type=item_expected_type, + rvalue=item_value, + context=item_value, + msg=ErrorMessage( + message_registry.INCOMPATIBLE_TYPES.value, code=codes.TYPEDDICT_ITEM + ), + lvalue_name=f'TypedDict item "{item_name}"', + rvalue_name="expression", + ) return orig_ret_type @@ -1175,6 +1373,55 @@ def transform_callee_type( return callee + def is_generic_decorator_overload_call( + self, callee_type: CallableType, args: list[Expression] + ) -> Overloaded | None: + """Check if this looks like an application of a generic function to overload argument.""" + assert callee_type.variables + if len(callee_type.arg_types) != 1 or len(args) != 1: + # TODO: can we handle more general cases? + return None + if not isinstance(get_proper_type(callee_type.arg_types[0]), CallableType): + return None + if not isinstance(get_proper_type(callee_type.ret_type), CallableType): + return None + with self.chk.local_type_map(): + with self.msg.filter_errors(): + arg_type = get_proper_type(self.accept(args[0], type_context=None)) + if isinstance(arg_type, Overloaded): + return arg_type + return None + + def handle_decorator_overload_call( + self, callee_type: CallableType, overloaded: Overloaded, ctx: Context + ) -> tuple[Type, Type] | None: + """Type-check application of a generic callable to an overload. + + We check call on each individual overload item, and then combine results into a new + overload. This function should be only used if callee_type takes and returns a Callable. + """ + result = [] + inferred_args = [] + for item in overloaded.items: + arg = TempNode(typ=item) + with self.msg.filter_errors() as err: + item_result, inferred_arg = self.check_call(callee_type, [arg], [ARG_POS], ctx) + if err.has_new_errors(): + # This overload doesn't match. + continue + p_item_result = get_proper_type(item_result) + if not isinstance(p_item_result, CallableType): + continue + p_inferred_arg = get_proper_type(inferred_arg) + if not isinstance(p_inferred_arg, CallableType): + continue + inferred_args.append(p_inferred_arg) + result.append(p_item_result) + if not result or not inferred_args: + # None of the overload matched (or overload was initially malformed). + return None + return Overloaded(result), Overloaded(inferred_args) + def check_call_expr_with_callee_type( self, callee_type: Type, @@ -1217,13 +1464,12 @@ def check_call_expr_with_callee_type( object_type=object_type, ) proper_callee = get_proper_type(callee_type) - if ( - isinstance(e.callee, RefExpr) - and isinstance(proper_callee, CallableType) - and proper_callee.type_guard is not None - ): + if isinstance(e.callee, RefExpr) and isinstance(proper_callee, CallableType): # Cache it for find_isinstance_check() - e.callee.type_guard = proper_callee.type_guard + if proper_callee.type_guard is not None: + e.callee.type_guard = proper_callee.type_guard + if proper_callee.type_is is not None: + e.callee.type_is = proper_callee.type_is return ret_type def check_union_call_expr(self, e: CallExpr, object_type: UnionType, member: str) -> Type: @@ -1265,6 +1511,7 @@ def check_call( callable_node: Expression | None = None, callable_name: str | None = None, object_type: Type | None = None, + original_type: Type | None = None, ) -> tuple[Type, Type]: """Type check a call. @@ -1289,6 +1536,17 @@ def check_call( callee = get_proper_type(callee) if isinstance(callee, CallableType): + if callee.variables: + overloaded = self.is_generic_decorator_overload_call(callee, args) + if overloaded is not None: + # Special casing for inline application of generic callables to overloads. + # Supporting general case would be tricky, but this should cover 95% of cases. + overloaded_result = self.handle_decorator_overload_call( + callee, overloaded, context + ) + if overloaded_result is not None: + return overloaded_result + return self.check_callable_call( callee, args, @@ -1316,7 +1574,7 @@ def check_call( is_super=False, is_operator=True, msg=self.msg, - original_type=callee, + original_type=original_type or callee, chk=self.chk, in_literal_context=self.is_literal_context(), ) @@ -1357,6 +1615,7 @@ def check_call( callable_node, callable_name, object_type, + original_type=callee, ) else: return self.msg.not_callable(callee, context), AnyType(TypeOfAny.from_error) @@ -1377,7 +1636,7 @@ def check_callable_call( See the docstring of check_call for more information. """ # Always unpack **kwargs before checking a call. - callee = callee.with_unpacked_kwargs() + callee = callee.with_unpacked_kwargs().with_normalized_var_args() if callable_name is None and callee.name: callable_name = callee.name ret_type = get_proper_type(callee.ret_type) @@ -1417,6 +1676,27 @@ def check_callable_call( callee.type_object().name, abstract_attributes, context ) + var_arg = callee.var_arg() + if var_arg and isinstance(var_arg.typ, UnpackType): + # It is hard to support multiple variadic unpacks (except for old-style *args: int), + # fail gracefully to avoid crashes later. + seen_unpack = False + for arg, arg_kind in zip(args, arg_kinds): + if arg_kind != ARG_STAR: + continue + arg_type = get_proper_type(self.accept(arg)) + if not isinstance(arg_type, TupleType) or any( + isinstance(t, UnpackType) for t in arg_type.items + ): + if seen_unpack: + self.msg.fail( + "Passing multiple variadic unpacks in a call is not supported", + context, + code=codes.CALL_ARG, + ) + return AnyType(TypeOfAny.from_error), callee + seen_unpack = True + formal_to_actual = map_actuals_to_formals( arg_kinds, arg_names, @@ -1425,6 +1705,16 @@ def check_callable_call( lambda i: self.accept(args[i]), ) + # This is tricky: return type may contain its own type variables, like in + # def [S] (S) -> def [T] (T) -> tuple[S, T], so we need to update their ids + # to avoid possible id clashes if this call itself appears in a generic + # function body. + ret_type = get_proper_type(callee.ret_type) + if isinstance(ret_type, CallableType) and ret_type.variables: + fresh_ret_type = freshen_all_functions_type_vars(callee.ret_type) + freeze_all_type_vars(fresh_ret_type) + callee = callee.copy_modified(ret_type=fresh_ret_type) + if callee.is_generic(): need_refresh = any( isinstance(v, (ParamSpecType, TypeVarTupleType)) for v in callee.variables @@ -1443,7 +1733,7 @@ def check_callable_call( lambda i: self.accept(args[i]), ) callee = self.infer_function_type_arguments( - callee, args, arg_kinds, formal_to_actual, context + callee, args, arg_kinds, arg_names, formal_to_actual, need_refresh, context ) if need_refresh: formal_to_actual = map_actuals_to_formals( @@ -1577,6 +1867,8 @@ def analyze_type_type_callee(self, item: ProperType, context: Context) -> Type: # We support Type of namedtuples but not of tuples in general if isinstance(item, TupleType) and tuple_fallback(item).type.fullname != "builtins.tuple": return self.analyze_type_type_callee(tuple_fallback(item), context) + if isinstance(item, TypedDictType): + return self.typeddict_callable_from_context(item) self.msg.unsupported_type_type(item, context) return AnyType(TypeOfAny.from_error) @@ -1669,7 +1961,7 @@ def infer_function_type_arguments_using_context( # valid results. erased_ctx = replace_meta_vars(ctx, ErasedType()) ret_type = callable.ret_type - if is_optional(ret_type) and is_optional(ctx): + if is_overlapping_none(ret_type) and is_overlapping_none(ctx): # If both the context and the return type are optional, unwrap the optional, # since in 99% cases this is what a user expects. In other words, we replace # Optional[T] <: Optional[int] @@ -1710,9 +2002,13 @@ def infer_function_type_arguments_using_context( # def identity(x: T) -> T: return x # # expects_literal(identity(3)) # Should type-check + # TODO: we may want to add similar exception if all arguments are lambdas, since + # in this case external context is almost everything we have. if not is_generic_instance(ctx) and not is_literal_type_like(ctx): return callable.copy_modified() - args = infer_type_arguments(callable.type_var_ids(), ret_type, erased_ctx) + args = infer_type_arguments( + callable.variables, ret_type, erased_ctx, skip_unsatisfied=True + ) # Only substitute non-Uninhabited and non-erased types. new_args: list[Type | None] = [] for arg in args: @@ -1731,7 +2027,9 @@ def infer_function_type_arguments( callee_type: CallableType, args: list[Expression], arg_kinds: list[ArgKind], + arg_names: Sequence[str | None] | None, formal_to_actual: list[list[int]], + need_refresh: bool, context: Context, ) -> CallableType: """Infer the type arguments for a generic callee type. @@ -1751,7 +2049,7 @@ def infer_function_type_arguments( ) arg_pass_nums = self.get_arg_infer_passes( - callee_type.arg_types, formal_to_actual, len(args) + callee_type, args, arg_types, formal_to_actual, len(args) ) pass1_args: list[Type | None] = [] @@ -1761,10 +2059,11 @@ def infer_function_type_arguments( else: pass1_args.append(arg) - inferred_args = infer_function_type_arguments( + inferred_args, _ = infer_function_type_arguments( callee_type, pass1_args, arg_kinds, + arg_names, formal_to_actual, context=self.argument_infer_context(), strict=self.chk.in_checked_function(), @@ -1773,7 +2072,14 @@ def infer_function_type_arguments( if 2 in arg_pass_nums: # Second pass of type inference. (callee_type, inferred_args) = self.infer_function_type_arguments_pass2( - callee_type, args, arg_kinds, formal_to_actual, inferred_args, context + callee_type, + args, + arg_kinds, + arg_names, + formal_to_actual, + inferred_args, + need_refresh, + context, ) if ( @@ -1792,6 +2098,63 @@ def infer_function_type_arguments( inferred_args[0] = self.named_type("builtins.str") elif not first_arg or not is_subtype(self.named_type("builtins.str"), first_arg): self.chk.fail(message_registry.KEYWORD_ARGUMENT_REQUIRES_STR_KEY_TYPE, context) + + if not self.chk.options.old_type_inference and any( + a is None + or isinstance(get_proper_type(a), UninhabitedType) + or set(get_type_vars(a)) & set(callee_type.variables) + for a in inferred_args + ): + if need_refresh: + # Technically we need to refresh formal_to_actual after *each* inference pass, + # since each pass can expand ParamSpec or TypeVarTuple. Although such situations + # are very rare, not doing this can cause crashes. + formal_to_actual = map_actuals_to_formals( + arg_kinds, + arg_names, + callee_type.arg_kinds, + callee_type.arg_names, + lambda a: self.accept(args[a]), + ) + # If the regular two-phase inference didn't work, try inferring type + # variables while allowing for polymorphic solutions, i.e. for solutions + # potentially involving free variables. + # TODO: support the similar inference for return type context. + poly_inferred_args, free_vars = infer_function_type_arguments( + callee_type, + arg_types, + arg_kinds, + arg_names, + formal_to_actual, + context=self.argument_infer_context(), + strict=self.chk.in_checked_function(), + allow_polymorphic=True, + ) + poly_callee_type = self.apply_generic_arguments( + callee_type, poly_inferred_args, context + ) + # Try applying inferred polymorphic type if possible, e.g. Callable[[T], T] can + # be interpreted as def [T] (T) -> T, but dict[T, T] cannot be expressed. + applied = apply_poly(poly_callee_type, free_vars) + if applied is not None and all( + a is not None and not isinstance(get_proper_type(a), UninhabitedType) + for a in poly_inferred_args + ): + freeze_all_type_vars(applied) + return applied + # If it didn't work, erase free variables as uninhabited, to avoid confusing errors. + unknown = UninhabitedType() + unknown.ambiguous = True + inferred_args = [ + ( + expand_type( + a, {v.id: unknown for v in list(callee_type.variables) + free_vars} + ) + if a is not None + else None + ) + for a in poly_inferred_args + ] else: # In dynamically typed functions use implicit 'Any' types for # type variables. @@ -1803,8 +2166,10 @@ def infer_function_type_arguments_pass2( callee_type: CallableType, args: list[Expression], arg_kinds: list[ArgKind], + arg_names: Sequence[str | None] | None, formal_to_actual: list[list[int]], old_inferred_args: Sequence[Type | None], + need_refresh: bool, context: Context, ) -> tuple[CallableType, list[Type | None]]: """Perform second pass of generic function type argument inference. @@ -1826,13 +2191,26 @@ def infer_function_type_arguments_pass2( if isinstance(arg, (NoneType, UninhabitedType)) or has_erased_component(arg): inferred_args[i] = None callee_type = self.apply_generic_arguments(callee_type, inferred_args, context) + if need_refresh: + formal_to_actual = map_actuals_to_formals( + arg_kinds, + arg_names, + callee_type.arg_kinds, + callee_type.arg_names, + lambda a: self.accept(args[a]), + ) - arg_types = self.infer_arg_types_in_context(callee_type, args, arg_kinds, formal_to_actual) + # Same as during first pass, disable type errors (we still have partial context). + with self.msg.filter_errors(): + arg_types = self.infer_arg_types_in_context( + callee_type, args, arg_kinds, formal_to_actual + ) - inferred_args = infer_function_type_arguments( + inferred_args, _ = infer_function_type_arguments( callee_type, arg_types, arg_kinds, + arg_names, formal_to_actual, context=self.argument_infer_context(), ) @@ -1845,7 +2223,12 @@ def argument_infer_context(self) -> ArgumentInferContext: ) def get_arg_infer_passes( - self, arg_types: list[Type], formal_to_actual: list[list[int]], num_actuals: int + self, + callee: CallableType, + args: list[Expression], + arg_types: list[Type], + formal_to_actual: list[list[int]], + num_actuals: int, ) -> list[int]: """Return pass numbers for args for two-pass argument type inference. @@ -1856,8 +2239,32 @@ def get_arg_infer_passes( lambdas more effectively. """ res = [1] * num_actuals - for i, arg in enumerate(arg_types): - if arg.accept(ArgInferSecondPassQuery()): + for i, arg in enumerate(callee.arg_types): + skip_param_spec = False + p_formal = get_proper_type(callee.arg_types[i]) + if isinstance(p_formal, CallableType) and p_formal.param_spec(): + for j in formal_to_actual[i]: + p_actual = get_proper_type(arg_types[j]) + # This is an exception from the usual logic where we put generic Callable + # arguments in the second pass. If we have a non-generic actual, it is + # likely to infer good constraints, for example if we have: + # def run(Callable[P, None], *args: P.args, **kwargs: P.kwargs) -> None: ... + # def test(x: int, y: int) -> int: ... + # run(test, 1, 2) + # we will use `test` for inference, since it will allow to infer also + # argument *names* for P <: [x: int, y: int]. + if isinstance(p_actual, Instance): + call_method = find_member("__call__", p_actual, p_actual, is_operator=True) + if call_method is not None: + p_actual = get_proper_type(call_method) + if ( + isinstance(p_actual, CallableType) + and not p_actual.variables + and not isinstance(args[j], LambdaExpr) + ): + skip_param_spec = True + break + if not skip_param_spec and arg.accept(ArgInferSecondPassQuery()): for j in formal_to_actual[i]: res[j] = 2 return res @@ -2052,30 +2459,28 @@ def check_argument_types( # the suffices to the tuple, e.g. a single actual like # Tuple[Unpack[Ts], int] expanded_tuple = False + actual_kinds = [arg_kinds[a] for a in actuals] if len(actuals) > 1: - first_actual_arg_type = get_proper_type(arg_types[actuals[0]]) + p_actual_type = get_proper_type(arg_types[actuals[0]]) if ( - isinstance(first_actual_arg_type, TupleType) - and len(first_actual_arg_type.items) == 1 - and isinstance(get_proper_type(first_actual_arg_type.items[0]), UnpackType) + isinstance(p_actual_type, TupleType) + and len(p_actual_type.items) == 1 + and isinstance(p_actual_type.items[0], UnpackType) + and actual_kinds == [nodes.ARG_STAR] + [nodes.ARG_POS] * (len(actuals) - 1) ): - # TODO: use walrus operator - actual_types = [first_actual_arg_type.items[0]] + [ - arg_types[a] for a in actuals[1:] - ] - actual_kinds = [nodes.ARG_STAR] + [nodes.ARG_POS] * (len(actuals) - 1) - - assert isinstance(orig_callee_arg_type, TupleType) - assert orig_callee_arg_type.items - callee_arg_types = orig_callee_arg_type.items - callee_arg_kinds = [nodes.ARG_STAR] + [nodes.ARG_POS] * ( - len(orig_callee_arg_type.items) - 1 - ) - expanded_tuple = True + actual_types = [p_actual_type.items[0]] + [arg_types[a] for a in actuals[1:]] + if isinstance(orig_callee_arg_type, UnpackType): + p_callee_type = get_proper_type(orig_callee_arg_type.type) + if isinstance(p_callee_type, TupleType): + assert p_callee_type.items + callee_arg_types = p_callee_type.items + callee_arg_kinds = [nodes.ARG_STAR] + [nodes.ARG_POS] * ( + len(p_callee_type.items) - 1 + ) + expanded_tuple = True if not expanded_tuple: actual_types = [arg_types[a] for a in actuals] - actual_kinds = [arg_kinds[a] for a in actuals] if isinstance(orig_callee_arg_type, UnpackType): unpacked_type = get_proper_type(orig_callee_arg_type.type) if isinstance(unpacked_type, TupleType): @@ -2084,19 +2489,31 @@ def check_argument_types( callee_arg_types = unpacked_type.items callee_arg_kinds = [ARG_POS] * len(actuals) else: - inner_unpack = get_proper_type(unpacked_type.items[inner_unpack_index]) + inner_unpack = unpacked_type.items[inner_unpack_index] assert isinstance(inner_unpack, UnpackType) inner_unpacked_type = get_proper_type(inner_unpack.type) - # We assume heterogenous tuples are desugared earlier - assert isinstance(inner_unpacked_type, Instance) - assert inner_unpacked_type.type.fullname == "builtins.tuple" - callee_arg_types = ( - unpacked_type.items[:inner_unpack_index] - + [inner_unpacked_type.args[0]] - * (len(actuals) - len(unpacked_type.items) + 1) - + unpacked_type.items[inner_unpack_index + 1 :] - ) - callee_arg_kinds = [ARG_POS] * len(actuals) + if isinstance(inner_unpacked_type, TypeVarTupleType): + # This branch mimics the expanded_tuple case above but for + # the case where caller passed a single * unpacked tuple argument. + callee_arg_types = unpacked_type.items + callee_arg_kinds = [ + ARG_POS if i != inner_unpack_index else ARG_STAR + for i in range(len(unpacked_type.items)) + ] + else: + # We assume heterogeneous tuples are desugared earlier. + assert isinstance(inner_unpacked_type, Instance) + assert inner_unpacked_type.type.fullname == "builtins.tuple" + callee_arg_types = ( + unpacked_type.items[:inner_unpack_index] + + [inner_unpacked_type.args[0]] + * (len(actuals) - len(unpacked_type.items) + 1) + + unpacked_type.items[inner_unpack_index + 1 :] + ) + callee_arg_kinds = [ARG_POS] * len(actuals) + elif isinstance(unpacked_type, TypeVarTupleType): + callee_arg_types = [orig_callee_arg_type] + callee_arg_kinds = [ARG_STAR] else: assert isinstance(unpacked_type, Instance) assert unpacked_type.type.fullname == "builtins.tuple" @@ -2109,8 +2526,10 @@ def check_argument_types( assert len(actual_types) == len(actuals) == len(actual_kinds) if len(callee_arg_types) != len(actual_types): - # TODO: Improve error message - self.chk.fail("Invalid number of arguments", context) + if len(actual_types) > len(callee_arg_types): + self.chk.msg.too_many_arguments(callee, context) + else: + self.chk.msg.too_few_arguments(callee, context, None) continue assert len(callee_arg_types) == len(actual_types) @@ -2126,10 +2545,16 @@ def check_argument_types( if actual_kind == nodes.ARG_STAR2 and not self.is_valid_keyword_var_arg( actual_type ): - is_mapping = is_subtype(actual_type, self.chk.named_type("typing.Mapping")) + is_mapping = is_subtype( + actual_type, self.chk.named_type("_typeshed.SupportsKeysAndGetItem") + ) self.msg.invalid_keyword_var_arg(actual_type, is_mapping, context) expanded_actual = mapper.expand_actual_type( - actual_type, actual_kind, callee.arg_names[i], callee_arg_kind + actual_type, + actual_kind, + callee.arg_names[i], + callee_arg_kind, + allow_unpack=isinstance(callee_arg_type, UnpackType), ) check_arg( expanded_actual, @@ -2165,15 +2590,7 @@ def check_arg( if isinstance(caller_type, DeletedType): self.msg.deleted_as_rvalue(caller_type, context) # Only non-abstract non-protocol class can be given where Type[...] is expected... - elif ( - isinstance(caller_type, CallableType) - and isinstance(callee_type, TypeType) - and caller_type.is_type_obj() - and (caller_type.type_object().is_abstract or caller_type.type_object().is_protocol) - and isinstance(callee_type.item, Instance) - and (callee_type.item.type.is_abstract or callee_type.item.type.is_protocol) - and not self.chk.allow_abstract_call - ): + elif self.has_abstract_type_part(caller_type, callee_type): self.msg.concrete_only_call(callee_type, context) elif not is_subtype(caller_type, callee_type, options=self.chk.options): code = self.msg.incompatible_argument( @@ -2190,7 +2607,7 @@ def check_arg( original_caller_type, callee_type, context, code=code ) if not self.msg.prefer_simple_messages(): - self.chk.check_possible_missing_await(caller_type, callee_type, context) + self.chk.check_possible_missing_await(caller_type, callee_type, context, code) def check_overload_call( self, @@ -2218,6 +2635,11 @@ def check_overload_call( # typevar. See https://github.com/python/mypy/issues/4063 for related discussion. erased_targets: list[CallableType] | None = None unioned_result: tuple[Type, Type] | None = None + + # Determine whether we need to encourage union math. This should be generally safe, + # as union math infers better results in the vast majority of cases, but it is very + # computationally intensive. + none_type_var_overlap = self.possible_none_type_var_overlap(arg_types, plausible_targets) union_interrupted = False # did we try all union combinations? if any(self.real_union(arg) for arg in arg_types): try: @@ -2230,6 +2652,7 @@ def check_overload_call( arg_names, callable_name, object_type, + none_type_var_overlap, context, ) except TooManyUnions: @@ -2262,8 +2685,10 @@ def check_overload_call( # If any of checks succeed, stop early. if inferred_result is not None and unioned_result is not None: # Both unioned and direct checks succeeded, choose the more precise type. - if is_subtype(inferred_result[0], unioned_result[0]) and not isinstance( - get_proper_type(inferred_result[0]), AnyType + if ( + is_subtype(inferred_result[0], unioned_result[0]) + and not isinstance(get_proper_type(inferred_result[0]), AnyType) + and not none_type_var_overlap ): return inferred_result return unioned_result @@ -2313,7 +2738,8 @@ def check_overload_call( callable_name=callable_name, object_type=object_type, ) - if union_interrupted: + # Do not show the extra error if the union math was forced. + if union_interrupted and not none_type_var_overlap: self.chk.fail(message_registry.TOO_MANY_UNION_COMBINATIONS, context) return result @@ -2337,10 +2763,8 @@ def plausible_overload_call_targets( def has_shape(typ: Type) -> bool: typ = get_proper_type(typ) - return ( - isinstance(typ, TupleType) - or isinstance(typ, TypedDictType) - or (isinstance(typ, Instance) and typ.type.is_named_tuple) + return isinstance(typ, (TupleType, TypedDictType)) or ( + isinstance(typ, Instance) and typ.type.is_named_tuple ) matches: list[CallableType] = [] @@ -2414,17 +2838,23 @@ def infer_overload_return_type( ) is_match = not w.has_new_errors() if is_match: - # Return early if possible; otherwise record info so we can + # Return early if possible; otherwise record info, so we can # check for ambiguity due to 'Any' below. if not args_contain_any: + self.chk.store_types(m) return ret_type, infer_type - matches.append(typ) + p_infer_type = get_proper_type(infer_type) + if isinstance(p_infer_type, CallableType): + # Prefer inferred types if possible, this will avoid false triggers for + # Any-ambiguity caused by arguments with Any passed to generic overloads. + matches.append(p_infer_type) + else: + matches.append(typ) return_types.append(ret_type) inferred_types.append(infer_type) type_maps.append(m) - if len(matches) == 0: - # No match was found + if not matches: return None elif any_causes_overload_ambiguity(matches, return_types, arg_types, arg_kinds, arg_names): # An argument of type or containing the type 'Any' caused ambiguity. @@ -2471,6 +2901,44 @@ def overload_erased_call_targets( matches.append(typ) return matches + def possible_none_type_var_overlap( + self, arg_types: list[Type], plausible_targets: list[CallableType] + ) -> bool: + """Heuristic to determine whether we need to try forcing union math. + + This is needed to avoid greedy type variable match in situations like this: + @overload + def foo(x: None) -> None: ... + @overload + def foo(x: T) -> list[T]: ... + + x: int | None + foo(x) + we want this call to infer list[int] | None, not list[int | None]. + """ + if not plausible_targets or not arg_types: + return False + has_optional_arg = False + for arg_type in get_proper_types(arg_types): + if not isinstance(arg_type, UnionType): + continue + for item in get_proper_types(arg_type.items): + if isinstance(item, NoneType): + has_optional_arg = True + break + if not has_optional_arg: + return False + + min_prefix = min(len(c.arg_types) for c in plausible_targets) + for i in range(min_prefix): + if any( + isinstance(get_proper_type(c.arg_types[i]), NoneType) for c in plausible_targets + ) and any( + isinstance(get_proper_type(c.arg_types[i]), TypeVarType) for c in plausible_targets + ): + return True + return False + def union_overload_result( self, plausible_targets: list[CallableType], @@ -2480,6 +2948,7 @@ def union_overload_result( arg_names: Sequence[str | None] | None, callable_name: str | None, object_type: Type | None, + none_type_var_overlap: bool, context: Context, level: int = 0, ) -> list[tuple[Type, Type]] | None: @@ -2519,20 +2988,23 @@ def union_overload_result( # Step 3: Try a direct match before splitting to avoid unnecessary union splits # and save performance. - with self.type_overrides_set(args, arg_types): - direct = self.infer_overload_return_type( - plausible_targets, - args, - arg_types, - arg_kinds, - arg_names, - callable_name, - object_type, - context, - ) - if direct is not None and not isinstance(get_proper_type(direct[0]), (UnionType, AnyType)): - # We only return non-unions soon, to avoid greedy match. - return [direct] + if not none_type_var_overlap: + with self.type_overrides_set(args, arg_types): + direct = self.infer_overload_return_type( + plausible_targets, + args, + arg_types, + arg_kinds, + arg_names, + callable_name, + object_type, + context, + ) + if direct is not None and not isinstance( + get_proper_type(direct[0]), (UnionType, AnyType) + ): + # We only return non-unions soon, to avoid greedy match. + return [direct] # Step 4: Split the first remaining union type in arguments into items and # try to match each item individually (recursive). @@ -2550,6 +3022,7 @@ def union_overload_result( arg_names, callable_name, object_type, + none_type_var_overlap, context, level + 1, ) @@ -2857,6 +3330,7 @@ def infer_literal_expr_type(self, value: LiteralValue, fallback_name: str) -> Ty def concat_tuples(self, left: TupleType, right: TupleType) -> TupleType: """Concatenate two fixed length tuples.""" + assert not (find_unpack_in_list(left.items) and find_unpack_in_list(right.items)) return TupleType( items=left.items + right.items, fallback=self.named_type("builtins.tuple") ) @@ -2896,7 +3370,7 @@ def visit_op_expr(self, e: OpExpr) -> Type: # Expressions of form [...] * e get special type inference. return self.check_list_multiply(e) if e.op == "%": - if isinstance(e.left, BytesExpr) and self.chk.options.python_version >= (3, 5): + if isinstance(e.left, BytesExpr): return self.strfrm_checker.check_str_interpolation(e.left, e.right) if isinstance(e.left, StrExpr): return self.strfrm_checker.check_str_interpolation(e.left, e.right) @@ -2910,11 +3384,85 @@ def visit_op_expr(self, e: OpExpr) -> Type: if isinstance(proper_right_type, TupleType): right_radd_method = proper_right_type.partial_fallback.type.get("__radd__") if right_radd_method is None: - return self.concat_tuples(proper_left_type, proper_right_type) + # One cannot have two variadic items in the same tuple. + if ( + find_unpack_in_list(proper_left_type.items) is None + or find_unpack_in_list(proper_right_type.items) is None + ): + return self.concat_tuples(proper_left_type, proper_right_type) + elif ( + PRECISE_TUPLE_TYPES in self.chk.options.enable_incomplete_feature + and isinstance(proper_right_type, Instance) + and self.chk.type_is_iterable(proper_right_type) + ): + # Handle tuple[X, Y] + tuple[Z, ...] = tuple[X, Y, *tuple[Z, ...]]. + right_radd_method = proper_right_type.type.get("__radd__") + if ( + right_radd_method is None + and proper_left_type.partial_fallback.type.fullname == "builtins.tuple" + and find_unpack_in_list(proper_left_type.items) is None + ): + item_type = self.chk.iterable_item_type(proper_right_type, e) + mapped = self.chk.named_generic_type("builtins.tuple", [item_type]) + return proper_left_type.copy_modified( + items=proper_left_type.items + [UnpackType(mapped)] + ) + + use_reverse: UseReverse = USE_REVERSE_DEFAULT + if e.op == "|": + if is_named_instance(proper_left_type, "builtins.dict"): + # This is a special case for `dict | TypedDict`. + # 1. Find `dict | TypedDict` case + # 2. Switch `dict.__or__` to `TypedDict.__ror__` (the same from both runtime and typing perspective) + proper_right_type = get_proper_type(self.accept(e.right)) + if isinstance(proper_right_type, TypedDictType): + use_reverse = USE_REVERSE_ALWAYS + if isinstance(proper_left_type, TypedDictType): + # This is the reverse case: `TypedDict | dict`, + # simply do not allow the reverse checking: + # do not call `__dict__.__ror__`. + proper_right_type = get_proper_type(self.accept(e.right)) + if is_named_instance(proper_right_type, "builtins.dict"): + use_reverse = USE_REVERSE_NEVER + + if PRECISE_TUPLE_TYPES in self.chk.options.enable_incomplete_feature: + # Handle tuple[X, ...] + tuple[Y, Z] = tuple[*tuple[X, ...], Y, Z]. + if ( + e.op == "+" + and isinstance(proper_left_type, Instance) + and proper_left_type.type.fullname == "builtins.tuple" + ): + proper_right_type = get_proper_type(self.accept(e.right)) + if ( + isinstance(proper_right_type, TupleType) + and proper_right_type.partial_fallback.type.fullname == "builtins.tuple" + and find_unpack_in_list(proper_right_type.items) is None + ): + return proper_right_type.copy_modified( + items=[UnpackType(proper_left_type)] + proper_right_type.items + ) if e.op in operators.op_methods: method = operators.op_methods[e.op] - result, method_type = self.check_op(method, left_type, e.right, e, allow_reverse=True) + if use_reverse is UseReverse.DEFAULT or use_reverse is UseReverse.NEVER: + result, method_type = self.check_op( + method, + base_type=left_type, + arg=e.right, + context=e, + allow_reverse=use_reverse is UseReverse.DEFAULT, + ) + elif use_reverse is UseReverse.ALWAYS: + result, method_type = self.check_op( + # The reverse operator here gives better error messages: + operators.reverse_op_methods[method], + base_type=self.accept(e.right), + arg=e.left, + context=e, + allow_reverse=False, + ) + else: + assert_never(use_reverse) e.method_type = method_type return result else: @@ -3015,7 +3563,7 @@ def visit_comparison_expr(self, e: ComparisonExpr) -> Type: if not encountered_partial_type and not failed_out: iterable_type = UnionType.make_union(iterable_types) if not is_subtype(left_type, iterable_type): - if len(container_types) == 0: + if not container_types: self.msg.unsupported_operand_types("in", left_type, right_type, e) else: container_type = UnionType.make_union(container_types) @@ -3089,8 +3637,9 @@ def dangerous_comparison( self, left: Type, right: Type, - original_container: Type | None = None, *, + original_container: Type | None = None, + seen_types: set[tuple[Type, Type]] | None = None, prefer_literal: bool = True, ) -> bool: """Check for dangerous non-overlapping comparisons like 42 == 'no'. @@ -3111,6 +3660,12 @@ def dangerous_comparison( if not self.chk.options.strict_equality: return False + if seen_types is None: + seen_types = set() + if (left, right) in seen_types: + return False + seen_types.add((left, right)) + left, right = get_proper_types((left, right)) # We suppress the error if there is a custom __eq__() method on either @@ -3166,9 +3721,21 @@ def dangerous_comparison( abstract_set = self.chk.lookup_typeinfo("typing.AbstractSet") left = map_instance_to_supertype(left, abstract_set) right = map_instance_to_supertype(right, abstract_set) - return self.dangerous_comparison(left.args[0], right.args[0]) + return self.dangerous_comparison( + left.args[0], right.args[0], seen_types=seen_types + ) + elif left.type.has_base("typing.Mapping") and right.type.has_base("typing.Mapping"): + # Similar to above: Mapping ignores the classes, it just compares items. + abstract_map = self.chk.lookup_typeinfo("typing.Mapping") + left = map_instance_to_supertype(left, abstract_map) + right = map_instance_to_supertype(right, abstract_map) + return self.dangerous_comparison( + left.args[0], right.args[0], seen_types=seen_types + ) or self.dangerous_comparison(left.args[1], right.args[1], seen_types=seen_types) elif left_name in ("builtins.list", "builtins.tuple") and right_name == left_name: - return self.dangerous_comparison(left.args[0], right.args[0]) + return self.dangerous_comparison( + left.args[0], right.args[0], seen_types=seen_types + ) elif left_name in OVERLAPPING_BYTES_ALLOWLIST and right_name in ( OVERLAPPING_BYTES_ALLOWLIST ): @@ -3470,9 +4037,7 @@ def check_op( left_variants = [base_type] base_type = get_proper_type(base_type) if isinstance(base_type, UnionType): - left_variants = [ - item for item in flatten_nested_unions(base_type.relevant_items()) - ] + left_variants = list(flatten_nested_unions(base_type.relevant_items())) right_type = self.accept(arg) # Step 1: We first try leaving the right arguments alone and destructure @@ -3665,7 +4230,10 @@ def visit_assignment_expr(self, e: AssignmentExpr) -> Type: value = self.accept(e.value) self.chk.check_assignment(e.target, e.value) self.chk.check_final(e) - self.chk.store_type(e.target, value) + if not has_uninhabited_component(value): + # TODO: can we get rid of this extra store_type()? + # Usually, check_assignment() already stores the lvalue type correctly. + self.chk.store_type(e.target, value) self.find_partial_type_ref_fast_path(e.target) return value @@ -3717,6 +4285,12 @@ def visit_index_with_type( # Visit the index, just to make sure we have a type for it available self.accept(index) + if isinstance(left_type, TupleType) and any( + isinstance(it, UnpackType) for it in left_type.items + ): + # Normalize variadic tuples for consistency. + left_type = expand_type(left_type, {}) + if isinstance(left_type, UnionType): original_type = original_type or left_type # Don't combine literal types, since we may need them for type narrowing. @@ -3737,12 +4311,14 @@ def visit_index_with_type( if ns is not None: out = [] for n in ns: - if n < 0: - n += len(left_type.items) - if 0 <= n < len(left_type.items): - out.append(left_type.items[n]) + item = self.visit_tuple_index_helper(left_type, n) + if item is not None: + out.append(item) else: self.chk.fail(message_registry.TUPLE_INDEX_OUT_OF_RANGE, e) + if any(isinstance(t, UnpackType) for t in left_type.items): + min_len = self.min_tuple_length(left_type) + self.chk.note(f"Variadic tuple can have length {min_len}", e) return AnyType(TypeOfAny.from_error) return make_simplified_union(out) else: @@ -3750,7 +4326,7 @@ def visit_index_with_type( elif isinstance(left_type, TypedDictType): return self.visit_typeddict_index_expr(left_type, e.index) elif ( - isinstance(left_type, CallableType) + isinstance(left_type, FunctionLike) and left_type.is_type_obj() and left_type.type_object().is_enum ): @@ -3766,6 +4342,64 @@ def visit_index_with_type( e.method_type = method_type return result + def min_tuple_length(self, left: TupleType) -> int: + unpack_index = find_unpack_in_list(left.items) + if unpack_index is None: + return left.length() + unpack = left.items[unpack_index] + assert isinstance(unpack, UnpackType) + if isinstance(unpack.type, TypeVarTupleType): + return left.length() - 1 + unpack.type.min_len + return left.length() - 1 + + def visit_tuple_index_helper(self, left: TupleType, n: int) -> Type | None: + unpack_index = find_unpack_in_list(left.items) + if unpack_index is None: + if n < 0: + n += len(left.items) + if 0 <= n < len(left.items): + return left.items[n] + return None + unpack = left.items[unpack_index] + assert isinstance(unpack, UnpackType) + unpacked = get_proper_type(unpack.type) + if isinstance(unpacked, TypeVarTupleType): + # Usually we say that TypeVarTuple can't be split, be in case of + # indexing it seems benign to just return the upper bound item, similar + # to what we do when indexing a regular TypeVar. + bound = get_proper_type(unpacked.upper_bound) + assert isinstance(bound, Instance) + assert bound.type.fullname == "builtins.tuple" + middle = bound.args[0] + else: + assert isinstance(unpacked, Instance) + assert unpacked.type.fullname == "builtins.tuple" + middle = unpacked.args[0] + + extra_items = self.min_tuple_length(left) - left.length() + 1 + if n >= 0: + if n >= self.min_tuple_length(left): + # For tuple[int, *tuple[str, ...], int] we allow either index 0 or 1, + # since variadic item may have zero items. + return None + if n < unpack_index: + return left.items[n] + return UnionType.make_union( + [middle] + + left.items[unpack_index + 1 : max(n - extra_items + 2, unpack_index + 1)], + left.line, + left.column, + ) + n += self.min_tuple_length(left) + if n < 0: + # Similar to above, we only allow -1, and -2 for tuple[int, *tuple[str, ...], int] + return None + if n >= unpack_index + extra_items: + return left.items[n - extra_items + 1] + return UnionType.make_union( + left.items[min(n, unpack_index) : unpack_index] + [middle], left.line, left.column + ) + def visit_tuple_slice_helper(self, left_type: TupleType, slic: SliceExpr) -> Type: begin: Sequence[int | None] = [None] end: Sequence[int | None] = [None] @@ -3791,7 +4425,11 @@ def visit_tuple_slice_helper(self, left_type: TupleType, slic: SliceExpr) -> Typ items: list[Type] = [] for b, e, s in itertools.product(begin, end, stride): - items.append(left_type.slice(b, e, s)) + item = left_type.slice(b, e, s, fallback=self.named_type("builtins.tuple")) + if item is None: + self.chk.fail(message_registry.AMBIGUOUS_SLICE_OF_VARIADIC_TUPLE, slic) + return AnyType(TypeOfAny.from_error) + items.append(item) return make_simplified_union(items) def try_getting_int_literals(self, index: Expression) -> list[int] | None: @@ -3800,7 +4438,7 @@ def try_getting_int_literals(self, index: Expression) -> list[int] | None: Otherwise, returns None. Specifically, this function is guaranteed to return a list with - one or more ints if one one the following is true: + one or more ints if one the following is true: 1. 'expr' is a IntExpr or a UnaryExpr backed by an IntExpr 2. 'typ' is a LiteralType containing an int @@ -3813,6 +4451,10 @@ def try_getting_int_literals(self, index: Expression) -> list[int] | None: operand = index.expr if isinstance(operand, IntExpr): return [-1 * operand.value] + if index.op == "+": + operand = index.expr + if isinstance(operand, IntExpr): + return [operand.value] typ = get_proper_type(self.accept(index)) if isinstance(typ, Instance) and typ.last_known_value is not None: typ = typ.last_known_value @@ -3831,11 +4473,30 @@ def try_getting_int_literals(self, index: Expression) -> list[int] | None: def nonliteral_tuple_index_helper(self, left_type: TupleType, index: Expression) -> Type: self.check_method_call_by_name("__getitem__", left_type, [index], [ARG_POS], context=index) # We could return the return type from above, but unions are often better than the join - union = make_simplified_union(left_type.items) + union = self.union_tuple_fallback_item(left_type) if isinstance(index, SliceExpr): return self.chk.named_generic_type("builtins.tuple", [union]) return union + def union_tuple_fallback_item(self, left_type: TupleType) -> Type: + # TODO: this duplicates logic in typeops.tuple_fallback(). + items = [] + for item in left_type.items: + if isinstance(item, UnpackType): + unpacked_type = get_proper_type(item.type) + if isinstance(unpacked_type, TypeVarTupleType): + unpacked_type = get_proper_type(unpacked_type.upper_bound) + if ( + isinstance(unpacked_type, Instance) + and unpacked_type.type.fullname == "builtins.tuple" + ): + items.append(unpacked_type.args[0]) + else: + raise NotImplementedError + else: + items.append(item) + return make_simplified_union(items) + def visit_typeddict_index_expr( self, td_type: TypedDictType, index: Expression, setitem: bool = False ) -> Type: @@ -3916,7 +4577,16 @@ def visit_assert_type_expr(self, expr: AssertTypeExpr) -> Type: allow_none_return=True, always_allow_any=True, ) + if self.chk.current_node_deferred: + return source_type + target_type = expr.type + proper_source_type = get_proper_type(source_type) + if ( + isinstance(proper_source_type, mypy.types.Instance) + and proper_source_type.last_known_value is not None + ): + source_type = proper_source_type.last_known_value if not is_same_type(source_type, target_type): if not self.chk.in_checked_function(): self.msg.note( @@ -3939,6 +4609,7 @@ def visit_reveal_expr(self, expr: RevealExpr) -> Type: self.msg.note( "'reveal_type' always outputs 'Any' in unchecked functions", expr.expr ) + self.check_reveal_imported(expr) return revealed_type else: # REVEAL_LOCALS @@ -3953,20 +4624,49 @@ def visit_reveal_expr(self, expr: RevealExpr) -> Type: ) self.msg.reveal_locals(names_to_types, expr) + self.check_reveal_imported(expr) return NoneType() + def check_reveal_imported(self, expr: RevealExpr) -> None: + if codes.UNIMPORTED_REVEAL not in self.chk.options.enabled_error_codes: + return + + name = "" + if expr.kind == REVEAL_LOCALS: + name = "reveal_locals" + elif expr.kind == REVEAL_TYPE and not expr.is_imported: + name = "reveal_type" + else: + return + + self.chk.fail(f'Name "{name}" is not defined', expr, code=codes.UNIMPORTED_REVEAL) + if name == "reveal_type": + module = ( + "typing" if self.chk.options.python_version >= (3, 11) else "typing_extensions" + ) + hint = ( + 'Did you forget to import it from "{module}"?' + ' (Suggestion: "from {module} import {name}")' + ).format(module=module, name=name) + self.chk.note(hint, expr, code=codes.UNIMPORTED_REVEAL) + def visit_type_application(self, tapp: TypeApplication) -> Type: """Type check a type application (expr[type, ...]). There are two different options here, depending on whether expr refers to a type alias or directly to a generic class. In the first case we need - to use a dedicated function typeanal.expand_type_alias(). This - is due to some differences in how type arguments are applied and checked. + to use a dedicated function typeanal.instantiate_type_alias(). This + is due to slight differences in how type arguments are applied and checked. """ if isinstance(tapp.expr, RefExpr) and isinstance(tapp.expr.node, TypeAlias): # Subscription of a (generic) alias in runtime context, expand the alias. - item = expand_type_alias( - tapp.expr.node, tapp.types, self.chk.fail, tapp.expr.node.no_args, tapp + item = instantiate_type_alias( + tapp.expr.node, + tapp.types, + self.chk.fail, + tapp.expr.node.no_args, + tapp, + self.chk.options, ) item = get_proper_type(item) if isinstance(item, Instance): @@ -4031,7 +4731,13 @@ class LongName(Generic[T]): ... disallow_any = self.chk.options.disallow_any_generics and self.is_callee item = get_proper_type( set_any_tvars( - alias, ctx.line, ctx.column, disallow_any=disallow_any, fail=self.msg.fail + alias, + [], + ctx.line, + ctx.column, + self.chk.options, + disallow_any=disallow_any, + fail=self.msg.fail, ) ) if isinstance(item, Instance): @@ -4050,6 +4756,8 @@ class LongName(Generic[T]): ... return type_object_type(tuple_fallback(item).type, self.named_type) elif isinstance(item, TypedDictType): return self.typeddict_callable_from_context(item) + elif isinstance(item, NoneType): + return TypeType(item, line=item.line, column=item.column) elif isinstance(item, AnyType): return AnyType(TypeOfAny.from_another_any, source_any=item) else: @@ -4058,6 +4766,57 @@ class LongName(Generic[T]): ... # The _SpecialForm type can be used in some runtime contexts (e.g. it may have __or__). return self.named_type("typing._SpecialForm") + def split_for_callable( + self, t: CallableType, args: Sequence[Type], ctx: Context + ) -> list[Type]: + """Handle directly applying type arguments to a variadic Callable. + + This is needed in situations where e.g. variadic class object appears in + runtime context. For example: + class C(Generic[T, Unpack[Ts]]): ... + x = C[int, str]() + + We simply group the arguments that need to go into Ts variable into a TupleType, + similar to how it is done in other places using split_with_prefix_and_suffix(). + """ + vars = t.variables + args = flatten_nested_tuples(args) + + # TODO: this logic is duplicated with semanal_typeargs. + for tv, arg in zip(t.variables, args): + if isinstance(tv, ParamSpecType): + if not isinstance( + get_proper_type(arg), (Parameters, ParamSpecType, AnyType, UnboundType) + ): + self.chk.fail( + "Can only replace ParamSpec with a parameter types list or" + f" another ParamSpec, got {format_type(arg, self.chk.options)}", + ctx, + ) + return [AnyType(TypeOfAny.from_error)] * len(vars) + + if not vars or not any(isinstance(v, TypeVarTupleType) for v in vars): + return list(args) + assert t.is_type_obj() + info = t.type_object() + # We reuse the logic from semanal phase to reduce code duplication. + fake = Instance(info, args, line=ctx.line, column=ctx.column) + # This code can be only called either from checking a type application, or from + # checking a type alias (after the caller handles no_args aliases), so we know it + # was initially an IndexExpr, and we allow empty tuple type arguments. + if not validate_instance(fake, self.chk.fail, empty_tuple_index=True): + fix_instance( + fake, self.chk.fail, self.chk.note, disallow_any=False, options=self.chk.options + ) + args = list(fake.args) + + prefix = next(i for (i, v) in enumerate(vars) if isinstance(v, TypeVarTupleType)) + suffix = len(vars) - prefix - 1 + tvt = vars[prefix] + assert isinstance(tvt, TypeVarTupleType) + start, middle, end = split_with_prefix_and_suffix(tuple(args), prefix, suffix) + return list(start) + [TupleType(list(middle), tvt.tuple_fallback)] + list(end) + def apply_type_arguments_to_callable( self, tp: Type, args: Sequence[Type], ctx: Context ) -> Type: @@ -4071,19 +4830,36 @@ def apply_type_arguments_to_callable( tp = get_proper_type(tp) if isinstance(tp, CallableType): - if len(tp.variables) != len(args): + min_arg_count = sum(not v.has_default() for v in tp.variables) + has_type_var_tuple = any(isinstance(v, TypeVarTupleType) for v in tp.variables) + if ( + len(args) < min_arg_count or len(args) > len(tp.variables) + ) and not has_type_var_tuple: if tp.is_type_obj() and tp.type_object().fullname == "builtins.tuple": # TODO: Specialize the callable for the type arguments return tp - self.msg.incompatible_type_application(len(tp.variables), len(args), ctx) + self.msg.incompatible_type_application( + min_arg_count, len(tp.variables), len(args), ctx + ) return AnyType(TypeOfAny.from_error) - return self.apply_generic_arguments(tp, args, ctx) + return self.apply_generic_arguments(tp, self.split_for_callable(tp, args, ctx), ctx) if isinstance(tp, Overloaded): for it in tp.items: - if len(it.variables) != len(args): - self.msg.incompatible_type_application(len(it.variables), len(args), ctx) + min_arg_count = sum(not v.has_default() for v in it.variables) + has_type_var_tuple = any(isinstance(v, TypeVarTupleType) for v in it.variables) + if ( + len(args) < min_arg_count or len(args) > len(it.variables) + ) and not has_type_var_tuple: + self.msg.incompatible_type_application( + min_arg_count, len(it.variables), len(args), ctx + ) return AnyType(TypeOfAny.from_error) - return Overloaded([self.apply_generic_arguments(it, args, ctx) for it in tp.items]) + return Overloaded( + [ + self.apply_generic_arguments(it, self.split_for_callable(it, args, ctx), ctx) + for it in tp.items + ] + ) return AnyType(TypeOfAny.special_form) def visit_list_expr(self, e: ListExpr) -> Type: @@ -4137,7 +4913,14 @@ def check_lst_expr(self, e: ListExpr | SetExpr | TupleExpr, fullname: str, tag: # Used for list and set expressions, as well as for tuples # containing star expressions that don't refer to a # Tuple. (Note: "lst" stands for list-set-tuple. :-) - tv = TypeVarType("T", "T", -1, [], self.object_type()) + tv = TypeVarType( + "T", + "T", + id=-1, + values=[], + upper_bound=self.object_type(), + default=AnyType(TypeOfAny.from_omitted_generics), + ) constructor = CallableType( [tv], [nodes.ARG_STAR], @@ -4155,6 +4938,19 @@ def check_lst_expr(self, e: ListExpr | SetExpr | TupleExpr, fullname: str, tag: )[0] return remove_instance_last_known_values(out) + def tuple_context_matches(self, expr: TupleExpr, ctx: TupleType) -> bool: + ctx_unpack_index = find_unpack_in_list(ctx.items) + if ctx_unpack_index is None: + # For fixed tuples accept everything that can possibly match, even if this + # requires all star items to be empty. + return len([e for e in expr.items if not isinstance(e, StarExpr)]) <= len(ctx.items) + # For variadic context, the only easy case is when structure matches exactly. + # TODO: try using tuple type context in more cases. + if len([e for e in expr.items if isinstance(e, StarExpr)]) != 1: + return False + expr_star_index = next(i for i, lv in enumerate(expr.items) if isinstance(lv, StarExpr)) + return len(expr.items) == len(ctx.items) and ctx_unpack_index == expr_star_index + def visit_tuple_expr(self, e: TupleExpr) -> Type: """Type check a tuple expression.""" # Try to determine type context for type inference. @@ -4164,7 +4960,7 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type: tuples_in_context = [ t for t in get_proper_types(type_context.items) - if (isinstance(t, TupleType) and len(t.items) == len(e.items)) + if (isinstance(t, TupleType) and self.tuple_context_matches(e, t)) or is_named_instance(t, TUPLE_LIKE_INSTANCE_NAMES) ] if len(tuples_in_context) == 1: @@ -4174,7 +4970,7 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type: # more than one. Either way, we can't decide on a context. pass - if isinstance(type_context, TupleType): + if isinstance(type_context, TupleType) and self.tuple_context_matches(e, type_context): type_context_items = type_context.items elif type_context and is_named_instance(type_context, TUPLE_LIKE_INSTANCE_NAMES): assert isinstance(type_context, Instance) @@ -4185,6 +4981,14 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type: # items that match a position in e, and we'll worry about type # mismatches later. + unpack_in_context = False + if type_context_items is not None: + unpack_in_context = find_unpack_in_list(type_context_items) is not None + seen_unpack_in_items = False + allow_precise_tuples = ( + unpack_in_context or PRECISE_TUPLE_TYPES in self.chk.options.enable_incomplete_feature + ) + # Infer item types. Give up if there's a star expression # that's not a Tuple. items: list[Type] = [] @@ -4197,12 +5001,41 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type: # TupleExpr, flatten it, so we can benefit from the # context? Counterargument: Why would anyone write # (1, *(2, 3)) instead of (1, 2, 3) except in a test? - tt = self.accept(item.expr) + if unpack_in_context: + # Note: this logic depends on full structure match in tuple_context_matches(). + assert type_context_items + ctx_item = type_context_items[j] + assert isinstance(ctx_item, UnpackType) + ctx = ctx_item.type + else: + ctx = None + tt = self.accept(item.expr, ctx) tt = get_proper_type(tt) if isinstance(tt, TupleType): + if find_unpack_in_list(tt.items) is not None: + if seen_unpack_in_items: + # Multiple unpack items are not allowed in tuples, + # fall back to instance type. + return self.check_lst_expr(e, "builtins.tuple", "") + else: + seen_unpack_in_items = True items.extend(tt.items) - j += len(tt.items) + # Note: this logic depends on full structure match in tuple_context_matches(). + if unpack_in_context: + j += 1 + else: + # If there is an unpack in expressions, but not in context, this will + # result in an error later, just do something predictable here. + j += len(tt.items) else: + if allow_precise_tuples and not seen_unpack_in_items: + # Handle (x, *y, z), where y is e.g. tuple[Y, ...]. + if isinstance(tt, Instance) and self.chk.type_is_iterable(tt): + item_type = self.chk.iterable_item_type(tt, e) + mapped = self.chk.named_generic_type("builtins.tuple", [item_type]) + items.append(UnpackType(mapped)) + seen_unpack_in_items = True + continue # A star expression that's not a Tuple. # Treat the whole thing as a variable-length tuple. return self.check_lst_expr(e, "builtins.tuple", "") @@ -4215,7 +5048,13 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type: items.append(tt) # This is a partial fallback item type. A precise type will be calculated on demand. fallback_item = AnyType(TypeOfAny.special_form) - return TupleType(items, self.chk.named_generic_type("builtins.tuple", [fallback_item])) + result: ProperType = TupleType( + items, self.chk.named_generic_type("builtins.tuple", [fallback_item]) + ) + if seen_unpack_in_items: + # Return already normalized tuple type just in case. + result = expand_type(result, {}) + return result def fast_dict_type(self, e: DictExpr) -> Type | None: """ @@ -4268,7 +5107,7 @@ def check_typeddict_literal_in_context( self, e: DictExpr, typeddict_context: TypedDictType ) -> Type: orig_ret_type = self.check_typeddict_call_with_dict( - callee=typeddict_context, kwargs=e, context=e, orig_callee=None + callee=typeddict_context, kwargs=e.items, context=e, orig_callee=None ) ret_type = get_proper_type(orig_ret_type) if isinstance(ret_type, TypedDictType): @@ -4304,12 +5143,33 @@ def visit_dict_expr(self, e: DictExpr) -> Type: if dt: return dt + # Define type variables (used in constructors below). + kt = TypeVarType( + "KT", + "KT", + id=-1, + values=[], + upper_bound=self.object_type(), + default=AnyType(TypeOfAny.from_omitted_generics), + ) + vt = TypeVarType( + "VT", + "VT", + id=-2, + values=[], + upper_bound=self.object_type(), + default=AnyType(TypeOfAny.from_omitted_generics), + ) + # Collect function arguments, watching out for **expr. - args: list[Expression] = [] # Regular "key: value" - stargs: list[Expression] = [] # For "**expr" + args: list[Expression] = [] + expected_types: list[Type] = [] for key, value in e.items: if key is None: - stargs.append(value) + args.append(value) + expected_types.append( + self.chk.named_generic_type("_typeshed.SupportsKeysAndGetItem", [kt, vt]) + ) else: tup = TupleExpr([key, value]) if key.line >= 0: @@ -4318,48 +5178,23 @@ def visit_dict_expr(self, e: DictExpr) -> Type: else: tup.line = value.line tup.column = value.column + tup.end_line = value.end_line + tup.end_column = value.end_column args.append(tup) - # Define type variables (used in constructors below). - kt = TypeVarType("KT", "KT", -1, [], self.object_type()) - vt = TypeVarType("VT", "VT", -2, [], self.object_type()) - rv = None - # Call dict(*args), unless it's empty and stargs is not. - if args or not stargs: - # The callable type represents a function like this: - # - # def (*v: Tuple[kt, vt]) -> Dict[kt, vt]: ... - constructor = CallableType( - [TupleType([kt, vt], self.named_type("builtins.tuple"))], - [nodes.ARG_STAR], - [None], - self.chk.named_generic_type("builtins.dict", [kt, vt]), - self.named_type("builtins.function"), - name="", - variables=[kt, vt], - ) - rv = self.check_call(constructor, args, [nodes.ARG_POS] * len(args), e)[0] - else: - # dict(...) will be called below. - pass - # Call rv.update(arg) for each arg in **stargs, - # except if rv isn't set yet, then set rv = dict(arg). - if stargs: - for arg in stargs: - if rv is None: - constructor = CallableType( - [self.chk.named_generic_type("typing.Mapping", [kt, vt])], - [nodes.ARG_POS], - [None], - self.chk.named_generic_type("builtins.dict", [kt, vt]), - self.named_type("builtins.function"), - name="", - variables=[kt, vt], - ) - rv = self.check_call(constructor, [arg], [nodes.ARG_POS], arg)[0] - else: - self.check_method_call_by_name("update", rv, [arg], [nodes.ARG_POS], arg) - assert rv is not None - return rv + expected_types.append(TupleType([kt, vt], self.named_type("builtins.tuple"))) + + # The callable type represents a function like this (except we adjust for **expr): + # def (*v: Tuple[kt, vt]) -> Dict[kt, vt]: ... + constructor = CallableType( + expected_types, + [nodes.ARG_POS] * len(expected_types), + [None] * len(expected_types), + self.chk.named_generic_type("builtins.dict", [kt, vt]), + self.named_type("builtins.function"), + name="", + variables=[kt, vt], + ) + return self.check_call(constructor, args, [nodes.ARG_POS] * len(args), e)[0] def find_typeddict_context( self, context: Type | None, dict_expr: DictExpr @@ -4372,7 +5207,9 @@ def find_typeddict_context( for item in context.items: item_contexts = self.find_typeddict_context(item, dict_expr) for item_context in item_contexts: - if self.match_typeddict_call_with_dict(item_context, dict_expr, dict_expr): + if self.match_typeddict_call_with_dict( + item_context, dict_expr.items, dict_expr + ): items.append(item_context) return items # No TypedDict type in context. @@ -4402,7 +5239,8 @@ def visit_lambda_expr(self, e: LambdaExpr) -> Type: else: # Type context available. self.chk.return_types.append(inferred_type.ret_type) - self.chk.check_func_item(e, type_override=type_override) + with self.chk.tscope.function_scope(e): + self.chk.check_func_item(e, type_override=type_override) if not self.chk.has_type(e.expr()): # TODO: return expression must be accepted before exiting function scope. self.accept(e.expr(), allow_none_return=True) @@ -4436,14 +5274,28 @@ def infer_lambda_type_using_context( # they must be considered as indeterminate. We use ErasedType since it # does not affect type inference results (it is for purposes like this # only). - callable_ctx = get_proper_type(replace_meta_vars(ctx, ErasedType())) - assert isinstance(callable_ctx, CallableType) + if not self.chk.options.old_type_inference: + # With new type inference we can preserve argument types even if they + # are generic, since new inference algorithm can handle constraints + # like S <: T (we still erase return type since it's ultimately unknown). + extra_vars = [] + for arg in ctx.arg_types: + meta_vars = [tv for tv in get_all_type_vars(arg) if tv.id.is_meta_var()] + extra_vars.extend([tv for tv in meta_vars if tv not in extra_vars]) + callable_ctx = ctx.copy_modified( + ret_type=replace_meta_vars(ctx.ret_type, ErasedType()), + variables=list(ctx.variables) + extra_vars, + ) + else: + erased_ctx = replace_meta_vars(ctx, ErasedType()) + assert isinstance(erased_ctx, ProperType) and isinstance(erased_ctx, CallableType) + callable_ctx = erased_ctx # The callable_ctx may have a fallback of builtins.type if the context # is a constructor -- but this fallback doesn't make sense for lambdas. callable_ctx = callable_ctx.copy_modified(fallback=self.named_type("builtins.function")) - if callable_ctx.type_guard is not None: + if callable_ctx.type_guard is not None or callable_ctx.type_is is not None: # Lambda's return type cannot be treated as a `TypeGuard`, # because it is implicit. And `TypeGuard`s must be explicit. # See https://github.com/python/mypy/issues/9927 @@ -4457,7 +5309,7 @@ def infer_lambda_type_using_context( is_ellipsis_args=False, arg_types=[AnyType(TypeOfAny.special_form)] * len(arg_kinds), arg_kinds=arg_kinds, - arg_names=e.arg_names[:], + arg_names=e.arg_names.copy(), ) if ARG_STAR in arg_kinds or ARG_STAR2 in arg_kinds: @@ -4469,7 +5321,9 @@ def infer_lambda_type_using_context( self.chk.fail(message_registry.CANNOT_INFER_LAMBDA_TYPE, e) return None, None - return callable_ctx, callable_ctx + # Type of lambda must have correct argument names, to prevent false + # negatives when lambdas appear in `ParamSpec` context. + return callable_ctx.copy_modified(arg_names=e.arg_names), callable_ctx def visit_super_expr(self, e: SuperExpr) -> Type: """Type check a super expression (non-lvalue).""" @@ -4638,7 +5492,11 @@ def _super_arg_types(self, e: SuperExpr) -> Type | tuple[Type, Type]: return type_type, instance_type def visit_slice_expr(self, e: SliceExpr) -> Type: - expected = make_optional_type(self.named_type("builtins.int")) + try: + supports_index = self.chk.named_type("typing_extensions.SupportsIndex") + except KeyError: + supports_index = self.chk.named_type("builtins.int") # thanks, fixture life + expected = make_optional_type(supports_index) for index in [e.begin_index, e.end_index, e.stride]: if index: t = self.accept(index) @@ -4684,7 +5542,14 @@ def check_generator_or_comprehension( # Infer the type of the list comprehension by using a synthetic generic # callable type. - tv = TypeVarType("T", "T", -1, [], self.object_type()) + tv = TypeVarType( + "T", + "T", + id=-1, + values=[], + upper_bound=self.object_type(), + default=AnyType(TypeOfAny.from_omitted_generics), + ) tv_list: list[Type] = [tv] constructor = CallableType( tv_list, @@ -4704,8 +5569,22 @@ def visit_dictionary_comprehension(self, e: DictionaryComprehension) -> Type: # Infer the type of the list comprehension by using a synthetic generic # callable type. - ktdef = TypeVarType("KT", "KT", -1, [], self.object_type()) - vtdef = TypeVarType("VT", "VT", -2, [], self.object_type()) + ktdef = TypeVarType( + "KT", + "KT", + id=-1, + values=[], + upper_bound=self.object_type(), + default=AnyType(TypeOfAny.from_omitted_generics), + ) + vtdef = TypeVarType( + "VT", + "VT", + id=-2, + values=[], + upper_bound=self.object_type(), + default=AnyType(TypeOfAny.from_omitted_generics), + ) constructor = CallableType( [ktdef, vtdef], [nodes.ARG_POS, nodes.ARG_POS], @@ -4916,15 +5795,8 @@ def named_type(self, name: str) -> Instance: def is_valid_var_arg(self, typ: Type) -> bool: """Is a type valid as a *args argument?""" typ = get_proper_type(typ) - return ( - isinstance(typ, TupleType) - or is_subtype( - typ, - self.chk.named_generic_type("typing.Iterable", [AnyType(TypeOfAny.special_form)]), - ) - or isinstance(typ, AnyType) - or isinstance(typ, ParamSpecType) - or isinstance(typ, UnpackType) + return isinstance(typ, (TupleType, AnyType, ParamSpecType, UnpackType)) or is_subtype( + typ, self.chk.named_generic_type("typing.Iterable", [AnyType(TypeOfAny.special_form)]) ) def is_valid_keyword_var_arg(self, typ: Type) -> bool: @@ -4933,14 +5805,14 @@ def is_valid_keyword_var_arg(self, typ: Type) -> bool: is_subtype( typ, self.chk.named_generic_type( - "typing.Mapping", + "_typeshed.SupportsKeysAndGetItem", [self.named_type("builtins.str"), AnyType(TypeOfAny.special_form)], ), ) or is_subtype( typ, self.chk.named_generic_type( - "typing.Mapping", [UninhabitedType(), UninhabitedType()] + "_typeshed.SupportsKeysAndGetItem", [UninhabitedType(), UninhabitedType()] ), ) or isinstance(typ, ParamSpecType) @@ -5104,17 +5976,7 @@ def visit_yield_from_expr(self, e: YieldFromExpr, allow_none_return: bool = Fals # Determine the type of the entire yield from expression. iter_type = get_proper_type(iter_type) - if isinstance(iter_type, Instance) and iter_type.type.fullname == "typing.Generator": - expr_type = self.chk.get_generator_return_type(iter_type, False) - else: - # Non-Generators don't return anything from `yield from` expressions. - # However special-case Any (which might be produced by an error). - actual_item_type = get_proper_type(actual_item_type) - if isinstance(actual_item_type, AnyType): - expr_type = AnyType(TypeOfAny.from_another_any, source_any=actual_item_type) - else: - # Treat `Iterator[X]` as a shorthand for `Generator[X, None, Any]`. - expr_type = NoneType() + expr_type = self.chk.get_generator_return_type(iter_type, is_coroutine=False) if not allow_none_return and isinstance(get_proper_type(expr_type), NoneType): self.chk.msg.does_not_return_value(None, e) @@ -5124,6 +5986,15 @@ def visit_temp_node(self, e: TempNode) -> Type: return e.type def visit_type_var_expr(self, e: TypeVarExpr) -> Type: + p_default = get_proper_type(e.default) + if not ( + isinstance(p_default, AnyType) + and p_default.type_of_any == TypeOfAny.from_omitted_generics + ): + if not is_subtype(p_default, e.upper_bound): + self.chk.fail("TypeVar default must be a subtype of the bound type", e) + if e.values and not any(p_default == value for value in e.values): + self.chk.fail("TypeVar default must be one of the constraint types", e) return AnyType(TypeOfAny.special_form) def visit_paramspec_expr(self, e: ParamSpecExpr) -> Type: @@ -5181,14 +6052,12 @@ def bool_type(self) -> Instance: return self.named_type("builtins.bool") @overload - def narrow_type_from_binder(self, expr: Expression, known_type: Type) -> Type: - ... + def narrow_type_from_binder(self, expr: Expression, known_type: Type) -> Type: ... @overload def narrow_type_from_binder( self, expr: Expression, known_type: Type, skip_non_overlapping: bool - ) -> Type | None: - ... + ) -> Type | None: ... def narrow_type_from_binder( self, expr: Expression, known_type: Type, skip_non_overlapping: bool = False @@ -5214,6 +6083,26 @@ def narrow_type_from_binder( return narrow_declared_type(known_type, restriction) return known_type + def has_abstract_type_part(self, caller_type: ProperType, callee_type: ProperType) -> bool: + # TODO: support other possible types here + if isinstance(caller_type, TupleType) and isinstance(callee_type, TupleType): + return any( + self.has_abstract_type(get_proper_type(caller), get_proper_type(callee)) + for caller, callee in zip(caller_type.items, callee_type.items) + ) + return self.has_abstract_type(caller_type, callee_type) + + def has_abstract_type(self, caller_type: ProperType, callee_type: ProperType) -> bool: + return ( + isinstance(caller_type, FunctionLike) + and isinstance(callee_type, TypeType) + and caller_type.is_type_obj() + and (caller_type.type_object().is_abstract or caller_type.type_object().is_protocol) + and isinstance(callee_type.item, Instance) + and (callee_type.item.type.is_abstract or callee_type.item.type.is_protocol) + and not self.chk.allow_abstract_call + ) + def has_any_type(t: Type, ignore_in_type_obj: bool = False) -> bool: """Whether t contains an Any type""" @@ -5233,6 +6122,18 @@ def visit_callable_type(self, t: CallableType) -> bool: return False return super().visit_callable_type(t) + def visit_type_var(self, t: TypeVarType) -> bool: + default = [t.default] if t.has_default() else [] + return self.query_types([t.upper_bound, *default] + t.values) + + def visit_param_spec(self, t: ParamSpecType) -> bool: + default = [t.default] if t.has_default() else [] + return self.query_types([t.upper_bound, *default]) + + def visit_type_var_tuple(self, t: TypeVarTupleType) -> bool: + default = [t.default] if t.has_default() else [] + return self.query_types([t.upper_bound, *default]) + def has_coroutine_decorator(t: Type) -> bool: """Whether t came from a function decorated with `@coroutine`.""" @@ -5299,6 +6200,129 @@ def replace_callable_return_type(c: CallableType, new_ret_type: Type) -> Callabl return c.copy_modified(ret_type=new_ret_type) +def apply_poly(tp: CallableType, poly_tvars: Sequence[TypeVarLikeType]) -> CallableType | None: + """Make free type variables generic in the type if possible. + + This will translate the type `tp` while trying to create valid bindings for + type variables `poly_tvars` while traversing the type. This follows the same rules + as we do during semantic analysis phase, examples: + * Callable[Callable[[T], T], T] -> def [T] (def (T) -> T) -> T + * Callable[[], Callable[[T], T]] -> def () -> def [T] (T -> T) + * List[T] -> None (not possible) + """ + try: + return tp.copy_modified( + arg_types=[t.accept(PolyTranslator(poly_tvars)) for t in tp.arg_types], + ret_type=tp.ret_type.accept(PolyTranslator(poly_tvars)), + variables=[], + ) + except PolyTranslationError: + return None + + +class PolyTranslationError(Exception): + pass + + +class PolyTranslator(TypeTranslator): + """Make free type variables generic in the type if possible. + + See docstring for apply_poly() for details. + """ + + def __init__( + self, + poly_tvars: Iterable[TypeVarLikeType], + bound_tvars: frozenset[TypeVarLikeType] = frozenset(), + seen_aliases: frozenset[TypeInfo] = frozenset(), + ) -> None: + self.poly_tvars = set(poly_tvars) + # This is a simplified version of TypeVarScope used during semantic analysis. + self.bound_tvars = bound_tvars + self.seen_aliases = seen_aliases + + def collect_vars(self, t: CallableType | Parameters) -> list[TypeVarLikeType]: + found_vars = [] + for arg in t.arg_types: + for tv in get_all_type_vars(arg): + if isinstance(tv, ParamSpecType): + normalized: TypeVarLikeType = tv.copy_modified( + flavor=ParamSpecFlavor.BARE, prefix=Parameters([], [], []) + ) + else: + normalized = tv + if normalized in self.poly_tvars and normalized not in self.bound_tvars: + found_vars.append(normalized) + return remove_dups(found_vars) + + def visit_callable_type(self, t: CallableType) -> Type: + found_vars = self.collect_vars(t) + self.bound_tvars |= set(found_vars) + result = super().visit_callable_type(t) + self.bound_tvars -= set(found_vars) + + assert isinstance(result, ProperType) and isinstance(result, CallableType) + result.variables = list(result.variables) + found_vars + return result + + def visit_type_var(self, t: TypeVarType) -> Type: + if t in self.poly_tvars and t not in self.bound_tvars: + raise PolyTranslationError() + return super().visit_type_var(t) + + def visit_param_spec(self, t: ParamSpecType) -> Type: + if t in self.poly_tvars and t not in self.bound_tvars: + raise PolyTranslationError() + return super().visit_param_spec(t) + + def visit_type_var_tuple(self, t: TypeVarTupleType) -> Type: + if t in self.poly_tvars and t not in self.bound_tvars: + raise PolyTranslationError() + return super().visit_type_var_tuple(t) + + def visit_type_alias_type(self, t: TypeAliasType) -> Type: + if not t.args: + return t.copy_modified() + if not t.is_recursive: + return get_proper_type(t).accept(self) + # We can't handle polymorphic application for recursive generic aliases + # without risking an infinite recursion, just give up for now. + raise PolyTranslationError() + + def visit_instance(self, t: Instance) -> Type: + if t.type.has_param_spec_type: + # We need this special-casing to preserve the possibility to store a + # generic function in an instance type. Things like + # forall T . Foo[[x: T], T] + # are not really expressible in current type system, but this looks like + # a useful feature, so let's keep it. + param_spec_index = next( + i for (i, tv) in enumerate(t.type.defn.type_vars) if isinstance(tv, ParamSpecType) + ) + p = get_proper_type(t.args[param_spec_index]) + if isinstance(p, Parameters): + found_vars = self.collect_vars(p) + self.bound_tvars |= set(found_vars) + new_args = [a.accept(self) for a in t.args] + self.bound_tvars -= set(found_vars) + + repl = new_args[param_spec_index] + assert isinstance(repl, ProperType) and isinstance(repl, Parameters) + repl.variables = list(repl.variables) + list(found_vars) + return t.copy_modified(args=new_args) + # There is the same problem with callback protocols as with aliases + # (callback protocols are essentially more flexible aliases to callables). + if t.args and t.type.is_protocol and t.type.protocol_members == ["__call__"]: + if t.type in self.seen_aliases: + raise PolyTranslationError() + call = find_member("__call__", t, t, is_operator=True) + assert call is not None + return call.accept( + PolyTranslator(self.poly_tvars, self.bound_tvars, self.seen_aliases | {t.type}) + ) + return super().visit_instance(t) + + class ArgInferSecondPassQuery(types.BoolTypeQuery): """Query whether an argument type should be inferred in the second pass. @@ -5311,6 +6335,7 @@ def __init__(self) -> None: super().__init__(types.ANY_STRATEGY) def visit_callable_type(self, t: CallableType) -> bool: + # TODO: we need to check only for type variables of original callable. return self.query_types(t.arg_types) or t.accept(HasTypeVarQuery()) @@ -5323,6 +6348,12 @@ def __init__(self) -> None: def visit_type_var(self, t: TypeVarType) -> bool: return True + def visit_param_spec(self, t: ParamSpecType) -> bool: + return True + + def visit_type_var_tuple(self, t: TypeVarTupleType) -> bool: + return True + def has_erased_component(t: Type | None) -> bool: return t is not None and t.accept(HasErasedComponentsQuery()) @@ -5474,7 +6505,7 @@ def any_causes_overload_ambiguity( def all_same_types(types: list[Type]) -> bool: - if len(types) == 0: + if not types: return True return all(is_same_type(t, types[0]) for t in types[1:]) @@ -5510,15 +6541,15 @@ def merge_typevars_in_callables_by_name( for tv in target.variables: name = tv.fullname if name not in unique_typevars: - # TODO(PEP612): fix for ParamSpecType - if isinstance(tv, ParamSpecType): + # TODO: support ParamSpecType and TypeVarTuple. + if isinstance(tv, (ParamSpecType, TypeVarTupleType)): continue assert isinstance(tv, TypeVarType) unique_typevars[name] = tv variables.append(tv) rename[tv.id] = unique_typevars[name] - target = cast(CallableType, expand_type(target, rename)) + target = expand_type(target, rename) output.append(target) return output, variables diff --git a/mypy/checkmember.py b/mypy/checkmember.py index a2c580e13446..5824b00a37f6 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -24,6 +24,7 @@ FuncDef, IndexExpr, MypyFile, + NameExpr, OverloadedFuncDef, SymbolNode, SymbolTable, @@ -122,6 +123,7 @@ def copy_modified( messages: MessageBuilder | None = None, self_type: Type | None = None, is_lvalue: bool | None = None, + original_type: Type | None = None, ) -> MemberContext: mx = MemberContext( self.is_lvalue, @@ -141,6 +143,8 @@ def copy_modified( mx.self_type = self_type if is_lvalue is not None: mx.is_lvalue = is_lvalue + if original_type is not None: + mx.original_type = original_type return mx @@ -271,11 +275,11 @@ def report_missing_attribute( mx: MemberContext, override_info: TypeInfo | None = None, ) -> Type: - res_type = mx.msg.has_no_attr(original_type, typ, name, mx.context, mx.module_symbol_table) + error_code = mx.msg.has_no_attr(original_type, typ, name, mx.context, mx.module_symbol_table) if not mx.msg.prefer_simple_messages(): if may_be_awaitable_attribute(name, typ, mx, override_info): - mx.msg.possible_missing_await(mx.context) - return res_type + mx.msg.possible_missing_await(mx.context, error_code) + return AnyType(TypeOfAny.from_error) # The several functions that follow implement analyze_member_access for various @@ -312,24 +316,30 @@ def analyze_instance_member_access( if method.is_property: assert isinstance(method, OverloadedFuncDef) - first_item = cast(Decorator, method.items[0]) + first_item = method.items[0] + assert isinstance(first_item, Decorator) return analyze_var(name, first_item.var, typ, info, mx) if mx.is_lvalue: mx.msg.cant_assign_to_method(mx.context) - signature = function_type(method, mx.named_type("builtins.function")) - signature = freshen_all_functions_type_vars(signature) - if name == "__new__" or method.is_static: - # __new__ is special and behaves like a static method -- don't strip - # the first argument. - pass + if not isinstance(method, OverloadedFuncDef): + signature = function_type(method, mx.named_type("builtins.function")) else: - if name != "__call__": - # TODO: use proper treatment of special methods on unions instead - # of this hack here and below (i.e. mx.self_type). - dispatched_type = meet.meet_types(mx.original_type, typ) - signature = check_self_arg( - signature, dispatched_type, method.is_class, mx.context, name, mx.msg - ) + if method.type is None: + # Overloads may be not ready if they are decorated. Handle this in same + # manner as we would handle a regular decorated function: defer if possible. + if not mx.no_deferral and method.items: + mx.not_ready_callback(method.name, mx.context) + return AnyType(TypeOfAny.special_form) + assert isinstance(method.type, Overloaded) + signature = method.type + signature = freshen_all_functions_type_vars(signature) + if not method.is_static: + # TODO: use proper treatment of special methods on unions instead + # of this hack here and below (i.e. mx.self_type). + dispatched_type = meet.meet_types(mx.original_type, typ) + signature = check_self_arg( + signature, dispatched_type, method.is_class, mx.context, name, mx.msg + ) signature = bind_self(signature, mx.self_type, is_classmethod=method.is_class) # TODO: should we skip these steps for static methods as well? # Since generic static methods should not be allowed. @@ -352,13 +362,7 @@ def validate_super_call(node: FuncBase, mx: MemberContext) -> None: impl = node.impl if isinstance(node.impl, FuncDef) else node.impl.func unsafe_super = impl.is_trivial_body if unsafe_super: - ret_type = ( - impl.type.ret_type - if isinstance(impl.type, CallableType) - else AnyType(TypeOfAny.unannotated) - ) - if not subtypes.is_subtype(NoneType(), ret_type): - mx.msg.unsafe_super(node.name, node.info.name, mx.context) + mx.msg.unsafe_super(node.name, node.info.name, mx.context) def analyze_type_callable_member_access(name: str, typ: FunctionLike, mx: MemberContext) -> Type: @@ -387,7 +391,7 @@ def analyze_type_callable_member_access(name: str, typ: FunctionLike, mx: Member # See https://github.com/python/mypy/pull/1787 for more info. # TODO: do not rely on same type variables being present in all constructor overloads. result = analyze_class_attribute_access( - ret_type, name, mx, original_vars=typ.items[0].variables + ret_type, name, mx, original_vars=typ.items[0].variables, mcs_fallback=typ.fallback ) if result: return result @@ -412,6 +416,13 @@ def analyze_type_type_member_access( upper_bound = get_proper_type(typ.item.upper_bound) if isinstance(upper_bound, Instance): item = upper_bound + elif isinstance(upper_bound, UnionType): + return _analyze_member_access( + name, + TypeType.make_normalized(upper_bound, line=typ.line, column=typ.column), + mx, + override_info, + ) elif isinstance(upper_bound, TupleType): item = tuple_fallback(upper_bound) elif isinstance(upper_bound, AnyType): @@ -426,17 +437,21 @@ def analyze_type_type_member_access( if isinstance(typ.item.item, Instance): item = typ.item.item.type.metaclass_type ignore_messages = False + + if item is not None: + fallback = item.type.metaclass_type or fallback + if item and not mx.is_operator: # See comment above for why operators are skipped - result = analyze_class_attribute_access(item, name, mx, override_info) + result = analyze_class_attribute_access( + item, name, mx, mcs_fallback=fallback, override_info=override_info + ) if result: if not (isinstance(get_proper_type(result), AnyType) and item.type.fallback_to_any): return result else: # We don't want errors on metaclass lookup for classes with Any fallback ignore_messages = True - if item is not None: - fallback = item.type.metaclass_type or fallback with mx.msg.filter_errors(filter_errors=ignore_messages): return _analyze_member_access(name, fallback, mx, override_info) @@ -590,7 +605,19 @@ def analyze_member_var_access( mx.msg.undefined_in_superclass(name, mx.context) return AnyType(TypeOfAny.from_error) else: - return report_missing_attribute(mx.original_type, itype, name, mx) + ret = report_missing_attribute(mx.original_type, itype, name, mx) + # Avoid paying double jeopardy if we can't find the member due to --no-implicit-reexport + if ( + mx.module_symbol_table is not None + and name in mx.module_symbol_table + and not mx.module_symbol_table[name].module_public + ): + v = mx.module_symbol_table[name].node + e = NameExpr(name) + e.set_line(mx.context) + e.node = v + return mx.chk.expr_checker.analyze_ref_expr(e, lvalue=mx.is_lvalue) + return ret def check_final_member(name: str, info: TypeInfo, msg: MessageBuilder, ctx: Context) -> None: @@ -620,6 +647,16 @@ def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type: return make_simplified_union( [analyze_descriptor_access(typ, mx) for typ in descriptor_type.items] ) + elif isinstance(instance_type, UnionType): + # map over the instance types + return make_simplified_union( + [ + analyze_descriptor_access( + descriptor_type, mx.copy_modified(original_type=original_type) + ) + for original_type in instance_type.items + ] + ) elif not isinstance(descriptor_type, Instance): return orig_descriptor_type @@ -629,7 +666,10 @@ def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type: dunder_get = descriptor_type.type.get_method("__get__") if dunder_get is None: mx.msg.fail( - message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format(descriptor_type), mx.context + message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format( + descriptor_type.str_with_options(mx.msg.options) + ), + mx.context, ) return AnyType(TypeOfAny.from_error) @@ -686,7 +726,10 @@ def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type: if not isinstance(inferred_dunder_get_type, CallableType): mx.msg.fail( - message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format(descriptor_type), mx.context + message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format( + descriptor_type.str_with_options(mx.msg.options) + ), + mx.context, ) return AnyType(TypeOfAny.from_error) @@ -717,12 +760,12 @@ def analyze_var( """Analyze access to an attribute via a Var node. This is conceptually part of analyze_member_access and the arguments are similar. - - itype is the class object in which var is defined + itype is the instance type in which attribute should be looked up original_type is the type of E in the expression E.var if implicit is True, the original Var was created as an assignment to self """ # Found a member variable. + original_itype = itype itype = map_instance_to_supertype(itype, var.info) typ = var.type if typ: @@ -738,16 +781,31 @@ def analyze_var( get_proper_type(mx.original_type) ): t = expand_self_type(var, t, mx.original_type) + elif ( + mx.is_self + and original_itype.type != var.info + # If an attribute with Self-type was defined in a supertype, we need to + # rebind the Self type variable to Self type variable of current class... + and original_itype.type.self_type is not None + # ...unless `self` has an explicit non-trivial annotation. + and original_itype == mx.chk.scope.active_self_type() + ): + t = expand_self_type(var, t, original_itype.type.self_type) t = get_proper_type(expand_type_by_instance(t, itype)) freeze_all_type_vars(t) result: Type = t typ = get_proper_type(typ) - if ( - var.is_initialized_in_class - and (not is_instance_var(var) or mx.is_operator) - and isinstance(typ, FunctionLike) - and not typ.is_type_obj() - ): + + call_type: ProperType | None = None + if var.is_initialized_in_class and (not is_instance_var(var) or mx.is_operator): + if isinstance(typ, FunctionLike) and not typ.is_type_obj(): + call_type = typ + elif var.is_property: + call_type = get_proper_type(_analyze_member_access("__call__", typ, mx)) + else: + call_type = typ + + if isinstance(call_type, FunctionLike) and not call_type.is_type_obj(): if mx.is_lvalue: if var.is_property: if not var.is_settable_property: @@ -758,7 +816,7 @@ def analyze_var( if not var.is_staticmethod: # Class-level function objects and classmethods become bound methods: # the former to the instance, the latter to the class. - functype = typ + functype: FunctionLike = call_type # Use meet to narrow original_type to the dispatched type. # For example, assume # * A.f: Callable[[A1], None] where A1 <: A (maybe A1 == A) @@ -857,6 +915,8 @@ def f(self: S) -> T: ... return functype else: selfarg = get_proper_type(item.arg_types[0]) + # This level of erasure matches the one in checker.check_func_def(), + # better keep these two checks consistent. if subtypes.is_subtype(dispatched_arg_type, erase_typevars(erase_to_bound(selfarg))): new_items.append(item) elif isinstance(selfarg, ParamSpecType): @@ -879,6 +939,8 @@ def analyze_class_attribute_access( itype: Instance, name: str, mx: MemberContext, + *, + mcs_fallback: Instance, override_info: TypeInfo | None = None, original_vars: Sequence[TypeVarLikeType] | None = None, ) -> Type | None: @@ -905,6 +967,22 @@ def analyze_class_attribute_access( return apply_class_attr_hook(mx, hook, AnyType(TypeOfAny.special_form)) return None + if ( + isinstance(node.node, Var) + and not node.node.is_classvar + and not hook + and mcs_fallback.type.get(name) + ): + # If the same attribute is declared on the metaclass and the class but with different types, + # and the attribute on the class is not a ClassVar, + # the type of the attribute on the metaclass should take priority + # over the type of the attribute on the class, + # when the attribute is being accessed from the class object itself. + # + # Return `None` here to signify that the name should be looked up + # on the class object itself rather than the instance. + return None + is_decorated = isinstance(node.node, Decorator) is_method = is_decorated or isinstance(node.node, FuncBase) if mx.is_lvalue: @@ -992,11 +1070,14 @@ def analyze_class_attribute_access( is_classmethod = (is_decorated and cast(Decorator, node.node).func.is_class) or ( isinstance(node.node, FuncBase) and node.node.is_class ) + is_staticmethod = (is_decorated and cast(Decorator, node.node).func.is_static) or ( + isinstance(node.node, FuncBase) and node.node.is_static + ) t = get_proper_type(t) if isinstance(t, FunctionLike) and is_classmethod: t = check_self_arg(t, mx.self_type, False, mx.context, name, mx.msg) result = add_class_tvars( - t, isuper, is_classmethod, mx.self_type, original_vars=original_vars + t, isuper, is_classmethod, is_staticmethod, mx.self_type, original_vars=original_vars ) if not mx.is_lvalue: result = analyze_descriptor_access(result, mx) @@ -1058,8 +1139,8 @@ def analyze_enum_class_attribute_access( # Skip these since Enum will remove it if name in ENUM_REMOVED_PROPS: return report_missing_attribute(mx.original_type, itype, name, mx) - # For other names surrendered by underscores, we don't make them Enum members - if name.startswith("__") and name.endswith("__") and name.replace("_", "") != "": + # Dunders and private names are not Enum members + if name.startswith("__") and name.replace("_", "") != "": return None enum_literal = LiteralType(name, fallback=itype) @@ -1106,6 +1187,7 @@ def add_class_tvars( t: ProperType, isuper: Instance | None, is_classmethod: bool, + is_staticmethod: bool, original_type: Type, original_vars: Sequence[TypeVarLikeType] | None = None, ) -> Type: @@ -1124,6 +1206,7 @@ class B(A[str]): pass isuper: Current instance mapped to the superclass where method was defined, this is usually done by map_instance_to_supertype() is_classmethod: True if this method is decorated with @classmethod + is_staticmethod: True if this method is decorated with @staticmethod original_type: The value of the type B in the expression B.foo() or the corresponding component in case of a union (this is used to bind the self-types) original_vars: Type variables of the class callable on which the method was accessed @@ -1146,12 +1229,13 @@ class B(A[str]): pass # (i.e. appear in the return type of the class object on which the method was accessed). if isinstance(t, CallableType): tvars = original_vars if original_vars is not None else [] + t = freshen_all_functions_type_vars(t) if is_classmethod: - t = freshen_all_functions_type_vars(t) t = bind_self(t, original_type, is_classmethod=True) + if is_classmethod or is_staticmethod: assert isuper is not None - t = cast(CallableType, expand_type_by_instance(t, isuper)) - freeze_all_type_vars(t) + t = expand_type_by_instance(t, isuper) + freeze_all_type_vars(t) return t.copy_modified(variables=list(tvars) + list(t.variables)) elif isinstance(t, Overloaded): return Overloaded( @@ -1159,7 +1243,12 @@ class B(A[str]): pass cast( CallableType, add_class_tvars( - item, isuper, is_classmethod, original_type, original_vars=original_vars + item, + isuper, + is_classmethod, + is_staticmethod, + original_type, + original_vars=original_vars, ), ) for item in t.items diff --git a/mypy/checkpattern.py b/mypy/checkpattern.py index 603b392eee29..a23be464b825 100644 --- a/mypy/checkpattern.py +++ b/mypy/checkpattern.py @@ -3,8 +3,7 @@ from __future__ import annotations from collections import defaultdict -from typing import NamedTuple -from typing_extensions import Final +from typing import Final, NamedTuple import mypy.checker from mypy import message_registry @@ -15,7 +14,8 @@ from mypy.maptype import map_instance_to_supertype from mypy.meet import narrow_declared_type from mypy.messages import MessageBuilder -from mypy.nodes import ARG_POS, Expression, NameExpr, TypeAlias, TypeInfo, Var +from mypy.nodes import ARG_POS, Context, Expression, NameExpr, TypeAlias, TypeInfo, Var +from mypy.options import Options from mypy.patterns import ( AsPattern, ClassPattern, @@ -45,9 +45,13 @@ Type, TypedDictType, TypeOfAny, + TypeVarTupleType, UninhabitedType, UnionType, + UnpackType, + find_unpack_in_list, get_proper_type, + split_with_prefix_and_suffix, ) from mypy.typevars import fill_typevars from mypy.visitor import PatternVisitor @@ -104,7 +108,11 @@ class PatternChecker(PatternVisitor[PatternType]): # non_sequence_match_type_names non_sequence_match_types: list[Type] - def __init__(self, chk: mypy.checker.TypeChecker, msg: MessageBuilder, plugin: Plugin) -> None: + options: Options + + def __init__( + self, chk: mypy.checker.TypeChecker, msg: MessageBuilder, plugin: Plugin, options: Options + ) -> None: self.chk = chk self.msg = msg self.plugin = plugin @@ -114,6 +122,7 @@ def __init__(self, chk: mypy.checker.TypeChecker, msg: MessageBuilder, plugin: P self.non_sequence_match_types = self.generate_types_from_names( non_sequence_match_type_names ) + self.options = options def accept(self, o: Pattern, type_context: Type) -> PatternType: self.type_context.append(type_context) @@ -178,7 +187,7 @@ def visit_or_pattern(self, o: OrPattern) -> PatternType: capture_types[node].append((expr, typ)) captures: dict[Expression, Type] = {} - for var, capture_list in capture_types.items(): + for capture_list in capture_types.values(): typ = UninhabitedType() for _, other in capture_list: typ = join_types(typ, other) @@ -193,7 +202,7 @@ def visit_value_pattern(self, o: ValuePattern) -> PatternType: typ = self.chk.expr_checker.accept(o.expr) typ = coerce_to_literal(typ) narrowed_type, rest_type = self.chk.conditional_types_with_intersection( - current_type, [get_type_range(typ)], o, default=current_type + current_type, [get_type_range(typ)], o, default=get_proper_type(typ) ) if not isinstance(get_proper_type(narrowed_type), (LiteralType, UninhabitedType)): return PatternType(narrowed_type, UnionType.make_union([narrowed_type, rest_type]), {}) @@ -234,15 +243,31 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType: # # get inner types of original type # + unpack_index = None if isinstance(current_type, TupleType): inner_types = current_type.items - size_diff = len(inner_types) - required_patterns - if size_diff < 0: - return self.early_non_match() - elif size_diff > 0 and star_position is None: - return self.early_non_match() + unpack_index = find_unpack_in_list(inner_types) + if unpack_index is None: + size_diff = len(inner_types) - required_patterns + if size_diff < 0: + return self.early_non_match() + elif size_diff > 0 and star_position is None: + return self.early_non_match() + else: + normalized_inner_types = [] + for it in inner_types: + # Unfortunately, it is not possible to "split" the TypeVarTuple + # into individual items, so we just use its upper bound for the whole + # analysis instead. + if isinstance(it, UnpackType) and isinstance(it.type, TypeVarTupleType): + it = UnpackType(it.type.upper_bound) + normalized_inner_types.append(it) + inner_types = normalized_inner_types + current_type = current_type.copy_modified(items=normalized_inner_types) + if len(inner_types) - 1 > required_patterns and star_position is None: + return self.early_non_match() else: - inner_type = self.get_sequence_type(current_type) + inner_type = self.get_sequence_type(current_type, o) if inner_type is None: inner_type = self.chk.named_type("builtins.object") inner_types = [inner_type] * len(o.patterns) @@ -265,10 +290,10 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType: self.update_type_map(captures, type_map) new_inner_types = self.expand_starred_pattern_types( - contracted_new_inner_types, star_position, len(inner_types) + contracted_new_inner_types, star_position, len(inner_types), unpack_index is not None ) rest_inner_types = self.expand_starred_pattern_types( - contracted_rest_inner_types, star_position, len(inner_types) + contracted_rest_inner_types, star_position, len(inner_types), unpack_index is not None ) # @@ -276,15 +301,14 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType: # new_type: Type rest_type: Type = current_type - if isinstance(current_type, TupleType): + if isinstance(current_type, TupleType) and unpack_index is None: narrowed_inner_types = [] inner_rest_types = [] for inner_type, new_inner_type in zip(inner_types, new_inner_types): - ( - narrowed_inner_type, - inner_rest_type, - ) = self.chk.conditional_types_with_intersection( - new_inner_type, [get_type_range(inner_type)], o, default=new_inner_type + (narrowed_inner_type, inner_rest_type) = ( + self.chk.conditional_types_with_intersection( + new_inner_type, [get_type_range(inner_type)], o, default=new_inner_type + ) ) narrowed_inner_types.append(narrowed_inner_type) inner_rest_types.append(inner_rest_type) @@ -296,6 +320,14 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType: if all(is_uninhabited(typ) for typ in inner_rest_types): # All subpatterns always match, so we can apply negative narrowing rest_type = TupleType(rest_inner_types, current_type.partial_fallback) + elif isinstance(current_type, TupleType): + # For variadic tuples it is too tricky to match individual items like for fixed + # tuples, so we instead try to narrow the entire type. + # TODO: use more precise narrowing when possible (e.g. for identical shapes). + new_tuple_type = TupleType(new_inner_types, current_type.partial_fallback) + new_type, rest_type = self.chk.conditional_types_with_intersection( + new_tuple_type, [get_type_range(current_type)], o, default=new_tuple_type + ) else: new_inner_type = UninhabitedType() for typ in new_inner_types: @@ -309,14 +341,14 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType: new_type = current_type return PatternType(new_type, rest_type, captures) - def get_sequence_type(self, t: Type) -> Type | None: + def get_sequence_type(self, t: Type, context: Context) -> Type | None: t = get_proper_type(t) if isinstance(t, AnyType): return AnyType(TypeOfAny.from_another_any, t) if isinstance(t, UnionType): - items = [self.get_sequence_type(item) for item in t.items] + items = [self.get_sequence_type(item, context) for item in t.items] not_none_items = [item for item in items if item is not None] - if len(not_none_items) > 0: + if not_none_items: return make_simplified_union(not_none_items) else: return None @@ -324,7 +356,7 @@ def get_sequence_type(self, t: Type) -> Type | None: if self.chk.type_is_iterable(t) and isinstance(t, (Instance, TupleType)): if isinstance(t, TupleType): t = tuple_fallback(t) - return self.chk.iterable_item_type(t) + return self.chk.iterable_item_type(t, context) else: return None @@ -340,17 +372,45 @@ def contract_starred_pattern_types( If star_pos in None the types are returned unchanged. """ - if star_pos is None: - return types - new_types = types[:star_pos] - star_length = len(types) - num_patterns - new_types.append(make_simplified_union(types[star_pos : star_pos + star_length])) - new_types += types[star_pos + star_length :] - - return new_types + unpack_index = find_unpack_in_list(types) + if unpack_index is not None: + # Variadic tuples require "re-shaping" to match the requested pattern. + unpack = types[unpack_index] + assert isinstance(unpack, UnpackType) + unpacked = get_proper_type(unpack.type) + # This should be guaranteed by the normalization in the caller. + assert isinstance(unpacked, Instance) and unpacked.type.fullname == "builtins.tuple" + if star_pos is None: + missing = num_patterns - len(types) + 1 + new_types = types[:unpack_index] + new_types += [unpacked.args[0]] * missing + new_types += types[unpack_index + 1 :] + return new_types + prefix, middle, suffix = split_with_prefix_and_suffix( + tuple([UnpackType(unpacked) if isinstance(t, UnpackType) else t for t in types]), + star_pos, + num_patterns - star_pos, + ) + new_middle = [] + for m in middle: + # The existing code expects the star item type, rather than the type of + # the whole tuple "slice". + if isinstance(m, UnpackType): + new_middle.append(unpacked.args[0]) + else: + new_middle.append(m) + return list(prefix) + [make_simplified_union(new_middle)] + list(suffix) + else: + if star_pos is None: + return types + new_types = types[:star_pos] + star_length = len(types) - num_patterns + new_types.append(make_simplified_union(types[star_pos : star_pos + star_length])) + new_types += types[star_pos + star_length :] + return new_types def expand_starred_pattern_types( - self, types: list[Type], star_pos: int | None, num_types: int + self, types: list[Type], star_pos: int | None, num_types: int, original_unpack: bool ) -> list[Type]: """Undoes the contraction done by contract_starred_pattern_types. @@ -359,6 +419,17 @@ def expand_starred_pattern_types( """ if star_pos is None: return types + if original_unpack: + # In the case where original tuple type has an unpack item, it is not practical + # to coerce pattern type back to the original shape (and may not even be possible), + # so we only restore the type of the star item. + res = [] + for i, t in enumerate(types): + if i != star_pos: + res.append(t) + else: + res.append(UnpackType(self.chk.named_generic_type("builtins.tuple", [t]))) + return res new_types = types[:star_pos] star_length = num_types - len(types) + 1 new_types += [types[star_pos]] * star_length @@ -454,15 +525,29 @@ def visit_class_pattern(self, o: ClassPattern) -> PatternType: return self.early_non_match() if isinstance(type_info, TypeInfo): any_type = AnyType(TypeOfAny.implementation_artifact) - typ: Type = Instance(type_info, [any_type] * len(type_info.defn.type_vars)) + args: list[Type] = [] + for tv in type_info.defn.type_vars: + if isinstance(tv, TypeVarTupleType): + args.append( + UnpackType(self.chk.named_generic_type("builtins.tuple", [any_type])) + ) + else: + args.append(any_type) + typ: Type = Instance(type_info, args) elif isinstance(type_info, TypeAlias): typ = type_info.target + elif ( + isinstance(type_info, Var) + and type_info.type is not None + and isinstance(get_proper_type(type_info.type), AnyType) + ): + typ = type_info.type else: - if isinstance(type_info, Var): - name = str(type_info.type) + if isinstance(type_info, Var) and type_info.type is not None: + name = type_info.type.str_with_options(self.options) else: name = type_info.name - self.msg.fail(message_registry.CLASS_PATTERN_TYPE_REQUIRED.format(name), o.class_ref) + self.msg.fail(message_registry.CLASS_PATTERN_TYPE_REQUIRED.format(name), o) return self.early_non_match() new_type, rest_type = self.chk.conditional_types_with_intersection( @@ -508,7 +593,12 @@ def visit_class_pattern(self, o: ClassPattern) -> PatternType: ) has_local_errors = local_errors.has_new_errors() if has_local_errors: - self.msg.fail(message_registry.MISSING_MATCH_ARGS.format(typ), o) + self.msg.fail( + message_registry.MISSING_MATCH_ARGS.format( + typ.str_with_options(self.options) + ), + o, + ) return self.early_non_match() proper_match_args_type = get_proper_type(match_args_type) @@ -573,7 +663,10 @@ def visit_class_pattern(self, o: ClassPattern) -> PatternType: if has_local_errors or key_type is None: key_type = AnyType(TypeOfAny.from_error) self.msg.fail( - message_registry.CLASS_PATTERN_UNKNOWN_KEYWORD.format(typ, keyword), pattern + message_registry.CLASS_PATTERN_UNKNOWN_KEYWORD.format( + typ.str_with_options(self.options), keyword + ), + pattern, ) inner_type, inner_rest_type, inner_captures = self.accept(pattern, key_type) diff --git a/mypy/checkstrformat.py b/mypy/checkstrformat.py index e7602f33095d..c63210a96c44 100644 --- a/mypy/checkstrformat.py +++ b/mypy/checkstrformat.py @@ -13,8 +13,8 @@ from __future__ import annotations import re -from typing import TYPE_CHECKING, Callable, Dict, Match, Pattern, Tuple, Union, cast -from typing_extensions import Final, TypeAlias as _TypeAlias +from typing import TYPE_CHECKING, Callable, Dict, Final, Match, Pattern, Tuple, Union, cast +from typing_extensions import TypeAlias as _TypeAlias import mypy.errorcodes as codes from mypy.errors import Errors @@ -47,8 +47,11 @@ TupleType, Type, TypeOfAny, + TypeVarTupleType, TypeVarType, UnionType, + UnpackType, + find_unpack_in_list, get_proper_type, get_proper_types, ) @@ -139,7 +142,6 @@ class ConversionSpecifier: def __init__( self, match: Match[str], start_pos: int = -1, non_standard_format_spec: bool = False ) -> None: - self.whole_seq = match.group() self.start_pos = start_pos @@ -370,7 +372,7 @@ def check_specs_in_format_call( ): # TODO: add support for some custom specs like datetime? self.msg.fail( - "Unrecognized format" ' specification "{}"'.format(spec.format_spec[1:]), + f'Unrecognized format specification "{spec.format_spec[1:]}"', call, code=codes.STRING_FORMATTING, ) @@ -435,9 +437,9 @@ def perform_special_format_checks( actual_type, "__str__" ): self.msg.fail( - 'On Python 3 formatting "b\'abc\'" with "{}" ' - 'produces "b\'abc\'", not "abc"; ' - 'use "{!r}" if this is desired behavior', + 'If x = b\'abc\' then f"{x}" or "{}".format(x) produces "b\'abc\'", ' + 'not "abc". If this is desired behavior, use f"{x!r}" or "{!r}".format(x). ' + "Otherwise, decode the bytes", call, code=codes.STR_BYTES_PY3, ) @@ -480,7 +482,7 @@ def find_replacements_in_call(self, call: CallExpr, keys: list[str]) -> list[Exp expr = self.get_expr_by_name(key, call) if not expr: self.msg.fail( - "Cannot find replacement for named" ' format specifier "{}"'.format(key), + f'Cannot find replacement for named format specifier "{key}"', call, code=codes.STRING_FORMATTING, ) @@ -589,7 +591,7 @@ def apply_field_accessors( return repl assert spec.field - temp_errors = Errors() + temp_errors = Errors(self.chk.options) dummy = DUMMY_FIELD_NAME + spec.field[len(spec.key) :] temp_ast: Node = parse( dummy, fnam="", module=None, options=self.chk.options, errors=temp_errors @@ -683,14 +685,6 @@ def check_str_interpolation(self, expr: FormatStringExpr, replacements: Expressi self.exprchk.accept(expr) specifiers = parse_conversion_specifiers(expr.value) has_mapping_keys = self.analyze_conversion_specifiers(specifiers, expr) - if isinstance(expr, BytesExpr) and self.chk.options.python_version < (3, 5): - self.msg.fail( - "Bytes formatting is only supported in Python 3.5 and later", - replacements, - code=codes.STRING_FORMATTING, - ) - return AnyType(TypeOfAny.from_error) - if has_mapping_keys is None: pass # Error was reported elif has_mapping_keys: @@ -737,6 +731,22 @@ def check_simple_str_interpolation( rep_types: list[Type] = [] if isinstance(rhs_type, TupleType): rep_types = rhs_type.items + unpack_index = find_unpack_in_list(rep_types) + if unpack_index is not None: + # TODO: we should probably warn about potentially short tuple. + # However, without special-casing for tuple(f(i) for in other_tuple) + # this causes false positive on mypy self-check in report.py. + extras = max(0, len(checkers) - len(rep_types) + 1) + unpacked = rep_types[unpack_index] + assert isinstance(unpacked, UnpackType) + unpacked = get_proper_type(unpacked.type) + if isinstance(unpacked, TypeVarTupleType): + unpacked = get_proper_type(unpacked.upper_bound) + assert ( + isinstance(unpacked, Instance) and unpacked.type.fullname == "builtins.tuple" + ) + unpack_items = [unpacked.args[0]] * extras + rep_types = rep_types[:unpack_index] + unpack_items + rep_types[unpack_index + 1 :] elif isinstance(rhs_type, AnyType): return elif isinstance(rhs_type, Instance) and rhs_type.type.fullname == "builtins.tuple": @@ -844,10 +854,14 @@ def build_dict_type(self, expr: FormatStringExpr) -> Type: any_type = AnyType(TypeOfAny.special_form) if isinstance(expr, BytesExpr): bytes_type = self.chk.named_generic_type("builtins.bytes", []) - return self.chk.named_generic_type("typing.Mapping", [bytes_type, any_type]) + return self.chk.named_generic_type( + "_typeshed.SupportsKeysAndGetItem", [bytes_type, any_type] + ) elif isinstance(expr, StrExpr): str_type = self.chk.named_generic_type("builtins.str", []) - return self.chk.named_generic_type("typing.Mapping", [str_type, any_type]) + return self.chk.named_generic_type( + "_typeshed.SupportsKeysAndGetItem", [str_type, any_type] + ) else: assert False, "Unreachable" @@ -946,9 +960,8 @@ def check_s_special_cases(self, expr: FormatStringExpr, typ: Type, context: Cont # Couple special cases for string formatting. if has_type_component(typ, "builtins.bytes"): self.msg.fail( - 'On Python 3 formatting "b\'abc\'" with "%s" ' - 'produces "b\'abc\'", not "abc"; ' - 'use "%r" if this is desired behavior', + 'If x = b\'abc\' then "%s" % x produces "b\'abc\'", not "abc". ' + 'If this is desired behavior use "%r" % x. Otherwise, decode the bytes', context, code=codes.STR_BYTES_PY3, ) @@ -1021,13 +1034,6 @@ def conversion_type( NUMERIC_TYPES = NUMERIC_TYPES_NEW if format_call else NUMERIC_TYPES_OLD INT_TYPES = REQUIRE_INT_NEW if format_call else REQUIRE_INT_OLD if p == "b" and not format_call: - if self.chk.options.python_version < (3, 5): - self.msg.fail( - 'Format character "b" is only supported in Python 3.5 and later', - context, - code=codes.STRING_FORMATTING, - ) - return None if not isinstance(expr, BytesExpr): self.msg.fail( 'Format character "b" is only supported on bytes patterns', diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 190782a3bded..a6bf021000c1 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -19,6 +19,7 @@ Any, Callable, Dict, + Final, Iterable, List, Mapping, @@ -28,13 +29,13 @@ Tuple, Union, ) -from typing_extensions import Final, TypeAlias as _TypeAlias +from typing_extensions import TypeAlias as _TypeAlias from mypy import defaults from mypy.options import PER_MODULE_OPTIONS, Options _CONFIG_VALUE_TYPES: _TypeAlias = Union[ - str, bool, int, float, Dict[str, str], List[str], Tuple[int, int], + str, bool, int, float, Dict[str, str], List[str], Tuple[int, int] ] _INI_PARSER_CALLABLE: _TypeAlias = Callable[[Any], _CONFIG_VALUE_TYPES] @@ -80,6 +81,20 @@ def validate_codes(codes: list[str]) -> list[str]: return codes +def validate_package_allow_list(allow_list: list[str]) -> list[str]: + for p in allow_list: + msg = f"Invalid allow list entry: {p}" + if "*" in p: + raise argparse.ArgumentTypeError( + f"{msg} (entries are already prefixes so must not contain *)" + ) + if "\\" in p or "/" in p: + raise argparse.ArgumentTypeError( + f"{msg} (entries must be packages like foo.bar not directories or files)" + ) + return allow_list + + def expand_path(path: str) -> str: """Expand the user home directory and any environment variables contained within the provided path. @@ -137,6 +152,17 @@ def check_follow_imports(choice: str) -> str: return choice +def check_junit_format(choice: str) -> str: + choices = ["global", "per_file"] + if choice not in choices: + raise argparse.ArgumentTypeError( + "invalid choice '{}' (choose from {})".format( + choice, ", ".join(f"'{x}'" for x in choices) + ) + ) + return choice + + def split_commas(value: str) -> list[str]: # Uses a bit smarter technique to allow last trailing comma # and to remove last `""` item from the split. @@ -158,11 +184,15 @@ def split_commas(value: str) -> list[str]: "files": split_and_match_files, "quickstart_file": expand_path, "junit_xml": expand_path, + "junit_format": check_junit_format, "follow_imports": check_follow_imports, "no_site_packages": bool, "plugins": lambda s: [p.strip() for p in split_commas(s)], "always_true": lambda s: [p.strip() for p in split_commas(s)], "always_false": lambda s: [p.strip() for p in split_commas(s)], + "untyped_calls_exclude": lambda s: validate_package_allow_list( + [p.strip() for p in split_commas(s)] + ), "enable_incomplete_feature": lambda s: [p.strip() for p in split_commas(s)], "disable_error_code": lambda s: validate_codes([p.strip() for p in split_commas(s)]), "enable_error_code": lambda s: validate_codes([p.strip() for p in split_commas(s)]), @@ -182,10 +212,12 @@ def split_commas(value: str) -> list[str]: "python_version": parse_version, "mypy_path": lambda s: [expand_path(p) for p in try_split(s, "[,:]")], "files": lambda s: split_and_match_files_list(try_split(s)), + "junit_format": lambda s: check_junit_format(str(s)), "follow_imports": lambda s: check_follow_imports(str(s)), "plugins": try_split, "always_true": try_split, "always_false": try_split, + "untyped_calls_exclude": lambda s: validate_package_allow_list(try_split(s)), "enable_incomplete_feature": try_split, "disable_error_code": lambda s: validate_codes(try_split(s)), "enable_error_code": lambda s: validate_codes(try_split(s)), @@ -273,14 +305,18 @@ def parse_config_file( ) if report_dirs: print( - "%sPer-module sections should not specify reports (%s)" - % (prefix, ", ".join(s + "_report" for s in sorted(report_dirs))), + prefix, + "Per-module sections should not specify reports ({})".format( + ", ".join(s + "_report" for s in sorted(report_dirs)) + ), file=stderr, ) if set(updates) - PER_MODULE_OPTIONS: print( - "%sPer-module sections should only specify per-module flags (%s)" - % (prefix, ", ".join(sorted(set(updates) - PER_MODULE_OPTIONS))), + prefix, + "Per-module sections should only specify per-module flags ({})".format( + ", ".join(sorted(set(updates) - PER_MODULE_OPTIONS)) + ), file=stderr, ) updates = {k: v for k, v in updates.items() if k in PER_MODULE_OPTIONS} @@ -296,8 +332,9 @@ def parse_config_file( "*" in x and x != "*" for x in glob.split(".") ): print( - "%sPatterns must be fully-qualified module names, optionally " - "with '*' in some components (e.g spam.*.eggs.*)" % prefix, + prefix, + "Patterns must be fully-qualified module names, optionally " + "with '*' in some components (e.g spam.*.eggs.*)", file=stderr, ) else: @@ -310,7 +347,7 @@ def get_prefix(file_read: str, name: str) -> str: else: module_name_str = name - return f"{file_read}: [{module_name_str}]: " + return f"{file_read}: [{module_name_str}]:" def is_toml(filename: str) -> bool: @@ -392,8 +429,7 @@ def destructure_overrides(toml_data: dict[str, Any]) -> dict[str, Any]: raise ConfigTOMLValueError( "toml config file contains " "[[tool.mypy.overrides]] sections with conflicting " - "values. Module '%s' has two different values for '%s'" - % (module, new_key) + f"values. Module '{module}' has two different values for '{new_key}'" ) result[old_config_name][new_key] = new_value @@ -415,11 +451,26 @@ def parse_section( """ results: dict[str, object] = {} report_dirs: dict[str, str] = {} + + # Because these fields exist on Options, without proactive checking, we would accept them + # and crash later + invalid_options = { + "enabled_error_codes": "enable_error_code", + "disabled_error_codes": "disable_error_code", + } + for key in section: invert = False options_key = key if key in config_types: ct = config_types[key] + elif key in invalid_options: + print( + f"{prefix}Unrecognized option: {key} = {section[key]}" + f" (did you mean {invalid_options[key]}?)", + file=stderr, + ) + continue else: dv = None # We have to keep new_semantic_analyzer in Options @@ -538,10 +589,7 @@ def split_directive(s: str) -> tuple[list[str], list[str]]: def mypy_comments_to_config_map(line: str, template: Options) -> tuple[dict[str, str], list[str]]: - """Rewrite the mypy comment syntax into ini file syntax. - - Returns - """ + """Rewrite the mypy comment syntax into ini file syntax.""" options = {} entries, errors = split_directive(line) for entry in entries: diff --git a/mypy/constant_fold.py b/mypy/constant_fold.py index a22c1b9ba9e5..4582b2a7396d 100644 --- a/mypy/constant_fold.py +++ b/mypy/constant_fold.py @@ -5,14 +5,23 @@ from __future__ import annotations -from typing import Union -from typing_extensions import Final - -from mypy.nodes import Expression, FloatExpr, IntExpr, NameExpr, OpExpr, StrExpr, UnaryExpr, Var +from typing import Final, Union + +from mypy.nodes import ( + ComplexExpr, + Expression, + FloatExpr, + IntExpr, + NameExpr, + OpExpr, + StrExpr, + UnaryExpr, + Var, +) # All possible result types of constant folding -ConstantValue = Union[int, bool, float, str] -CONST_TYPES: Final = (int, bool, float, str) +ConstantValue = Union[int, bool, float, complex, str] +CONST_TYPES: Final = (int, bool, float, complex, str) def constant_fold_expr(expr: Expression, cur_mod_id: str) -> ConstantValue | None: @@ -39,6 +48,8 @@ def constant_fold_expr(expr: Expression, cur_mod_id: str) -> ConstantValue | Non return expr.value if isinstance(expr, FloatExpr): return expr.value + if isinstance(expr, ComplexExpr): + return expr.value elif isinstance(expr, NameExpr): if expr.name == "True": return True @@ -56,24 +67,60 @@ def constant_fold_expr(expr: Expression, cur_mod_id: str) -> ConstantValue | Non elif isinstance(expr, OpExpr): left = constant_fold_expr(expr.left, cur_mod_id) right = constant_fold_expr(expr.right, cur_mod_id) - if isinstance(left, int) and isinstance(right, int): - return constant_fold_binary_int_op(expr.op, left, right) - elif isinstance(left, str) and isinstance(right, str): - return constant_fold_binary_str_op(expr.op, left, right) + if left is not None and right is not None: + return constant_fold_binary_op(expr.op, left, right) elif isinstance(expr, UnaryExpr): value = constant_fold_expr(expr.expr, cur_mod_id) - if isinstance(value, int): - return constant_fold_unary_int_op(expr.op, value) + if value is not None: + return constant_fold_unary_op(expr.op, value) + return None + + +def constant_fold_binary_op( + op: str, left: ConstantValue, right: ConstantValue +) -> ConstantValue | None: + if isinstance(left, int) and isinstance(right, int): + return constant_fold_binary_int_op(op, left, right) + + # Float and mixed int/float arithmetic. + if isinstance(left, float) and isinstance(right, float): + return constant_fold_binary_float_op(op, left, right) + elif isinstance(left, float) and isinstance(right, int): + return constant_fold_binary_float_op(op, left, right) + elif isinstance(left, int) and isinstance(right, float): + return constant_fold_binary_float_op(op, left, right) + + # String concatenation and multiplication. + if op == "+" and isinstance(left, str) and isinstance(right, str): + return left + right + elif op == "*" and isinstance(left, str) and isinstance(right, int): + return left * right + elif op == "*" and isinstance(left, int) and isinstance(right, str): + return left * right + + # Complex construction. + if op == "+" and isinstance(left, (int, float)) and isinstance(right, complex): + return left + right + elif op == "+" and isinstance(left, complex) and isinstance(right, (int, float)): + return left + right + elif op == "-" and isinstance(left, (int, float)) and isinstance(right, complex): + return left - right + elif op == "-" and isinstance(left, complex) and isinstance(right, (int, float)): + return left - right + return None -def constant_fold_binary_int_op(op: str, left: int, right: int) -> int | None: +def constant_fold_binary_int_op(op: str, left: int, right: int) -> int | float | None: if op == "+": return left + right if op == "-": return left - right elif op == "*": return left * right + elif op == "/": + if right != 0: + return left / right elif op == "//": if right != 0: return left // right @@ -100,17 +147,41 @@ def constant_fold_binary_int_op(op: str, left: int, right: int) -> int | None: return None -def constant_fold_unary_int_op(op: str, value: int) -> int | None: - if op == "-": - return -value - elif op == "~": - return ~value - elif op == "+": - return value +def constant_fold_binary_float_op(op: str, left: int | float, right: int | float) -> float | None: + assert not (isinstance(left, int) and isinstance(right, int)), (op, left, right) + if op == "+": + return left + right + elif op == "-": + return left - right + elif op == "*": + return left * right + elif op == "/": + if right != 0: + return left / right + elif op == "//": + if right != 0: + return left // right + elif op == "%": + if right != 0: + return left % right + elif op == "**": + if (left < 0 and isinstance(right, int)) or left > 0: + try: + ret = left**right + except OverflowError: + return None + else: + assert isinstance(ret, float), ret + return ret + return None -def constant_fold_binary_str_op(op: str, left: str, right: str) -> str | None: - if op == "+": - return left + right +def constant_fold_unary_op(op: str, value: ConstantValue) -> int | float | None: + if op == "-" and isinstance(value, (int, float)): + return -value + elif op == "~" and isinstance(value, int): + return ~value + elif op == "+" and isinstance(value, (int, float)): + return value return None diff --git a/mypy/constraints.py b/mypy/constraints.py index a8f04094ca63..cdfa39ac45f3 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -2,15 +2,23 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, List, Sequence -from typing_extensions import Final +from typing import TYPE_CHECKING, Final, Iterable, List, Sequence import mypy.subtypes import mypy.typeops from mypy.argmap import ArgTypeExpander from mypy.erasetype import erase_typevars from mypy.maptype import map_instance_to_supertype -from mypy.nodes import ARG_OPT, ARG_POS, CONTRAVARIANT, COVARIANT, ArgKind +from mypy.nodes import ( + ARG_OPT, + ARG_POS, + ARG_STAR, + ARG_STAR2, + CONTRAVARIANT, + COVARIANT, + ArgKind, + TypeInfo, +) from mypy.types import ( TUPLE_LIKE_INSTANCE_NAMES, AnyType, @@ -20,6 +28,7 @@ Instance, LiteralType, NoneType, + NormalizedCallableType, Overloaded, Parameters, ParamSpecType, @@ -41,20 +50,15 @@ UninhabitedType, UnionType, UnpackType, - callable_with_ellipsis, + find_unpack_in_list, get_proper_type, has_recursive_types, has_type_vars, is_named_instance, - is_union_with_any, -) -from mypy.typestate import type_state -from mypy.typevartuples import ( - extract_unpack, - find_unpack_in_list, - split_with_mapped_and_template, split_with_prefix_and_suffix, ) +from mypy.types_utils import is_union_with_any +from mypy.typestate import type_state if TYPE_CHECKING: from mypy.infer import ArgumentInferContext @@ -76,8 +80,14 @@ class Constraint: def __init__(self, type_var: TypeVarLikeType, op: int, target: Type) -> None: self.type_var = type_var.id self.op = op + # TODO: should we add "assert not isinstance(target, UnpackType)"? + # UnpackType is a synthetic type, and is never valid as a constraint target. self.target = target self.origin_type_var = type_var + # These are additional type variables that should be solved for together with type_var. + # TODO: A cleaner solution may be to modify the return type of infer_constraints() + # to include these instead, but this is a rather big refactoring. + self.extra_tvars: list[TypeVarLikeType] = [] def __repr__(self) -> str: op_str = "<:" @@ -98,6 +108,7 @@ def infer_constraints_for_callable( callee: CallableType, arg_types: Sequence[Type | None], arg_kinds: list[ArgKind], + arg_names: Sequence[str | None] | None, formal_to_actual: list[list[int]], context: ArgumentInferContext, ) -> list[Constraint]: @@ -108,30 +119,61 @@ def infer_constraints_for_callable( constraints: list[Constraint] = [] mapper = ArgTypeExpander(context) + param_spec = callee.param_spec() + param_spec_arg_types = [] + param_spec_arg_names = [] + param_spec_arg_kinds = [] + + incomplete_star_mapping = False + for i, actuals in enumerate(formal_to_actual): + for actual in actuals: + if actual is None and callee.arg_kinds[i] in (ARG_STAR, ARG_STAR2): + # We can't use arguments to infer ParamSpec constraint, if only some + # are present in the current inference pass. + incomplete_star_mapping = True + break + for i, actuals in enumerate(formal_to_actual): if isinstance(callee.arg_types[i], UnpackType): unpack_type = callee.arg_types[i] assert isinstance(unpack_type, UnpackType) - # In this case we are binding all of the actuals to *args + # In this case we are binding all the actuals to *args, # and we want a constraint that the typevar tuple being unpacked # is equal to a type list of all the actuals. actual_types = [] + + unpacked_type = get_proper_type(unpack_type.type) + if isinstance(unpacked_type, TypeVarTupleType): + tuple_instance = unpacked_type.tuple_fallback + elif isinstance(unpacked_type, TupleType): + tuple_instance = unpacked_type.partial_fallback + else: + assert False, "mypy bug: unhandled constraint inference case" + for actual in actuals: actual_arg_type = arg_types[actual] if actual_arg_type is None: continue - actual_types.append( - mapper.expand_actual_type( - actual_arg_type, - arg_kinds[actual], - callee.arg_names[i], - callee.arg_kinds[i], - ) + expanded_actual = mapper.expand_actual_type( + actual_arg_type, + arg_kinds[actual], + callee.arg_names[i], + callee.arg_kinds[i], + allow_unpack=True, ) - unpacked_type = get_proper_type(unpack_type.type) + if arg_kinds[actual] != ARG_STAR or isinstance( + get_proper_type(actual_arg_type), TupleType + ): + actual_types.append(expanded_actual) + else: + # If we are expanding an iterable inside * actual, append a homogeneous item instead + actual_types.append( + UnpackType(tuple_instance.copy_modified(args=[expanded_actual])) + ) + if isinstance(unpacked_type, TypeVarTupleType): constraints.append( Constraint( @@ -147,15 +189,32 @@ def infer_constraints_for_callable( inner_unpack = unpacked_type.items[0] assert isinstance(inner_unpack, UnpackType) inner_unpacked_type = get_proper_type(inner_unpack.type) - assert isinstance(inner_unpacked_type, TypeVarTupleType) suffix_len = len(unpacked_type.items) - 1 - constraints.append( - Constraint( - inner_unpacked_type, - SUPERTYPE_OF, - TupleType(actual_types[:-suffix_len], inner_unpacked_type.tuple_fallback), + if isinstance(inner_unpacked_type, TypeVarTupleType): + # Variadic item can be either *Ts... + constraints.append( + Constraint( + inner_unpacked_type, + SUPERTYPE_OF, + TupleType( + actual_types[:-suffix_len], inner_unpacked_type.tuple_fallback + ), + ) ) - ) + else: + # ...or it can be a homogeneous tuple. + assert ( + isinstance(inner_unpacked_type, Instance) + and inner_unpacked_type.type.fullname == "builtins.tuple" + ) + for at in actual_types[:-suffix_len]: + constraints.extend( + infer_constraints(inner_unpacked_type.args[0], at, SUPERTYPE_OF) + ) + # Now handle the suffix (if any). + if suffix_len: + for tt, at in zip(unpacked_type.items[1:], actual_types[-suffix_len:]): + constraints.extend(infer_constraints(tt, at, SUPERTYPE_OF)) else: assert False, "mypy bug: unhandled constraint inference case" else: @@ -167,13 +226,53 @@ def infer_constraints_for_callable( actual_type = mapper.expand_actual_type( actual_arg_type, arg_kinds[actual], callee.arg_names[i], callee.arg_kinds[i] ) - c = infer_constraints(callee.arg_types[i], actual_type, SUPERTYPE_OF) - constraints.extend(c) - + if param_spec and callee.arg_kinds[i] in (ARG_STAR, ARG_STAR2): + # If actual arguments are mapped to ParamSpec type, we can't infer individual + # constraints, instead store them and infer single constraint at the end. + # It is impossible to map actual kind to formal kind, so use some heuristic. + # This inference is used as a fallback, so relying on heuristic should be OK. + if not incomplete_star_mapping: + param_spec_arg_types.append( + mapper.expand_actual_type( + actual_arg_type, arg_kinds[actual], None, arg_kinds[actual] + ) + ) + actual_kind = arg_kinds[actual] + param_spec_arg_kinds.append( + ARG_POS if actual_kind not in (ARG_STAR, ARG_STAR2) else actual_kind + ) + param_spec_arg_names.append(arg_names[actual] if arg_names else None) + else: + c = infer_constraints(callee.arg_types[i], actual_type, SUPERTYPE_OF) + constraints.extend(c) + if ( + param_spec + and not any(c.type_var == param_spec.id for c in constraints) + and not incomplete_star_mapping + ): + # Use ParamSpec constraint from arguments only if there are no other constraints, + # since as explained above it is quite ad-hoc. + constraints.append( + Constraint( + param_spec, + SUPERTYPE_OF, + Parameters( + arg_types=param_spec_arg_types, + arg_kinds=param_spec_arg_kinds, + arg_names=param_spec_arg_names, + imprecise_arg_kinds=True, + ), + ) + ) + if any(isinstance(v, ParamSpecType) for v in callee.variables): + # As a perf optimization filter imprecise constraints only when we can have them. + constraints = filter_imprecise_kinds(constraints) return constraints -def infer_constraints(template: Type, actual: Type, direction: int) -> list[Constraint]: +def infer_constraints( + template: Type, actual: Type, direction: int, skip_neg_op: bool = False +) -> list[Constraint]: """Infer type constraints. Match a template type, which may contain type variable references, @@ -192,7 +291,9 @@ def infer_constraints(template: Type, actual: Type, direction: int) -> list[Cons ((T, S), (X, Y)) --> T :> X and S :> Y (X[T], Any) --> T <: Any and T :> Any - The constraints are represented as Constraint objects. + The constraints are represented as Constraint objects. If skip_neg_op == True, + then skip adding reverse (polymorphic) constraints (since this is already a call + to infer such constraints). """ if any( get_proper_type(template) == get_proper_type(t) @@ -207,13 +308,15 @@ def infer_constraints(template: Type, actual: Type, direction: int) -> list[Cons # Return early on an empty branch. return [] type_state.inferring.append((template, actual)) - res = _infer_constraints(template, actual, direction) + res = _infer_constraints(template, actual, direction, skip_neg_op) type_state.inferring.pop() return res - return _infer_constraints(template, actual, direction) + return _infer_constraints(template, actual, direction, skip_neg_op) -def _infer_constraints(template: Type, actual: Type, direction: int) -> list[Constraint]: +def _infer_constraints( + template: Type, actual: Type, direction: int, skip_neg_op: bool +) -> list[Constraint]: orig_template = template template = get_proper_type(template) actual = get_proper_type(actual) @@ -243,6 +346,18 @@ def _infer_constraints(template: Type, actual: Type, direction: int) -> list[Con if isinstance(template, TypeVarType): return [Constraint(template, direction, actual)] + if ( + isinstance(actual, TypeVarType) + and not actual.id.is_meta_var() + and direction == SUPERTYPE_OF + ): + # Unless template is also a type variable (or a union that contains one), using the upper + # bound for inference will usually give better result for actual that is a type variable. + if not isinstance(template, UnionType) or not any( + isinstance(t, TypeVarType) for t in template.items + ): + actual = get_proper_type(actual.upper_bound) + # Now handle the case of either template or actual being a Union. # For a Union to be a subtype of another type, every item of the Union # must be a subtype of that type, so concatenate the constraints. @@ -289,7 +404,7 @@ def _infer_constraints(template: Type, actual: Type, direction: int) -> list[Con return [] # Remaining cases are handled by ConstraintBuilderVisitor. - return template.accept(ConstraintBuilderVisitor(actual, direction)) + return template.accept(ConstraintBuilderVisitor(actual, direction, skip_neg_op)) def infer_constraints_if_possible( @@ -515,10 +630,14 @@ class ConstraintBuilderVisitor(TypeVisitor[List[Constraint]]): # TODO: The value may be None. Is that actually correct? actual: ProperType - def __init__(self, actual: ProperType, direction: int) -> None: + def __init__(self, actual: ProperType, direction: int, skip_neg_op: bool) -> None: # Direction must be SUBTYPE_OF or SUPERTYPE_OF. self.actual = actual self.direction = direction + # Whether to skip polymorphic inference (involves inference in opposite direction) + # this is used to prevent infinite recursion when both template and actual are + # generic callables. + self.skip_neg_op = skip_neg_op # Trivial leaf types @@ -568,10 +687,14 @@ def visit_unpack_type(self, template: UnpackType) -> list[Constraint]: raise RuntimeError("Mypy bug: unpack should be handled at a higher level.") def visit_parameters(self, template: Parameters) -> list[Constraint]: - # constraining Any against C[P] turns into infer_against_any([P], Any) - # ... which seems like the only case this can happen. Better to fail loudly. + # Constraining Any against C[P] turns into infer_against_any([P], Any) + # ... which seems like the only case this can happen. Better to fail loudly otherwise. if isinstance(self.actual, AnyType): return self.infer_against_any(template.arg_types, self.actual) + if type_state.infer_polymorphic and isinstance(self.actual, Parameters): + # For polymorphic inference we need to be able to infer secondary constraints + # in situations like [x: T] <: P <: [x: int]. + return infer_callable_arguments_constraints(template, self.actual, self.direction) raise RuntimeError("Parameters cannot be constrained to") # Non-leaf types @@ -648,28 +771,23 @@ def visit_instance(self, template: Instance) -> list[Constraint]: tvars = mapped.type.defn.type_vars if instance.type.has_type_var_tuple_type: + # Variadic types need special handling to map each type argument to + # the correct corresponding type variable. assert instance.type.type_var_tuple_prefix is not None assert instance.type.type_var_tuple_suffix is not None - assert mapped.type.type_var_tuple_prefix is not None - assert mapped.type.type_var_tuple_suffix is not None - - unpack_constraints, mapped_args, instance_args = build_constraints_for_unpack( - mapped.args, - mapped.type.type_var_tuple_prefix, - mapped.type.type_var_tuple_suffix, - instance.args, - instance.type.type_var_tuple_prefix, - instance.type.type_var_tuple_suffix, - self.direction, + prefix_len = instance.type.type_var_tuple_prefix + suffix_len = instance.type.type_var_tuple_suffix + tvt = instance.type.defn.type_vars[prefix_len] + assert isinstance(tvt, TypeVarTupleType) + fallback = tvt.tuple_fallback + i_prefix, i_middle, i_suffix = split_with_prefix_and_suffix( + instance.args, prefix_len, suffix_len ) - res.extend(unpack_constraints) - - tvars_prefix, _, tvars_suffix = split_with_prefix_and_suffix( - tuple(tvars), - instance.type.type_var_tuple_prefix, - instance.type.type_var_tuple_suffix, + m_prefix, m_middle, m_suffix = split_with_prefix_and_suffix( + mapped.args, prefix_len, suffix_len ) - tvars = list(tvars_prefix + tvars_suffix) + instance_args = i_prefix + (TupleType(list(i_middle), fallback),) + i_suffix + mapped_args = m_prefix + (TupleType(list(m_middle), fallback),) + m_suffix else: mapped_args = mapped.args instance_args = instance.args @@ -677,7 +795,6 @@ def visit_instance(self, template: Instance) -> list[Constraint]: # N.B: We use zip instead of indexing because the lengths might have # mismatches during daemon reprocessing. for tvar, mapped_arg, instance_arg in zip(tvars, mapped_args, instance_args): - # TODO(PEP612): More ParamSpec work (or is Parameters the only thing accepted) if isinstance(tvar, TypeVarType): # The constraints for generic type parameters depend on variance. # Include constraints from both directions if invariant. @@ -688,64 +805,60 @@ def visit_instance(self, template: Instance) -> list[Constraint]: infer_constraints(mapped_arg, instance_arg, neg_op(self.direction)) ) elif isinstance(tvar, ParamSpecType) and isinstance(mapped_arg, ParamSpecType): - suffix = get_proper_type(instance_arg) - - if isinstance(suffix, CallableType): - prefix = mapped_arg.prefix - from_concat = bool(prefix.arg_types) or suffix.from_concatenate - suffix = suffix.copy_modified(from_concatenate=from_concat) - - if isinstance(suffix, Parameters) or isinstance(suffix, CallableType): - # no such thing as variance for ParamSpecs - # TODO: is there a case I am missing? - # TODO: constraints between prefixes - prefix = mapped_arg.prefix - suffix = suffix.copy_modified( - suffix.arg_types[len(prefix.arg_types) :], - suffix.arg_kinds[len(prefix.arg_kinds) :], - suffix.arg_names[len(prefix.arg_names) :], + prefix = mapped_arg.prefix + if isinstance(instance_arg, Parameters): + # No such thing as variance for ParamSpecs, consider them invariant + # TODO: constraints between prefixes using + # infer_callable_arguments_constraints() + suffix: Type = instance_arg.copy_modified( + instance_arg.arg_types[len(prefix.arg_types) :], + instance_arg.arg_kinds[len(prefix.arg_kinds) :], + instance_arg.arg_names[len(prefix.arg_names) :], ) + res.append(Constraint(mapped_arg, SUBTYPE_OF, suffix)) res.append(Constraint(mapped_arg, SUPERTYPE_OF, suffix)) - elif isinstance(suffix, ParamSpecType): + elif isinstance(instance_arg, ParamSpecType): + suffix = instance_arg.copy_modified( + prefix=Parameters( + instance_arg.prefix.arg_types[len(prefix.arg_types) :], + instance_arg.prefix.arg_kinds[len(prefix.arg_kinds) :], + instance_arg.prefix.arg_names[len(prefix.arg_names) :], + ) + ) + res.append(Constraint(mapped_arg, SUBTYPE_OF, suffix)) res.append(Constraint(mapped_arg, SUPERTYPE_OF, suffix)) - else: - # This case should have been handled above. - assert not isinstance(tvar, TypeVarTupleType) + elif isinstance(tvar, TypeVarTupleType): + # Handle variadic type variables covariantly for consistency. + res.extend(infer_constraints(mapped_arg, instance_arg, self.direction)) return res elif self.direction == SUPERTYPE_OF and instance.type.has_base(template.type.fullname): mapped = map_instance_to_supertype(instance, template.type) tvars = template.type.defn.type_vars if template.type.has_type_var_tuple_type: - assert mapped.type.type_var_tuple_prefix is not None - assert mapped.type.type_var_tuple_suffix is not None + # Variadic types need special handling to map each type argument to + # the correct corresponding type variable. assert template.type.type_var_tuple_prefix is not None assert template.type.type_var_tuple_suffix is not None - - unpack_constraints, mapped_args, template_args = build_constraints_for_unpack( - mapped.args, - mapped.type.type_var_tuple_prefix, - mapped.type.type_var_tuple_suffix, - template.args, - template.type.type_var_tuple_prefix, - template.type.type_var_tuple_suffix, - self.direction, + prefix_len = template.type.type_var_tuple_prefix + suffix_len = template.type.type_var_tuple_suffix + tvt = template.type.defn.type_vars[prefix_len] + assert isinstance(tvt, TypeVarTupleType) + fallback = tvt.tuple_fallback + t_prefix, t_middle, t_suffix = split_with_prefix_and_suffix( + template.args, prefix_len, suffix_len ) - res.extend(unpack_constraints) - - tvars_prefix, _, tvars_suffix = split_with_prefix_and_suffix( - tuple(tvars), - template.type.type_var_tuple_prefix, - template.type.type_var_tuple_suffix, + m_prefix, m_middle, m_suffix = split_with_prefix_and_suffix( + mapped.args, prefix_len, suffix_len ) - tvars = list(tvars_prefix + tvars_suffix) + template_args = t_prefix + (TupleType(list(t_middle), fallback),) + t_suffix + mapped_args = m_prefix + (TupleType(list(m_middle), fallback),) + m_suffix else: mapped_args = mapped.args template_args = template.args # N.B: We use zip instead of indexing because the lengths might have # mismatches during daemon reprocessing. for tvar, mapped_arg, template_arg in zip(tvars, mapped_args, template_args): - assert not isinstance(tvar, TypeVarTupleType) if isinstance(tvar, TypeVarType): # The constraints for generic type parameters depend on variance. # Include constraints from both directions if invariant. @@ -758,30 +871,32 @@ def visit_instance(self, template: Instance) -> list[Constraint]: elif isinstance(tvar, ParamSpecType) and isinstance( template_arg, ParamSpecType ): - suffix = get_proper_type(mapped_arg) - - if isinstance(suffix, CallableType): - prefix = template_arg.prefix - from_concat = bool(prefix.arg_types) or suffix.from_concatenate - suffix = suffix.copy_modified(from_concatenate=from_concat) - - if isinstance(suffix, Parameters) or isinstance(suffix, CallableType): - # no such thing as variance for ParamSpecs - # TODO: is there a case I am missing? - # TODO: constraints between prefixes - prefix = template_arg.prefix - - suffix = suffix.copy_modified( - suffix.arg_types[len(prefix.arg_types) :], - suffix.arg_kinds[len(prefix.arg_kinds) :], - suffix.arg_names[len(prefix.arg_names) :], + prefix = template_arg.prefix + if isinstance(mapped_arg, Parameters): + # No such thing as variance for ParamSpecs, consider them invariant + # TODO: constraints between prefixes using + # infer_callable_arguments_constraints() + suffix = mapped_arg.copy_modified( + mapped_arg.arg_types[len(prefix.arg_types) :], + mapped_arg.arg_kinds[len(prefix.arg_kinds) :], + mapped_arg.arg_names[len(prefix.arg_names) :], ) + res.append(Constraint(template_arg, SUBTYPE_OF, suffix)) res.append(Constraint(template_arg, SUPERTYPE_OF, suffix)) - elif isinstance(suffix, ParamSpecType): + elif isinstance(mapped_arg, ParamSpecType): + suffix = mapped_arg.copy_modified( + prefix=Parameters( + mapped_arg.prefix.arg_types[len(prefix.arg_types) :], + mapped_arg.prefix.arg_kinds[len(prefix.arg_kinds) :], + mapped_arg.prefix.arg_names[len(prefix.arg_names) :], + ) + ) + res.append(Constraint(template_arg, SUBTYPE_OF, suffix)) res.append(Constraint(template_arg, SUPERTYPE_OF, suffix)) - else: - # This case should have been handled above. - assert not isinstance(tvar, TypeVarTupleType) + elif isinstance(tvar, TypeVarTupleType): + # Consider variadic type variables to be invariant. + res.extend(infer_constraints(template_arg, mapped_arg, SUBTYPE_OF)) + res.extend(infer_constraints(template_arg, mapped_arg, SUPERTYPE_OF)) return res if ( template.type.is_protocol @@ -833,13 +948,23 @@ def visit_instance(self, template: Instance) -> list[Constraint]: and self.direction == SUPERTYPE_OF ): for item in actual.items: + if isinstance(item, UnpackType): + unpacked = get_proper_type(item.type) + if isinstance(unpacked, TypeVarTupleType): + # Cannot infer anything for T from [T, ...] <: *Ts + continue + assert ( + isinstance(unpacked, Instance) + and unpacked.type.fullname == "builtins.tuple" + ) + item = unpacked.args[0] cb = infer_constraints(template.args[0], item, SUPERTYPE_OF) res.extend(cb) return res elif isinstance(actual, TupleType) and self.direction == SUPERTYPE_OF: return infer_constraints(template, mypy.typeops.tuple_fallback(actual), self.direction) elif isinstance(actual, TypeVarType): - if not actual.values: + if not actual.values and not actual.id.is_meta_var(): return infer_constraints(template, actual.upper_bound, self.direction) return [] elif isinstance(actual, ParamSpecType): @@ -883,83 +1008,130 @@ def visit_callable_type(self, template: CallableType) -> list[Constraint]: # Normalize callables before matching against each other. # Note that non-normalized callables can be created in annotations # using e.g. callback protocols. + # TODO: check that callables match? Ideally we should not infer constraints + # callables that can never be subtypes of one another in given direction. template = template.with_unpacked_kwargs() + extra_tvars = False if isinstance(self.actual, CallableType): res: list[Constraint] = [] cactual = self.actual.with_unpacked_kwargs() param_spec = template.param_spec() + + template_ret_type, cactual_ret_type = template.ret_type, cactual.ret_type + if template.type_guard is not None and cactual.type_guard is not None: + template_ret_type = template.type_guard + cactual_ret_type = cactual.type_guard + elif template.type_guard is not None: + template_ret_type = AnyType(TypeOfAny.special_form) + elif cactual.type_guard is not None: + cactual_ret_type = AnyType(TypeOfAny.special_form) + + if template.type_is is not None and cactual.type_is is not None: + template_ret_type = template.type_is + cactual_ret_type = cactual.type_is + elif template.type_is is not None: + template_ret_type = AnyType(TypeOfAny.special_form) + elif cactual.type_is is not None: + cactual_ret_type = AnyType(TypeOfAny.special_form) + + res.extend(infer_constraints(template_ret_type, cactual_ret_type, self.direction)) + if param_spec is None: - # FIX verify argument counts - # FIX what if one of the functions is generic + # TODO: Erase template variables if it is generic? + if ( + type_state.infer_polymorphic + and cactual.variables + and not self.skip_neg_op + # Technically, the correct inferred type for application of e.g. + # Callable[..., T] -> Callable[..., T] (with literal ellipsis), to a generic + # like U -> U, should be Callable[..., Any], but if U is a self-type, we can + # allow it to leak, to be later bound to self. A bunch of existing code + # depends on this old behaviour. + and not any(tv.id.raw_id == 0 for tv in cactual.variables) + ): + # If the actual callable is generic, infer constraints in the opposite + # direction, and indicate to the solver there are extra type variables + # to solve for (see more details in mypy/solve.py). + res.extend( + infer_constraints( + cactual, template, neg_op(self.direction), skip_neg_op=True + ) + ) + extra_tvars = True # We can't infer constraints from arguments if the template is Callable[..., T] # (with literal '...'). if not template.is_ellipsis_args: - if find_unpack_in_list(template.arg_types) is not None: - ( - unpack_constraints, - cactual_args_t, - template_args_t, - ) = find_and_build_constraints_for_unpack( - tuple(cactual.arg_types), tuple(template.arg_types), self.direction + unpack_present = find_unpack_in_list(template.arg_types) + if unpack_present is not None: + # We need to re-normalize args to the form they appear in tuples, + # for callables we always pack the suffix inside another tuple. + unpack = template.arg_types[unpack_present] + assert isinstance(unpack, UnpackType) + tuple_type = get_tuple_fallback_from_unpack(unpack) + template_types = repack_callable_args(template, tuple_type) + actual_types = repack_callable_args(cactual, tuple_type) + # Now we can use the same general helper as for tuple types. + unpack_constraints = build_constraints_for_simple_unpack( + template_types, actual_types, neg_op(self.direction) ) - template_args = list(template_args_t) - cactual_args = list(cactual_args_t) res.extend(unpack_constraints) - assert len(template_args) == len(cactual_args) else: - template_args = template.arg_types - cactual_args = cactual.arg_types - # The lengths should match, but don't crash (it will error elsewhere). - for t, a in zip(template_args, cactual_args): - # Negate direction due to function argument type contravariance. - res.extend(infer_constraints(t, a, neg_op(self.direction))) + # TODO: do we need some special-casing when unpack is present in actual + # callable but not in template callable? + res.extend( + infer_callable_arguments_constraints(template, cactual, self.direction) + ) else: - # sometimes, it appears we try to get constraints between two paramspec callables? - - # TODO: Direction - # TODO: check the prefixes match prefix = param_spec.prefix prefix_len = len(prefix.arg_types) cactual_ps = cactual.param_spec() - if not cactual_ps: - max_prefix_len = len([k for k in cactual.arg_kinds if k in (ARG_POS, ARG_OPT)]) - prefix_len = min(prefix_len, max_prefix_len) - res.append( - Constraint( - param_spec, - SUBTYPE_OF, - cactual.copy_modified( - arg_types=cactual.arg_types[prefix_len:], - arg_kinds=cactual.arg_kinds[prefix_len:], - arg_names=cactual.arg_names[prefix_len:], - ret_type=UninhabitedType(), - ), + if type_state.infer_polymorphic and cactual.variables and not self.skip_neg_op: + # Similar logic to the branch above. + res.extend( + infer_constraints( + cactual, template, neg_op(self.direction), skip_neg_op=True ) ) - else: - res.append(Constraint(param_spec, SUBTYPE_OF, cactual_ps)) + extra_tvars = True - # compare prefixes + # Compare prefixes as well cactual_prefix = cactual.copy_modified( arg_types=cactual.arg_types[:prefix_len], arg_kinds=cactual.arg_kinds[:prefix_len], arg_names=cactual.arg_names[:prefix_len], ) + res.extend( + infer_callable_arguments_constraints(prefix, cactual_prefix, self.direction) + ) - # TODO: see above "FIX" comments for param_spec is None case - # TODO: this assume positional arguments - for t, a in zip(prefix.arg_types, cactual_prefix.arg_types): - res.extend(infer_constraints(t, a, neg_op(self.direction))) - - template_ret_type, cactual_ret_type = template.ret_type, cactual.ret_type - if template.type_guard is not None: - template_ret_type = template.type_guard - if cactual.type_guard is not None: - cactual_ret_type = cactual.type_guard - - res.extend(infer_constraints(template_ret_type, cactual_ret_type, self.direction)) + param_spec_target: Type | None = None + if not cactual_ps: + max_prefix_len = len([k for k in cactual.arg_kinds if k in (ARG_POS, ARG_OPT)]) + prefix_len = min(prefix_len, max_prefix_len) + param_spec_target = Parameters( + arg_types=cactual.arg_types[prefix_len:], + arg_kinds=cactual.arg_kinds[prefix_len:], + arg_names=cactual.arg_names[prefix_len:], + variables=cactual.variables if not type_state.infer_polymorphic else [], + imprecise_arg_kinds=cactual.imprecise_arg_kinds, + ) + else: + if len(param_spec.prefix.arg_types) <= len(cactual_ps.prefix.arg_types): + param_spec_target = cactual_ps.copy_modified( + prefix=Parameters( + arg_types=cactual_ps.prefix.arg_types[prefix_len:], + arg_kinds=cactual_ps.prefix.arg_kinds[prefix_len:], + arg_names=cactual_ps.prefix.arg_names[prefix_len:], + imprecise_arg_kinds=cactual_ps.prefix.imprecise_arg_kinds, + ) + ) + if param_spec_target is not None: + res.append(Constraint(param_spec, self.direction, param_spec_target)) + if extra_tvars: + for c in res: + c.extra_tvars += cactual.variables return res elif isinstance(self.actual, AnyType): param_spec = template.param_spec() @@ -972,7 +1144,7 @@ def visit_callable_type(self, template: CallableType) -> list[Constraint]: Constraint( param_spec, SUBTYPE_OF, - callable_with_ellipsis(any_type, any_type, template.fallback), + Parameters([any_type, any_type], [ARG_STAR, ARG_STAR2], [None, None]), ) ] res.extend(infer_constraints(template.ret_type, any_type, self.direction)) @@ -1006,7 +1178,6 @@ def infer_against_overloaded( return infer_constraints(template, item, self.direction) def visit_tuple_type(self, template: TupleType) -> list[Constraint]: - actual = self.actual unpack_index = find_unpack_in_list(template.items) is_varlength_tuple = ( @@ -1017,24 +1188,68 @@ def visit_tuple_type(self, template: TupleType) -> list[Constraint]: res: list[Constraint] = [] if unpack_index is not None: if is_varlength_tuple: + # Variadic tuple can be only a supertype of a tuple type, but even if + # direction is opposite, inferring something may give better error messages. unpack_type = template.items[unpack_index] assert isinstance(unpack_type, UnpackType) - unpacked_type = unpack_type.type - assert isinstance(unpacked_type, TypeVarTupleType) - return [Constraint(type_var=unpacked_type, op=self.direction, target=actual)] + unpacked_type = get_proper_type(unpack_type.type) + if isinstance(unpacked_type, TypeVarTupleType): + res = [ + Constraint(type_var=unpacked_type, op=self.direction, target=actual) + ] + else: + assert ( + isinstance(unpacked_type, Instance) + and unpacked_type.type.fullname == "builtins.tuple" + ) + res = infer_constraints(unpacked_type, actual, self.direction) + assert isinstance(actual, Instance) # ensured by is_varlength_tuple == True + for i, ti in enumerate(template.items): + if i == unpack_index: + # This one we just handled above. + continue + # For Tuple[T, *Ts, S] <: tuple[X, ...] infer also T <: X and S <: X. + res.extend(infer_constraints(ti, actual.args[0], self.direction)) + return res else: assert isinstance(actual, TupleType) - ( - unpack_constraints, - actual_items, - template_items, - ) = find_and_build_constraints_for_unpack( - tuple(actual.items), tuple(template.items), self.direction + unpack_constraints = build_constraints_for_simple_unpack( + template.items, actual.items, self.direction ) + actual_items: tuple[Type, ...] = () + template_items: tuple[Type, ...] = () res.extend(unpack_constraints) elif isinstance(actual, TupleType): - actual_items = tuple(actual.items) - template_items = tuple(template.items) + a_unpack_index = find_unpack_in_list(actual.items) + if a_unpack_index is not None: + # The case where template tuple doesn't have an unpack, but actual tuple + # has an unpack. We can infer something if actual unpack is a variadic tuple. + # Tuple[T, S, U] <: tuple[X, *tuple[Y, ...], Z] => T <: X, S <: Y, U <: Z. + a_unpack = actual.items[a_unpack_index] + assert isinstance(a_unpack, UnpackType) + a_unpacked = get_proper_type(a_unpack.type) + if len(actual.items) + 1 <= len(template.items): + a_prefix_len = a_unpack_index + a_suffix_len = len(actual.items) - a_unpack_index - 1 + t_prefix, t_middle, t_suffix = split_with_prefix_and_suffix( + tuple(template.items), a_prefix_len, a_suffix_len + ) + actual_items = tuple(actual.items[:a_prefix_len]) + if a_suffix_len: + actual_items += tuple(actual.items[-a_suffix_len:]) + template_items = t_prefix + t_suffix + if isinstance(a_unpacked, Instance): + assert a_unpacked.type.fullname == "builtins.tuple" + for tm in t_middle: + res.extend( + infer_constraints(tm, a_unpacked.args[0], self.direction) + ) + else: + actual_items = () + template_items = () + else: + actual_items = tuple(actual.items) + template_items = tuple(template.items) else: return res @@ -1065,7 +1280,7 @@ def visit_typeddict_type(self, template: TypedDictType) -> list[Constraint]: res: list[Constraint] = [] # NOTE: Non-matching keys are ignored. Compatibility is checked # elsewhere so this shouldn't be unsafe. - for (item_name, template_item_type, actual_item_type) in template.zip(actual): + for item_name, template_item_type, actual_item_type in template.zip(actual): res.extend(infer_constraints(template_item_type, actual_item_type, self.direction)) return res elif isinstance(actual, AnyType): @@ -1085,8 +1300,13 @@ def visit_type_alias_type(self, template: TypeAliasType) -> list[Constraint]: def infer_against_any(self, types: Iterable[Type], any_type: AnyType) -> list[Constraint]: res: list[Constraint] = [] for t in types: - if isinstance(t, UnpackType) and isinstance(t.type, TypeVarTupleType): - res.append(Constraint(t.type, self.direction, any_type)) + if isinstance(t, UnpackType): + if isinstance(t.type, TypeVarTupleType): + res.append(Constraint(t.type, self.direction, any_type)) + else: + unpacked = get_proper_type(t.type) + assert isinstance(unpacked, Instance) + res.extend(infer_constraints(unpacked, any_type, self.direction)) else: # Note that we ignore variance and simply always use the # original direction. This is because for Any targets direction is @@ -1135,7 +1355,11 @@ def find_matching_overload_item(overloaded: Overloaded, template: CallableType) # Return type may be indeterminate in the template, so ignore it when performing a # subtype check. if mypy.subtypes.is_callable_compatible( - item, template, is_compat=mypy.subtypes.is_subtype, ignore_return=True + item, + template, + is_compat=mypy.subtypes.is_subtype, + is_proper_subtype=False, + ignore_return=True, ): return item # Fall back to the first item if we can't find a match. This is totally arbitrary -- @@ -1153,93 +1377,260 @@ def find_matching_overload_items( # Return type may be indeterminate in the template, so ignore it when performing a # subtype check. if mypy.subtypes.is_callable_compatible( - item, template, is_compat=mypy.subtypes.is_subtype, ignore_return=True + item, + template, + is_compat=mypy.subtypes.is_subtype, + is_proper_subtype=False, + ignore_return=True, ): res.append(item) if not res: # Falling back to all items if we can't find a match is pretty arbitrary, but # it maintains backward compatibility. - res = items[:] + res = items.copy() return res -def find_and_build_constraints_for_unpack( - mapped: tuple[Type, ...], template: tuple[Type, ...], direction: int -) -> tuple[list[Constraint], tuple[Type, ...], tuple[Type, ...]]: - mapped_prefix_len = find_unpack_in_list(mapped) - if mapped_prefix_len is not None: - mapped_suffix_len: int | None = len(mapped) - mapped_prefix_len - 1 +def get_tuple_fallback_from_unpack(unpack: UnpackType) -> TypeInfo: + """Get builtins.tuple type from available types to construct homogeneous tuples.""" + tp = get_proper_type(unpack.type) + if isinstance(tp, Instance) and tp.type.fullname == "builtins.tuple": + return tp.type + if isinstance(tp, TypeVarTupleType): + return tp.tuple_fallback.type + if isinstance(tp, TupleType): + for base in tp.partial_fallback.type.mro: + if base.fullname == "builtins.tuple": + return base + assert False, "Invalid unpack type" + + +def repack_callable_args(callable: CallableType, tuple_type: TypeInfo) -> list[Type]: + """Present callable with star unpack in a normalized form. + + Since positional arguments cannot follow star argument, they are packed in a suffix, + while prefix is represented as individual positional args. We want to put all in a single + list with unpack in the middle, and prefix/suffix on the sides (as they would appear + in e.g. a TupleType). + """ + if ARG_STAR not in callable.arg_kinds: + return callable.arg_types + star_index = callable.arg_kinds.index(ARG_STAR) + arg_types = callable.arg_types[:star_index] + star_type = callable.arg_types[star_index] + suffix_types = [] + if not isinstance(star_type, UnpackType): + # Re-normalize *args: X -> *args: *tuple[X, ...] + star_type = UnpackType(Instance(tuple_type, [star_type])) + else: + tp = get_proper_type(star_type.type) + if isinstance(tp, TupleType): + assert isinstance(tp.items[0], UnpackType) + star_type = tp.items[0] + suffix_types = tp.items[1:] + return arg_types + [star_type] + suffix_types + + +def build_constraints_for_simple_unpack( + template_args: list[Type], actual_args: list[Type], direction: int +) -> list[Constraint]: + """Infer constraints between two lists of types with variadic items. + + This function is only supposed to be called when a variadic item is present in templates. + If there is no variadic item the actuals, we simply use split_with_prefix_and_suffix() + and infer prefix <: prefix, suffix <: suffix, variadic <: middle. If there is a variadic + item in the actuals we need to be more careful, only common prefix/suffix can generate + constraints, also we can only infer constraints for variadic template item, if template + prefix/suffix are shorter that actual ones, otherwise there may be partial overlap + between variadic items, for example if template prefix is longer: + + templates: T1, T2, Ts, Ts, Ts, ... + actuals: A1, As, As, As, ... + + Note: this function can only be called for builtin variadic constructors: Tuple and Callable. + For instances, you should first find correct type argument mapping. + """ + template_unpack = find_unpack_in_list(template_args) + assert template_unpack is not None + template_prefix = template_unpack + template_suffix = len(template_args) - template_prefix - 1 + + t_unpack = None + res = [] + + actual_unpack = find_unpack_in_list(actual_args) + if actual_unpack is None: + t_unpack = template_args[template_unpack] + if template_prefix + template_suffix > len(actual_args): + # These can't be subtypes of each-other, return fast. + assert isinstance(t_unpack, UnpackType) + if isinstance(t_unpack.type, TypeVarTupleType): + # Set TypeVarTuple to empty to improve error messages. + return [ + Constraint( + t_unpack.type, direction, TupleType([], t_unpack.type.tuple_fallback) + ) + ] + else: + return [] + common_prefix = template_prefix + common_suffix = template_suffix else: - mapped_suffix_len = None - - template_prefix_len = find_unpack_in_list(template) - assert template_prefix_len is not None - template_suffix_len = len(template) - template_prefix_len - 1 - - return build_constraints_for_unpack( - mapped, - mapped_prefix_len, - mapped_suffix_len, - template, - template_prefix_len, - template_suffix_len, - direction, + actual_prefix = actual_unpack + actual_suffix = len(actual_args) - actual_prefix - 1 + common_prefix = min(template_prefix, actual_prefix) + common_suffix = min(template_suffix, actual_suffix) + if actual_prefix >= template_prefix and actual_suffix >= template_suffix: + # This is the only case where we can guarantee there will be no partial overlap + # (note however partial overlap is OK for variadic tuples, it is handled below). + t_unpack = template_args[template_unpack] + + # Handle constraints from prefixes/suffixes first. + start, middle, end = split_with_prefix_and_suffix( + tuple(actual_args), common_prefix, common_suffix ) + for t, a in zip(template_args[:common_prefix], start): + res.extend(infer_constraints(t, a, direction)) + if common_suffix: + for t, a in zip(template_args[-common_suffix:], end): + res.extend(infer_constraints(t, a, direction)) + + if t_unpack is not None: + # Add constraint(s) for variadic item when possible. + assert isinstance(t_unpack, UnpackType) + tp = get_proper_type(t_unpack.type) + if isinstance(tp, Instance) and tp.type.fullname == "builtins.tuple": + # Homogeneous case *tuple[T, ...] <: [X, Y, Z, ...]. + for a in middle: + # TODO: should we use union instead of join here? + if not isinstance(a, UnpackType): + res.extend(infer_constraints(tp.args[0], a, direction)) + else: + a_tp = get_proper_type(a.type) + # This is the case *tuple[T, ...] <: *tuple[A, ...]. + if isinstance(a_tp, Instance) and a_tp.type.fullname == "builtins.tuple": + res.extend(infer_constraints(tp.args[0], a_tp.args[0], direction)) + elif isinstance(tp, TypeVarTupleType): + res.append(Constraint(tp, direction, TupleType(list(middle), tp.tuple_fallback))) + elif actual_unpack is not None: + # A special case for a variadic tuple unpack, we simply infer T <: X from + # Tuple[..., *tuple[T, ...], ...] <: Tuple[..., *tuple[X, ...], ...]. + actual_unpack_type = actual_args[actual_unpack] + assert isinstance(actual_unpack_type, UnpackType) + a_unpacked = get_proper_type(actual_unpack_type.type) + if isinstance(a_unpacked, Instance) and a_unpacked.type.fullname == "builtins.tuple": + t_unpack = template_args[template_unpack] + assert isinstance(t_unpack, UnpackType) + tp = get_proper_type(t_unpack.type) + if isinstance(tp, Instance) and tp.type.fullname == "builtins.tuple": + res.extend(infer_constraints(tp.args[0], a_unpacked.args[0], direction)) + return res -def build_constraints_for_unpack( - mapped: tuple[Type, ...], - mapped_prefix_len: int | None, - mapped_suffix_len: int | None, - template: tuple[Type, ...], - template_prefix_len: int, - template_suffix_len: int, +def infer_directed_arg_constraints(left: Type, right: Type, direction: int) -> list[Constraint]: + """Infer constraints between two arguments using direction between original callables.""" + if isinstance(left, (ParamSpecType, UnpackType)) or isinstance( + right, (ParamSpecType, UnpackType) + ): + # This avoids bogus constraints like T <: P.args + # TODO: can we infer something useful for *T vs P? + return [] + if direction == SUBTYPE_OF: + # We invert direction to account for argument contravariance. + return infer_constraints(left, right, neg_op(direction)) + else: + return infer_constraints(right, left, neg_op(direction)) + + +def infer_callable_arguments_constraints( + template: NormalizedCallableType | Parameters, + actual: NormalizedCallableType | Parameters, direction: int, -) -> tuple[list[Constraint], tuple[Type, ...], tuple[Type, ...]]: - if mapped_prefix_len is None: - mapped_prefix_len = template_prefix_len - if mapped_suffix_len is None: - mapped_suffix_len = template_suffix_len - - split_result = split_with_mapped_and_template( - mapped, - mapped_prefix_len, - mapped_suffix_len, - template, - template_prefix_len, - template_suffix_len, - ) - assert split_result is not None - ( - mapped_prefix, - mapped_middle, - mapped_suffix, - template_prefix, - template_middle, - template_suffix, - ) = split_result - - template_unpack = extract_unpack(template_middle) - res = [] +) -> list[Constraint]: + """Infer constraints between argument types of two callables. - if template_unpack is not None: - if isinstance(template_unpack, TypeVarTupleType): - res.append( - Constraint( - template_unpack, - direction, - TupleType(list(mapped_middle), template_unpack.tuple_fallback), + This function essentially extracts four steps from are_parameters_compatible() in + subtypes.py that involve subtype checks between argument types. We keep the argument + matching logic, but ignore various strictness flags present there, and checks that + do not involve subtyping. Then in place of every subtype check we put an infer_constraints() + call for the same types. + """ + res = [] + if direction == SUBTYPE_OF: + left, right = template, actual + else: + left, right = actual, template + left_star = left.var_arg() + left_star2 = left.kw_arg() + right_star = right.var_arg() + right_star2 = right.kw_arg() + + # Numbering of steps below matches the one in are_parameters_compatible() for convenience. + # Phase 1a: compare star vs star arguments. + if left_star is not None and right_star is not None: + res.extend(infer_directed_arg_constraints(left_star.typ, right_star.typ, direction)) + if left_star2 is not None and right_star2 is not None: + res.extend(infer_directed_arg_constraints(left_star2.typ, right_star2.typ, direction)) + + # Phase 1b: compare left args with corresponding non-star right arguments. + for right_arg in right.formal_arguments(): + left_arg = mypy.typeops.callable_corresponding_argument(left, right_arg) + if left_arg is None: + continue + res.extend(infer_directed_arg_constraints(left_arg.typ, right_arg.typ, direction)) + + # Phase 1c: compare left args with right *args. + if right_star is not None: + right_by_position = right.try_synthesizing_arg_from_vararg(None) + assert right_by_position is not None + i = right_star.pos + assert i is not None + while i < len(left.arg_kinds) and left.arg_kinds[i].is_positional(): + left_by_position = left.argument_by_position(i) + assert left_by_position is not None + res.extend( + infer_directed_arg_constraints( + left_by_position.typ, right_by_position.typ, direction ) ) - elif ( - isinstance(template_unpack, Instance) - and template_unpack.type.fullname == "builtins.tuple" + i += 1 + + # Phase 1d: compare left args with right **kwargs. + if right_star2 is not None: + right_names = {name for name in right.arg_names if name is not None} + left_only_names = set() + for name, kind in zip(left.arg_names, left.arg_kinds): + if name is None or kind.is_star() or name in right_names: + continue + left_only_names.add(name) + + right_by_name = right.try_synthesizing_arg_from_kwarg(None) + assert right_by_name is not None + for name in left_only_names: + left_by_name = left.argument_by_name(name) + assert left_by_name is not None + res.extend( + infer_directed_arg_constraints(left_by_name.typ, right_by_name.typ, direction) + ) + return res + + +def filter_imprecise_kinds(cs: list[Constraint]) -> list[Constraint]: + """For each ParamSpec remove all imprecise constraints, if at least one precise available.""" + have_precise = set() + for c in cs: + if not isinstance(c.origin_type_var, ParamSpecType): + continue + if ( + isinstance(c.target, ParamSpecType) + or isinstance(c.target, Parameters) + and not c.target.imprecise_arg_kinds ): - for item in mapped_middle: - res.extend(infer_constraints(template_unpack.args[0], item, direction)) - - elif isinstance(template_unpack, TupleType): - if len(template_unpack.items) == len(mapped_middle): - for template_arg, item in zip(template_unpack.items, mapped_middle): - res.extend(infer_constraints(template_arg, item, direction)) - return (res, mapped_prefix + mapped_suffix, template_prefix + template_suffix) + have_precise.add(c.type_var) + new_cs = [] + for c in cs: + if not isinstance(c.origin_type_var, ParamSpecType) or c.type_var not in have_precise: + new_cs.append(c) + if not isinstance(c.target, Parameters) or not c.target.imprecise_arg_kinds: + new_cs.append(c) + return new_cs diff --git a/mypy/copytype.py b/mypy/copytype.py index 6024e527705b..4ca381c4a8c4 100644 --- a/mypy/copytype.py +++ b/mypy/copytype.py @@ -28,7 +28,7 @@ ) # type_visitor needs to be imported after types -from mypy.type_visitor import TypeVisitor # isort: skip +from mypy.type_visitor import TypeVisitor # ruff: isort: skip def copy_type(t: ProperType) -> ProperType: @@ -69,18 +69,12 @@ def visit_instance(self, t: Instance) -> ProperType: return self.copy_common(t, dup) def visit_type_var(self, t: TypeVarType) -> ProperType: - dup = TypeVarType( - t.name, - t.fullname, - t.id, - values=t.values, - upper_bound=t.upper_bound, - variance=t.variance, - ) - return self.copy_common(t, dup) + return self.copy_common(t, t.copy_modified()) def visit_param_spec(self, t: ParamSpecType) -> ProperType: - dup = ParamSpecType(t.name, t.fullname, t.id, t.flavor, t.upper_bound, prefix=t.prefix) + dup = ParamSpecType( + t.name, t.fullname, t.id, t.flavor, t.upper_bound, t.default, prefix=t.prefix + ) return self.copy_common(t, dup) def visit_parameters(self, t: Parameters) -> ProperType: @@ -94,7 +88,9 @@ def visit_parameters(self, t: Parameters) -> ProperType: return self.copy_common(t, dup) def visit_type_var_tuple(self, t: TypeVarTupleType) -> ProperType: - dup = TypeVarTupleType(t.name, t.fullname, t.id, t.upper_bound, t.tuple_fallback) + dup = TypeVarTupleType( + t.name, t.fullname, t.id, t.upper_bound, t.tuple_fallback, t.default + ) return self.copy_common(t, dup) def visit_unpack_type(self, t: UnpackType) -> ProperType: diff --git a/mypy/defaults.py b/mypy/defaults.py index 02562b5f0963..2bbae23d7e2d 100644 --- a/mypy/defaults.py +++ b/mypy/defaults.py @@ -1,18 +1,16 @@ from __future__ import annotations import os -from typing_extensions import Final - -PYTHON2_VERSION: Final = (2, 7) +from typing import Final # Earliest fully supported Python 3.x version. Used as the default Python # version in tests. Mypy wheels should be built starting with this version, # and CI tests should be run on this version (and later versions). -PYTHON3_VERSION: Final = (3, 7) +PYTHON3_VERSION: Final = (3, 8) # Earliest Python 3.x version supported via --python-version 3.x. To run # mypy, at least version PYTHON3_VERSION is needed. -PYTHON3_VERSION_MIN: Final = (3, 4) +PYTHON3_VERSION_MIN: Final = (3, 8) # Keep in sync with typeshed's python support CACHE_DIR: Final = ".mypy_cache" CONFIG_FILE: Final = ["mypy.ini", ".mypy.ini"] @@ -44,5 +42,5 @@ ] # Threshold after which we sometimes filter out most errors to avoid very -# verbose output -MANY_ERRORS_THRESHOLD: Final = 200 +# verbose output. The default is to show all errors. +MANY_ERRORS_THRESHOLD: Final = -1 diff --git a/mypy/dmypy/client.py b/mypy/dmypy/client.py index efa1b5f01288..9f0751e93609 100644 --- a/mypy/dmypy/client.py +++ b/mypy/dmypy/client.py @@ -17,7 +17,7 @@ from typing import Any, Callable, Mapping, NoReturn from mypy.dmypy_os import alive, kill -from mypy.dmypy_util import DEFAULT_STATUS_FILE, receive +from mypy.dmypy_util import DEFAULT_STATUS_FILE, receive, send from mypy.ipc import IPCClient, IPCException from mypy.util import check_python_version, get_terminal_width, should_force_color from mypy.version import __version__ @@ -244,6 +244,7 @@ def __init__(self, prog: str) -> None: p.add_argument( "--timeout", metavar="TIMEOUT", type=int, help="Server shutdown timeout (in seconds)" ) +p.add_argument("--log-file", metavar="FILE", type=str, help="Direct daemon stdout/stderr to FILE") p.add_argument( "flags", metavar="FLAG", nargs="*", type=str, help="Regular mypy flags (precede with --)" ) @@ -561,6 +562,7 @@ def check_output( sys.stdout.write(out) sys.stdout.flush() sys.stderr.write(err) + sys.stderr.flush() if verbose: show_stats(response) if junit_xml: @@ -571,7 +573,7 @@ def check_output( write_junit_xml( response["roundtrip_time"], bool(err), - messages, + {None: messages} if messages else {}, junit_xml, response["python_version"], response["platform"], @@ -587,13 +589,14 @@ def check_output( def show_stats(response: Mapping[str, object]) -> None: for key, value in sorted(response.items()): - if key not in ("out", "err"): - print("%-24s: %10s" % (key, "%.3f" % value if isinstance(value, float) else value)) - else: + if key in ("out", "err", "stdout", "stderr"): + # Special case text output to display just 40 characters of text value = repr(value)[1:-1] if len(value) > 50: - value = value[:40] + " ..." + value = f"{value[:40]} ... {len(value)-40} more characters" print("%-24s: %s" % (key, value)) + continue + print("%-24s: %10s" % (key, "%.3f" % value if isinstance(value, float) else value)) @action(hang_parser) @@ -608,21 +611,22 @@ def do_daemon(args: argparse.Namespace) -> None: # Lazy import so this import doesn't slow down other commands. from mypy.dmypy_server import Server, process_start_options + if args.log_file: + sys.stdout = sys.stderr = open(args.log_file, "a", buffering=1) + fd = sys.stdout.fileno() + os.dup2(fd, 2) + os.dup2(fd, 1) + if args.options_data: from mypy.options import Options - options_dict, timeout, log_file = pickle.loads(base64.b64decode(args.options_data)) + options_dict = pickle.loads(base64.b64decode(args.options_data)) options_obj = Options() options = options_obj.apply_changes(options_dict) - if log_file: - sys.stdout = sys.stderr = open(log_file, "a", buffering=1) - fd = sys.stdout.fileno() - os.dup2(fd, 2) - os.dup2(fd, 1) else: options = process_start_options(args.flags, allow_sources=False) - timeout = args.timeout - Server(options, args.status_file, timeout=timeout).serve() + + Server(options, args.status_file, timeout=args.timeout).serve() @action(help_parser) @@ -655,21 +659,29 @@ def request( # so that it can format the type checking output accordingly. args["is_tty"] = sys.stdout.isatty() or should_force_color() args["terminal_width"] = get_terminal_width() - bdata = json.dumps(args).encode("utf8") _, name = get_status(status_file) try: with IPCClient(name, timeout) as client: - client.write(bdata) - response = receive(client) + send(client, args) + + final = False + while not final: + response = receive(client) + final = bool(response.pop("final", False)) + # Display debugging output written to stdout/stderr in the server process for convenience. + # This should not be confused with "out" and "err" fields in the response. + # Those fields hold the output of the "check" command, and are handled in check_output(). + stdout = response.pop("stdout", None) + if stdout: + sys.stdout.write(stdout) + stderr = response.pop("stderr", None) + if stderr: + sys.stderr.write(stderr) except (OSError, IPCException) as err: return {"error": str(err)} # TODO: Other errors, e.g. ValueError, UnicodeError - else: - # Display debugging output written to stdout in the server process for convenience. - stdout = response.get("stdout") - if stdout: - sys.stdout.write(stdout) - return response + + return response def get_status(status_file: str) -> tuple[int, str]: diff --git a/mypy/dmypy_server.py b/mypy/dmypy_server.py index 7227cd559946..f8a0f91f87d9 100644 --- a/mypy/dmypy_server.py +++ b/mypy/dmypy_server.py @@ -17,13 +17,13 @@ import time import traceback from contextlib import redirect_stderr, redirect_stdout -from typing import AbstractSet, Any, Callable, List, Sequence, Tuple -from typing_extensions import Final, TypeAlias as _TypeAlias +from typing import AbstractSet, Any, Callable, Final, List, Sequence, Tuple +from typing_extensions import TypeAlias as _TypeAlias import mypy.build import mypy.errors import mypy.main -from mypy.dmypy_util import receive +from mypy.dmypy_util import WriteToConn, receive, send from mypy.find_sources import InvalidSourceList, create_source_list from mypy.fscache import FileSystemCache from mypy.fswatcher import FileData, FileSystemWatcher @@ -56,8 +56,12 @@ def daemonize( It also pickles the options to be unpickled by mypy. """ command = [sys.executable, "-m", "mypy.dmypy", "--status-file", status_file, "daemon"] - pickled_options = pickle.dumps((options.snapshot(), timeout, log_file)) + pickled_options = pickle.dumps(options.snapshot()) command.append(f'--options-data="{base64.b64encode(pickled_options).decode()}"') + if timeout: + command.append(f"--timeout={timeout}") + if log_file: + command.append(f"--log-file={log_file}") info = STARTUPINFO() info.dwFlags = 0x1 # STARTF_USESHOWWINDOW aka use wShowWindow's value info.wShowWindow = 0 # SW_HIDE aka make the window invisible @@ -163,7 +167,6 @@ def ignore_suppressed_imports(module: str) -> bool: class Server: - # NOTE: the instance is constructed in the parent process but # serve() is called in the grandchild (by daemonize()). @@ -205,8 +208,12 @@ def _response_metadata(self) -> dict[str, str]: def serve(self) -> None: """Serve requests, synchronously (no thread or fork).""" + command = None server = IPCServer(CONNECTION_NAME, self.timeout) + orig_stdout = sys.stdout + orig_stderr = sys.stderr + try: with open(self.status_file, "w") as f: json.dump({"pid": os.getpid(), "connection_name": server.connection_name}, f) @@ -214,8 +221,8 @@ def serve(self) -> None: while True: with server: data = receive(server) - debug_stdout = io.StringIO() - sys.stdout = debug_stdout + sys.stdout = WriteToConn(server, "stdout", sys.stdout.isatty()) + sys.stderr = WriteToConn(server, "stderr", sys.stderr.isatty()) resp: dict[str, Any] = {} if "command" not in data: resp = {"error": "No command found in request"} @@ -232,19 +239,23 @@ def serve(self) -> None: tb = traceback.format_exception(*sys.exc_info()) resp = {"error": "Daemon crashed!\n" + "".join(tb)} resp.update(self._response_metadata()) - resp["stdout"] = debug_stdout.getvalue() - server.write(json.dumps(resp).encode("utf8")) + resp["final"] = True + send(server, resp) raise - resp["stdout"] = debug_stdout.getvalue() + resp["final"] = True try: resp.update(self._response_metadata()) - server.write(json.dumps(resp).encode("utf8")) + send(server, resp) except OSError: pass # Maybe the client hung up if command == "stop": reset_global_state() sys.exit(0) finally: + # Revert stdout/stderr so we can see any errors. + sys.stdout = orig_stdout + sys.stderr = orig_stderr + # If the final command is something other than a clean # stop, remove the status file. (We can't just # simplify the logic and always remove the file, since @@ -323,7 +334,7 @@ def cmd_run( header=argparse.SUPPRESS, ) # Signal that we need to restart if the options have changed - if self.options_snapshot != options.snapshot(): + if not options.compare_stable(self.options_snapshot): return {"restart": "configuration changed"} if __version__ != version: return {"restart": "mypy version changed"} @@ -372,6 +383,9 @@ def cmd_recheck( removals = set(remove) sources = [s for s in sources if s.path and s.path not in removals] if update: + # Sort list of file updates by extension, so *.pyi files are first. + update.sort(key=lambda f: os.path.splitext(f)[1], reverse=True) + known = {s.path for s in sources if s.path} added = [p for p in update if p not in known] try: @@ -382,15 +396,21 @@ def cmd_recheck( t1 = time.time() manager = self.fine_grained_manager.manager manager.log(f"fine-grained increment: cmd_recheck: {t1 - t0:.3f}s") - self.options.export_types = export_types + old_export_types = self.options.export_types + self.options.export_types = self.options.export_types or export_types if not self.following_imports(): - messages = self.fine_grained_increment(sources, remove, update) + messages = self.fine_grained_increment( + sources, remove, update, explicit_export_types=export_types + ) else: assert remove is None and update is None - messages = self.fine_grained_increment_follow_imports(sources) + messages = self.fine_grained_increment_follow_imports( + sources, explicit_export_types=export_types + ) res = self.increment_output(messages, sources, is_tty, terminal_width) self.flush_caches() self.update_stats(res) + self.options.export_types = old_export_types return res def check( @@ -401,17 +421,21 @@ def check( If is_tty is True format the output nicely with colors and summary line (unless disabled in self.options). Also pass the terminal_width to formatter. """ - self.options.export_types = export_types + old_export_types = self.options.export_types + self.options.export_types = self.options.export_types or export_types if not self.fine_grained_manager: res = self.initialize_fine_grained(sources, is_tty, terminal_width) else: if not self.following_imports(): - messages = self.fine_grained_increment(sources) + messages = self.fine_grained_increment(sources, explicit_export_types=export_types) else: - messages = self.fine_grained_increment_follow_imports(sources) + messages = self.fine_grained_increment_follow_imports( + sources, explicit_export_types=export_types + ) res = self.increment_output(messages, sources, is_tty, terminal_width) self.flush_caches() self.update_stats(res) + self.options.export_types = old_export_types return res def flush_caches(self) -> None: @@ -450,6 +474,7 @@ def initialize_fine_grained( messages = result.errors self.fine_grained_manager = FineGrainedBuildManager(result) + original_sources_len = len(sources) if self.following_imports(): sources = find_all_sources_in_build(self.fine_grained_manager.graph, sources) self.update_sources(sources) @@ -514,7 +539,8 @@ def initialize_fine_grained( __, n_notes, __ = count_stats(messages) status = 1 if messages and n_notes < len(messages) else 0 - messages = self.pretty_messages(messages, len(sources), is_tty, terminal_width) + # We use explicit sources length to match the logic in non-incremental mode. + messages = self.pretty_messages(messages, original_sources_len, is_tty, terminal_width) return {"out": "".join(s + "\n" for s in messages), "err": "", "status": status} def fine_grained_increment( @@ -522,6 +548,7 @@ def fine_grained_increment( sources: list[BuildSource], remove: list[str] | None = None, update: list[str] | None = None, + explicit_export_types: bool = False, ) -> list[str]: """Perform a fine-grained type checking increment. @@ -532,6 +559,8 @@ def fine_grained_increment( sources: sources passed on the command line remove: paths of files that have been removed update: paths of files that have been changed or created + explicit_export_types: --export-type was passed in a check command + (as opposite to being set in dmypy start) """ assert self.fine_grained_manager is not None manager = self.fine_grained_manager.manager @@ -546,6 +575,10 @@ def fine_grained_increment( # Use the remove/update lists to update fswatcher. # This avoids calling stat() for unchanged files. changed, removed = self.update_changed(sources, remove or [], update or []) + if explicit_export_types: + # If --export-types is given, we need to force full re-checking of all + # explicitly passed files, since we need to visit each expression. + add_all_sources_to_changed(sources, changed) changed += self.find_added_suppressed( self.fine_grained_manager.graph, set(), manager.search_paths ) @@ -564,7 +597,9 @@ def fine_grained_increment( self.previous_sources = sources return messages - def fine_grained_increment_follow_imports(self, sources: list[BuildSource]) -> list[str]: + def fine_grained_increment_follow_imports( + self, sources: list[BuildSource], explicit_export_types: bool = False + ) -> list[str]: """Like fine_grained_increment, but follow imports.""" t0 = time.time() @@ -590,13 +625,16 @@ def fine_grained_increment_follow_imports(self, sources: list[BuildSource]) -> l changed, new_files = self.find_reachable_changed_modules( sources, graph, seen, changed_paths ) + if explicit_export_types: + # Same as in fine_grained_increment(). + add_all_sources_to_changed(sources, changed) sources.extend(new_files) # Process changes directly reachable from roots. messages = fine_grained_manager.update(changed, [], followed=True) # Follow deps from changed modules (still within graph). - worklist = changed[:] + worklist = changed.copy() while worklist: module = worklist.pop() if module[0] not in graph: @@ -703,7 +741,7 @@ def find_reachable_changed_modules( """ changed = [] new_files = [] - worklist = roots[:] + worklist = roots.copy() seen.update(source.module for source in worklist) while worklist: nxt = worklist.pop() @@ -824,7 +862,6 @@ def update_sources(self, sources: list[BuildSource]) -> None: def update_changed( self, sources: list[BuildSource], remove: list[str], update: list[str] ) -> ChangesAndRemovals: - changed_paths = self.fswatcher.update_changed(remove, update) return self._find_changed(sources, changed_paths) @@ -851,6 +888,21 @@ def _find_changed( assert path removed.append((source.module, path)) + # Always add modules that were (re-)added, since they may be detected as not changed by + # fswatcher (if they were actually not changed), but they may still need to be checked + # in case they had errors before they were deleted from sources on previous runs. + previous_modules = {source.module for source in self.previous_sources} + changed_set = set(changed) + changed.extend( + [ + (source.module, source.path) + for source in sources + if source.path + and source.module not in previous_modules + and (source.module, source.path) not in changed_set + ] + ) + # Find anything that has had its module path change because of added or removed __init__s last = {s.path: s.module for s in self.previous_sources} for s in sources: @@ -875,8 +927,6 @@ def cmd_inspect( force_reload: bool = False, ) -> dict[str, object]: """Locate and inspect expression(s).""" - if sys.version_info < (3, 8): - return {"error": 'Python 3.8 required for "inspect" command'} if not self.fine_grained_manager: return { "error": 'Command "inspect" is only valid after a "check" command' @@ -986,13 +1036,29 @@ def find_all_sources_in_build( return result +def add_all_sources_to_changed(sources: list[BuildSource], changed: list[tuple[str, str]]) -> None: + """Add all (explicit) sources to the list changed files in place. + + Use this when re-processing of unchanged files is needed (e.g. for + the purpose of exporting types for inspections). + """ + changed_set = set(changed) + changed.extend( + [ + (bs.module, bs.path) + for bs in sources + if bs.path and (bs.module, bs.path) not in changed_set + ] + ) + + def fix_module_deps(graph: mypy.build.Graph) -> None: """After an incremental update, update module dependencies to reflect the new state. This can make some suppressed dependencies non-suppressed, and vice versa (if modules have been added to or removed from the build). """ - for module, state in graph.items(): + for state in graph.values(): new_suppressed = [] new_dependencies = [] for dep in state.dependencies + state.suppressed: diff --git a/mypy/dmypy_util.py b/mypy/dmypy_util.py index a1b419617f73..0baff863b3c3 100644 --- a/mypy/dmypy_util.py +++ b/mypy/dmypy_util.py @@ -5,9 +5,10 @@ from __future__ import annotations +import io import json -from typing import Any -from typing_extensions import Final +from types import TracebackType +from typing import Any, Final, Iterable, Iterator, TextIO from mypy.ipc import IPCBase @@ -15,7 +16,7 @@ def receive(connection: IPCBase) -> Any: - """Receive JSON data from a connection until EOF. + """Receive single JSON data frame from a connection. Raise OSError if the data received is not valid JSON or if it is not a dict. @@ -24,9 +25,93 @@ def receive(connection: IPCBase) -> Any: if not bdata: raise OSError("No data received") try: - data = json.loads(bdata.decode("utf8")) + data = json.loads(bdata) except Exception as e: raise OSError("Data received is not valid JSON") from e if not isinstance(data, dict): raise OSError(f"Data received is not a dict ({type(data)})") return data + + +def send(connection: IPCBase, data: Any) -> None: + """Send data to a connection encoded and framed. + + The data must be JSON-serializable. We assume that a single send call is a + single frame to be sent on the connect. + """ + connection.write(json.dumps(data)) + + +class WriteToConn(TextIO): + """Helper class to write to a connection instead of standard output.""" + + def __init__(self, server: IPCBase, output_key: str, isatty: bool) -> None: + self.server = server + self.output_key = output_key + self._isatty = isatty + + def __enter__(self) -> TextIO: + return self + + def __exit__( + self, + t: type[BaseException] | None, + value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + pass + + def __iter__(self) -> Iterator[str]: + raise io.UnsupportedOperation + + def __next__(self) -> str: + raise io.UnsupportedOperation + + def close(self) -> None: + pass + + def fileno(self) -> int: + raise OSError + + def flush(self) -> None: + pass + + def isatty(self) -> bool: + return self._isatty + + def read(self, n: int = 0) -> str: + raise io.UnsupportedOperation + + def readable(self) -> bool: + return False + + def readline(self, limit: int = 0) -> str: + raise io.UnsupportedOperation + + def readlines(self, hint: int = 0) -> list[str]: + raise io.UnsupportedOperation + + def seek(self, offset: int, whence: int = 0) -> int: + raise io.UnsupportedOperation + + def seekable(self) -> bool: + return False + + def tell(self) -> int: + raise io.UnsupportedOperation + + def truncate(self, size: int | None = 0) -> int: + raise io.UnsupportedOperation + + def write(self, output: str) -> int: + resp: dict[str, Any] = {} + resp[self.output_key] = output + send(self.server, resp) + return len(output) + + def writable(self) -> bool: + return True + + def writelines(self, lines: Iterable[str]) -> None: + for s in lines: + self.write(s) diff --git a/mypy/erasetype.py b/mypy/erasetype.py index 6533d0c4e0f9..b41eefcd4821 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -71,13 +71,24 @@ def visit_erased_type(self, t: ErasedType) -> ProperType: def visit_partial_type(self, t: PartialType) -> ProperType: # Should not get here. - raise RuntimeError() + raise RuntimeError("Cannot erase partial types") def visit_deleted_type(self, t: DeletedType) -> ProperType: return t def visit_instance(self, t: Instance) -> ProperType: - return Instance(t.type, [AnyType(TypeOfAny.special_form)] * len(t.args), t.line) + args: list[Type] = [] + for tv in t.type.defn.type_vars: + # Valid erasure for *Ts is *tuple[Any, ...], not just Any. + if isinstance(tv, TypeVarTupleType): + args.append( + UnpackType( + tv.tuple_fallback.copy_modified(args=[AnyType(TypeOfAny.special_form)]) + ) + ) + else: + args.append(AnyType(TypeOfAny.special_form)) + return Instance(t.type, args, t.line) def visit_type_var(self, t: TypeVarType) -> ProperType: return AnyType(TypeOfAny.special_form) @@ -89,7 +100,9 @@ def visit_parameters(self, t: Parameters) -> ProperType: raise RuntimeError("Parameters should have been bound to a class") def visit_type_var_tuple(self, t: TypeVarTupleType) -> ProperType: - return AnyType(TypeOfAny.special_form) + # Likely, we can never get here because of aggressive erasure of types that + # can contain this, but better still return a valid replacement. + return t.tuple_fallback.copy_modified(args=[AnyType(TypeOfAny.special_form)]) def visit_unpack_type(self, t: UnpackType) -> ProperType: return AnyType(TypeOfAny.special_form) @@ -165,9 +178,41 @@ def visit_type_var(self, t: TypeVarType) -> Type: return self.replacement return t + # TODO: below two methods duplicate some logic with expand_type(). + # In fact, we may want to refactor this whole visitor to use expand_type(). + def visit_instance(self, t: Instance) -> Type: + result = super().visit_instance(t) + assert isinstance(result, ProperType) and isinstance(result, Instance) + if t.type.fullname == "builtins.tuple": + # Normalize Tuple[*Tuple[X, ...], ...] -> Tuple[X, ...] + arg = result.args[0] + if isinstance(arg, UnpackType): + unpacked = get_proper_type(arg.type) + if isinstance(unpacked, Instance): + assert unpacked.type.fullname == "builtins.tuple" + return unpacked + return result + + def visit_tuple_type(self, t: TupleType) -> Type: + result = super().visit_tuple_type(t) + assert isinstance(result, ProperType) and isinstance(result, TupleType) + if len(result.items) == 1: + # Normalize Tuple[*Tuple[X, ...]] -> Tuple[X, ...] + item = result.items[0] + if isinstance(item, UnpackType): + unpacked = get_proper_type(item.type) + if isinstance(unpacked, Instance): + assert unpacked.type.fullname == "builtins.tuple" + if result.partial_fallback.type.fullname != "builtins.tuple": + # If it is a subtype (like named tuple) we need to preserve it, + # this essentially mimics the logic in tuple_fallback(). + return result.partial_fallback.accept(self) + return unpacked + return result + def visit_type_var_tuple(self, t: TypeVarTupleType) -> Type: if self.erase_id(t.id): - return self.replacement + return t.tuple_fallback.copy_modified(args=[self.replacement]) return t def visit_param_spec(self, t: ParamSpecType) -> Type: diff --git a/mypy/error_formatter.py b/mypy/error_formatter.py new file mode 100644 index 000000000000..ffc6b6747596 --- /dev/null +++ b/mypy/error_formatter.py @@ -0,0 +1,37 @@ +"""Defines the different custom formats in which mypy can output.""" + +import json +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from mypy.errors import MypyError + + +class ErrorFormatter(ABC): + """Base class to define how errors are formatted before being printed.""" + + @abstractmethod + def report_error(self, error: "MypyError") -> str: + raise NotImplementedError + + +class JSONFormatter(ErrorFormatter): + """Formatter for basic JSON output format.""" + + def report_error(self, error: "MypyError") -> str: + """Prints out the errors as simple, static JSON lines.""" + return json.dumps( + { + "file": error.file_path, + "line": error.line, + "column": error.column, + "message": error.message, + "hint": None if len(error.hints) == 0 else "\n".join(error.hints), + "code": None if error.errorcode is None else error.errorcode.code, + "severity": error.severity, + } + ) + + +OUTPUT_CHOICES = {"json": JSONFormatter()} diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index 3d8b1096ed4f..7de796a70c8d 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -6,12 +6,15 @@ from __future__ import annotations from collections import defaultdict -from typing_extensions import Final +from typing import Final + +from mypy_extensions import mypyc_attr error_codes: dict[str, ErrorCode] = {} sub_code_map: dict[str, set[str]] = defaultdict(set) +@mypyc_attr(allow_interpreted_subclasses=True) class ErrorCode: def __init__( self, @@ -34,6 +37,14 @@ def __init__( def __str__(self) -> str: return f"" + def __eq__(self, other: object) -> bool: + if not isinstance(other, ErrorCode): + return False + return self.code == other.code + + def __hash__(self) -> int: + return hash((self.code,)) + ATTR_DEFINED: Final = ErrorCode("attr-defined", "Check that attribute exists", "General") NAME_DEFINED: Final = ErrorCode("name-defined", "Check that name is defined", "General") @@ -96,6 +107,12 @@ def __str__(self) -> str: IMPORT: Final = ErrorCode( "import", "Require that imported module can be found or has stubs", "General" ) +IMPORT_NOT_FOUND: Final = ErrorCode( + "import-not-found", "Require that imported module can be found", "General", sub_code_of=IMPORT +) +IMPORT_UNTYPED: Final = ErrorCode( + "import-untyped", "Require that imported module has stubs", "General", sub_code_of=IMPORT +) NO_REDEF: Final = ErrorCode("no-redef", "Check that each name is defined once", "General") FUNC_RETURNS_VALUE: Final = ErrorCode( "func-returns-value", "Check that called function returns a value in value context", "General" @@ -113,7 +130,7 @@ def __str__(self) -> str: "str-format", "Check that string formatting/interpolation is type-safe", "General" ) STR_BYTES_PY3: Final = ErrorCode( - "str-bytes-safe", "Warn about dangerous coercions related to bytes and string types", "General" + "str-bytes-safe", "Warn about implicit coercions related to bytes and string types", "General" ) EXIT_RETURN: Final = ErrorCode( "exit-return", "Warn about too general return type for '__exit__'", "General" @@ -132,7 +149,12 @@ def __str__(self) -> str: SAFE_SUPER: Final = ErrorCode( "safe-super", "Warn about calls to abstract methods with empty/trivial bodies", "General" ) - +TOP_LEVEL_AWAIT: Final = ErrorCode( + "top-level-await", "Warn about top level await expressions", "General" +) +AWAIT_NOT_ASYNC: Final = ErrorCode( + "await-not-async", 'Warn about "await" outside coroutine ("async def")', "General" +) # These error codes aren't enabled by default. NO_UNTYPED_DEF: Final[ErrorCode] = ErrorCode( "no-untyped-def", "Check that every function has an annotation", "General" @@ -218,10 +240,30 @@ def __str__(self) -> str: USED_BEFORE_DEF: Final[ErrorCode] = ErrorCode( "used-before-def", "Warn about variables that are used before they are defined", "General" ) - +UNUSED_IGNORE: Final = ErrorCode( + "unused-ignore", "Ensure that all type ignores are used", "General", default_enabled=False +) +EXPLICIT_OVERRIDE_REQUIRED: Final = ErrorCode( + "explicit-override", + "Require @override decorator if method is overriding a base class method", + "General", + default_enabled=False, +) +UNIMPORTED_REVEAL: Final = ErrorCode( + "unimported-reveal", + "Require explicit import from typing or typing_extensions for reveal_type", + "General", + default_enabled=False, +) +MUTABLE_OVERRIDE: Final[ErrorCode] = ErrorCode( + "mutable-override", + "Reject covariant overrides for mutable attributes", + "General", + default_enabled=False, +) # Syntax errors are often blocking. -SYNTAX: Final = ErrorCode("syntax", "Report syntax errors", "General") +SYNTAX: Final[ErrorCode] = ErrorCode("syntax", "Report syntax errors", "General") # This is an internal marker code for a whole-file ignore. It is not intended to # be user-visible. @@ -230,3 +272,26 @@ def __str__(self) -> str: # This is a catch-all for remaining uncategorized errors. MISC: Final = ErrorCode("misc", "Miscellaneous other checks", "General") + +OVERLOAD_OVERLAP: Final[ErrorCode] = ErrorCode( + "overload-overlap", + "Warn if multiple @overload variants overlap in unsafe ways", + "General", + sub_code_of=MISC, +) + +PROPERTY_DECORATOR = ErrorCode( + "prop-decorator", + "Decorators on top of @property are not supported", + "General", + sub_code_of=MISC, +) + +NARROWED_TYPE_NOT_SUBTYPE: Final[ErrorCode] = ErrorCode( + "narrowed-type-not-subtype", + "Warn if a TypeIs function's narrowed type is not a subtype of the original type", + "General", +) + +# This copy will not include any error codes defined later in the plugins. +mypy_error_codes = error_codes.copy() diff --git a/mypy/errors.py b/mypy/errors.py index 2c2c1e5ca227..7a937da39c20 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -4,11 +4,12 @@ import sys import traceback from collections import defaultdict -from typing import Callable, Iterable, NoReturn, Optional, TextIO, Tuple, TypeVar -from typing_extensions import Final, Literal, TypeAlias as _TypeAlias +from typing import Callable, Final, Iterable, NoReturn, Optional, TextIO, Tuple, TypeVar +from typing_extensions import Literal, TypeAlias as _TypeAlias from mypy import errorcodes as codes -from mypy.errorcodes import IMPORT, ErrorCode +from mypy.error_formatter import ErrorFormatter +from mypy.errorcodes import IMPORT, IMPORT_NOT_FOUND, IMPORT_UNTYPED, ErrorCode, mypy_error_codes from mypy.message_registry import ErrorMessage from mypy.options import Options from mypy.scope import Scope @@ -20,8 +21,27 @@ # Show error codes for some note-level messages (these usually appear alone # and not as a comment for a previous error-level message). SHOW_NOTE_CODES: Final = {codes.ANNOTATION_UNCHECKED} + +# Do not add notes with links to error code docs to errors with these codes. +# We can tweak this set as we get more experience about what is helpful and what is not. +HIDE_LINK_CODES: Final = { + # This is a generic error code, so it has no useful docs + codes.MISC, + # These are trivial and have some custom notes (e.g. for list being invariant) + codes.ASSIGNMENT, + codes.ARG_TYPE, + codes.RETURN_VALUE, + # Undefined name/attribute errors are self-explanatory + codes.ATTR_DEFINED, + codes.NAME_DEFINED, + # Overrides have a custom link to docs + codes.OVERRIDE, +} + allowed_duplicates: Final = ["@overload", "Got:", "Expected:"] +BASE_RTD_URL: Final = "https://mypy.rtfd.io/en/stable/_refs.html#code" + # Keep track of the original error code when the error code of a message is changed. # This is used to give notes about out-of-date "type: ignore" comments. original_error_codes: Final = {codes.LITERAL_REQ: codes.MISC, codes.TYPE_ABSTRACT: codes.MISC} @@ -90,6 +110,7 @@ class ErrorInfo: def __init__( self, import_ctx: list[tuple[str, int]], + *, file: str, module: str | None, typ: str | None, @@ -106,6 +127,7 @@ def __init__( allow_dups: bool, origin: tuple[str, Iterable[int]] | None = None, target: str | None = None, + priority: int = 0, ) -> None: self.import_ctx = import_ctx self.file = file @@ -124,6 +146,7 @@ def __init__( self.allow_dups = allow_dups self.origin = origin or (file, [line]) self.target = target + self.priority = priority # Type used internally to represent errors: @@ -148,7 +171,7 @@ def __init__( *, filter_errors: bool | Callable[[str, ErrorInfo], bool] = False, save_filtered_errors: bool = False, - ): + ) -> None: self.errors = errors self._has_new_errors = False self._filter = filter_errors @@ -222,6 +245,10 @@ class Errors: # (path -> line -> error-codes) ignored_lines: dict[str, dict[int, list[str]]] + # Lines that were skipped during semantic analysis e.g. due to ALWAYS_FALSE, MYPY_FALSE, + # or platform/version checks. Those lines would not be type-checked. + skipped_lines: dict[str, set[int]] + # Lines on which an error was actually ignored. used_ignored_lines: dict[str, dict[int, list[str]]] @@ -258,28 +285,17 @@ class Errors: def __init__( self, - show_error_context: bool = False, - show_column_numbers: bool = False, - hide_error_codes: bool = False, - pretty: bool = False, - show_error_end: bool = False, + options: Options, + *, read_source: Callable[[str], list[str] | None] | None = None, - show_absolute_path: bool = False, - many_errors_threshold: int = -1, - options: Options | None = None, + hide_error_codes: bool | None = None, ) -> None: - self.show_error_context = show_error_context - self.show_column_numbers = show_column_numbers - self.hide_error_codes = hide_error_codes - self.show_absolute_path = show_absolute_path - self.pretty = pretty - self.show_error_end = show_error_end - if show_error_end: - assert show_column_numbers, "Inconsistent formatting, must be prevented by argparse" + self.options = options + self.hide_error_codes = ( + hide_error_codes if hide_error_codes is not None else options.hide_error_codes + ) # We use fscache to read source code when showing snippets. self.read_source = read_source - self.many_errors_threshold = many_errors_threshold - self.options = options self.initialize() def initialize(self) -> None: @@ -288,6 +304,7 @@ def initialize(self) -> None: self.import_ctx = [] self.function_or_member = [None] self.ignored_lines = {} + self.skipped_lines = {} self.used_ignored_lines = defaultdict(lambda: defaultdict(list)) self.ignored_files = set() self.only_once_messages = set() @@ -308,7 +325,7 @@ def set_ignore_prefix(self, prefix: str) -> None: self.ignore_prefix = prefix def simplify_path(self, file: str) -> str: - if self.show_absolute_path: + if self.options.show_absolute_path: return os.path.abspath(file) else: file = os.path.normpath(file) @@ -336,6 +353,9 @@ def set_file_ignored_lines( if ignore_all: self.ignored_files.add(file) + def set_skipped_lines(self, file: str, skipped_lines: set[int]) -> None: + self.skipped_lines[file] = skipped_lines + def current_target(self) -> str | None: """Retrieves the current target from the associated scope. @@ -349,11 +369,11 @@ def current_module(self) -> str | None: def import_context(self) -> list[tuple[str, int]]: """Return a copy of the import context.""" - return self.import_ctx[:] + return self.import_ctx.copy() def set_import_context(self, ctx: list[tuple[str, int]]) -> None: """Replace the entire import context with a new value.""" - self.import_ctx = ctx[:] + self.import_ctx = ctx.copy() def report( self, @@ -419,21 +439,21 @@ def report( code = code or (codes.MISC if not blocker else None) info = ErrorInfo( - self.import_context(), - file, - self.current_module(), - type, - function, - line, - column, - end_line, - end_column, - severity, - message, - code, - blocker, - only_once, - allow_dups, + import_ctx=self.import_context(), + file=file, + module=self.current_module(), + typ=type, + function_or_member=function, + line=line, + column=column, + end_line=end_line, + end_column=end_column, + severity=severity, + message=message, + code=code, + blocker=blocker, + only_once=only_once, + allow_dups=allow_dups, origin=(self.file, origin_span), target=self.current_target(), ) @@ -450,7 +470,7 @@ def _add_error_info(self, file: str, info: ErrorInfo) -> None: self.error_info_map[file].append(info) if info.blocker: self.has_blockers.add(file) - if info.code is IMPORT: + if info.code in (IMPORT, IMPORT_UNTYPED, IMPORT_NOT_FOUND): self.seen_import_error = True def _filter_error(self, file: str, info: ErrorInfo) -> bool: @@ -491,7 +511,11 @@ def add_error_info(self, info: ErrorInfo) -> None: if info.message in self.only_once_messages: return self.only_once_messages.add(info.message) - if self.seen_import_error and info.code is not IMPORT and self.has_many_errors(): + if ( + self.seen_import_error + and info.code not in (IMPORT, IMPORT_UNTYPED, IMPORT_NOT_FOUND) + and self.has_many_errors() + ): # Missing stubs can easily cause thousands of errors about # Any types, especially when upgrading to mypy 0.900, # which no longer bundles third-party library stubs. Avoid @@ -515,32 +539,62 @@ def add_error_info(self, info: ErrorInfo) -> None: + "may be out of date" ) note = ErrorInfo( - info.import_ctx, - info.file, - info.module, - info.type, - info.function_or_member, - info.line, - info.column, - info.end_line, - info.end_column, - "note", - msg, + import_ctx=info.import_ctx, + file=info.file, + module=info.module, + typ=info.type, + function_or_member=info.function_or_member, + line=info.line, + column=info.column, + end_line=info.end_line, + end_column=info.end_column, + severity="note", + message=msg, code=None, blocker=False, only_once=False, allow_dups=False, ) self._add_error_info(file, note) + if ( + self.options.show_error_code_links + and not self.options.hide_error_codes + and info.code is not None + and info.code not in HIDE_LINK_CODES + and info.code.code in mypy_error_codes + ): + message = f"See {BASE_RTD_URL}-{info.code.code} for more info" + if message in self.only_once_messages: + return + self.only_once_messages.add(message) + info = ErrorInfo( + import_ctx=info.import_ctx, + file=info.file, + module=info.module, + typ=info.type, + function_or_member=info.function_or_member, + line=info.line, + column=info.column, + end_line=info.end_line, + end_column=info.end_column, + severity="note", + message=message, + code=info.code, + blocker=False, + only_once=True, + allow_dups=False, + priority=20, + ) + self._add_error_info(file, info) def has_many_errors(self) -> bool: - if self.many_errors_threshold < 0: + if self.options.many_errors_threshold < 0: return False - if len(self.error_info_map) >= self.many_errors_threshold: + if len(self.error_info_map) >= self.options.many_errors_threshold: return True if ( sum(len(errors) for errors in self.error_info_map.values()) - >= self.many_errors_threshold + >= self.options.many_errors_threshold ): return True return False @@ -634,40 +688,44 @@ def generate_unused_ignore_errors(self, file: str) -> None: ignored_lines = self.ignored_lines[file] used_ignored_lines = self.used_ignored_lines[file] for line, ignored_codes in ignored_lines.items(): + if line in self.skipped_lines[file]: + continue + if codes.UNUSED_IGNORE.code in ignored_codes: + continue used_ignored_codes = used_ignored_lines[line] unused_ignored_codes = set(ignored_codes) - set(used_ignored_codes) # `ignore` is used - if len(ignored_codes) == 0 and len(used_ignored_codes) > 0: + if not ignored_codes and used_ignored_codes: continue # All codes appearing in `ignore[...]` are used - if len(ignored_codes) > 0 and len(unused_ignored_codes) == 0: + if ignored_codes and not unused_ignored_codes: continue # Display detail only when `ignore[...]` specifies more than one error code unused_codes_message = "" - if len(ignored_codes) > 1 and len(unused_ignored_codes) > 0: + if len(ignored_codes) > 1 and unused_ignored_codes: unused_codes_message = f"[{', '.join(sorted(unused_ignored_codes))}]" message = f'Unused "type: ignore{unused_codes_message}" comment' for unused in unused_ignored_codes: narrower = set(used_ignored_codes) & codes.sub_code_map[unused] if narrower: - message += f", use narrower [{', '.join(narrower)}] instead of [{unused}]" + message += f", use narrower [{', '.join(narrower)}] instead of [{unused}] code" # Don't use report since add_error_info will ignore the error! info = ErrorInfo( - self.import_context(), - file, - self.current_module(), - None, - None, - line, - -1, - line, - -1, - "error", - message, - None, - False, - False, - False, + import_ctx=self.import_context(), + file=file, + module=self.current_module(), + typ=None, + function_or_member=None, + line=line, + column=-1, + end_line=line, + end_column=-1, + severity="error", + message=message, + code=codes.UNUSED_IGNORE, + blocker=False, + only_once=False, + allow_dups=False, ) self._add_error_info(file, info) @@ -705,21 +763,21 @@ def generate_ignore_without_code_errors( message = f'"type: ignore" comment without error code{codes_hint}' # Don't use report since add_error_info will ignore the error! info = ErrorInfo( - self.import_context(), - file, - self.current_module(), - None, - None, - line, - -1, - line, - -1, - "error", - message, - codes.IGNORE_WITHOUT_CODE, - False, - False, - False, + import_ctx=self.import_context(), + file=file, + module=self.current_module(), + typ=None, + function_or_member=None, + line=line, + column=-1, + end_line=line, + end_column=-1, + severity="error", + message=message, + code=codes.IGNORE_WITHOUT_CODE, + blocker=False, + only_once=False, + allow_dups=False, ) self._add_error_info(file, info) @@ -777,7 +835,7 @@ def raise_error(self, use_stdout: bool = True) -> NoReturn: ) def format_messages( - self, error_info: list[ErrorInfo], source_lines: list[str] | None + self, error_tuples: list[ErrorTuple], source_lines: list[str] | None ) -> list[str]: """Return a string list that represents the error messages. @@ -786,9 +844,6 @@ def format_messages( severity 'error'). """ a: list[str] = [] - error_info = [info for info in error_info if not info.hidden] - errors = self.render_messages(self.sort_messages(error_info)) - errors = self.remove_duplicates(errors) for ( file, line, @@ -799,12 +854,12 @@ def format_messages( message, allow_dups, code, - ) in errors: + ) in error_tuples: s = "" if file is not None: - if self.show_column_numbers and line >= 0 and column >= 0: + if self.options.show_column_numbers and line >= 0 and column >= 0: srcloc = f"{file}:{line}:{1 + column}" - if self.show_error_end and end_line >= 0 and end_column >= 0: + if self.options.show_error_end and end_line >= 0 and end_column >= 0: srcloc += f":{end_line}:{end_column}" elif line >= 0: srcloc = f"{file}:{line}" @@ -822,7 +877,7 @@ def format_messages( # displaying duplicate error codes. s = f"{s} [{code.code}]" a.append(s) - if self.pretty: + if self.options.pretty: # Add source code fragment and a location marker. if severity == "error" and source_lines and line > 0: source_line = source_lines[line - 1] @@ -844,19 +899,28 @@ def format_messages( a.append(" " * (DEFAULT_SOURCE_OFFSET + column) + marker) return a - def file_messages(self, path: str) -> list[str]: + def file_messages(self, path: str, formatter: ErrorFormatter | None = None) -> list[str]: """Return a string list of new error messages from a given file. Use a form suitable for displaying to the user. """ if path not in self.error_info_map: return [] + + error_info = self.error_info_map[path] + error_info = [info for info in error_info if not info.hidden] + error_tuples = self.render_messages(self.sort_messages(error_info)) + error_tuples = self.remove_duplicates(error_tuples) + + if formatter is not None: + errors = create_errors(error_tuples) + return [formatter.report_error(err) for err in errors] + self.flushed_files.add(path) source_lines = None - if self.pretty: - assert self.read_source + if self.options.pretty and self.read_source: source_lines = self.read_source(path) - return self.format_messages(self.error_info_map[path], source_lines) + return self.format_messages(error_tuples, source_lines) def new_messages(self) -> list[str]: """Return a string list of new error messages. @@ -894,7 +958,7 @@ def render_messages(self, errors: list[ErrorInfo]) -> list[ErrorTuple]: for e in errors: # Report module import context, if different from previous message. - if not self.show_error_context: + if not self.options.show_error_context: pass elif e.import_ctx != prev_import_context: last = len(e.import_ctx) - 1 @@ -919,7 +983,7 @@ def render_messages(self, errors: list[ErrorInfo]) -> list[ErrorTuple]: file = self.simplify_path(e.file) # Report context within a source file. - if not self.show_error_context: + if not self.options.show_error_context: pass elif e.function_or_member != prev_function_or_member or e.type != prev_type: if e.function_or_member is None: @@ -1039,6 +1103,34 @@ def sort_messages(self, errors: list[ErrorInfo]) -> list[ErrorInfo]: # Sort the errors specific to a file according to line number and column. a = sorted(errors[i0:i], key=lambda x: (x.line, x.column)) + a = self.sort_within_context(a) + result.extend(a) + return result + + def sort_within_context(self, errors: list[ErrorInfo]) -> list[ErrorInfo]: + """For the same location decide which messages to show first/last. + + Currently, we only compare within the same error code, to decide the + order of various additional notes. + """ + result = [] + i = 0 + while i < len(errors): + i0 = i + # Find neighbouring errors with the same position and error code. + while ( + i + 1 < len(errors) + and errors[i + 1].line == errors[i].line + and errors[i + 1].column == errors[i].column + and errors[i + 1].end_line == errors[i].end_line + and errors[i + 1].end_column == errors[i].end_column + and errors[i + 1].code == errors[i].code + ): + i += 1 + i += 1 + + # Sort the messages specific to a given error by priority. + a = sorted(errors[i0:i], key=lambda x: x.priority) result.extend(a) return result @@ -1194,3 +1286,56 @@ def report_internal_error( # Exit. The caller has nothing more to say. # We use exit code 2 to signal that this is no ordinary error. raise SystemExit(2) + + +class MypyError: + def __init__( + self, + file_path: str, + line: int, + column: int, + message: str, + errorcode: ErrorCode | None, + severity: Literal["error", "note"], + ) -> None: + self.file_path = file_path + self.line = line + self.column = column + self.message = message + self.errorcode = errorcode + self.severity = severity + self.hints: list[str] = [] + + +# (file_path, line, column) +_ErrorLocation = Tuple[str, int, int] + + +def create_errors(error_tuples: list[ErrorTuple]) -> list[MypyError]: + errors: list[MypyError] = [] + latest_error_at_location: dict[_ErrorLocation, MypyError] = {} + + for error_tuple in error_tuples: + file_path, line, column, _, _, severity, message, _, errorcode = error_tuple + if file_path is None: + continue + + assert severity in ("error", "note") + if severity == "note": + error_location = (file_path, line, column) + error = latest_error_at_location.get(error_location) + if error is None: + # This is purely a note, with no error correlated to it + error = MypyError(file_path, line, column, message, errorcode, severity="note") + errors.append(error) + continue + + error.hints.append(message) + + else: + error = MypyError(file_path, line, column, message, errorcode, severity="error") + errors.append(error) + error_location = (file_path, line, column) + latest_error_at_location[error_location] = error + + return errors diff --git a/mypy/evalexpr.py b/mypy/evalexpr.py index 2bc6966fa2fa..e39c5840d47a 100644 --- a/mypy/evalexpr.py +++ b/mypy/evalexpr.py @@ -6,8 +6,9 @@ put it in a mypyc-compiled file. """ + import ast -from typing_extensions import Final +from typing import Final import mypy.nodes from mypy.visitor import ExpressionVisitor diff --git a/mypy/expandtype.py b/mypy/expandtype.py index 7933283b24d6..f7fa0258f588 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -1,10 +1,9 @@ from __future__ import annotations -from typing import Iterable, Mapping, Sequence, TypeVar, cast, overload -from typing_extensions import Final +from typing import Final, Iterable, Mapping, Sequence, TypeVar, cast, overload -from mypy.nodes import ARG_POS, ARG_STAR, ArgKind, Var -from mypy.type_visitor import TypeTranslator +from mypy.nodes import ARG_STAR, Var +from mypy.state import state from mypy.types import ( ANY_STRATEGY, AnyType, @@ -18,72 +17,74 @@ NoneType, Overloaded, Parameters, + ParamSpecFlavor, ParamSpecType, PartialType, ProperType, + TrivialSyntheticTypeTranslator, TupleType, Type, TypeAliasType, TypedDictType, + TypeOfAny, TypeType, TypeVarId, TypeVarLikeType, TypeVarTupleType, TypeVarType, - TypeVisitor, UnboundType, UninhabitedType, UnionType, UnpackType, - expand_param_spec, flatten_nested_unions, get_proper_type, - remove_trivial, -) -from mypy.typevartuples import ( - find_unpack_in_list, - split_with_instance, split_with_prefix_and_suffix, ) +from mypy.typevartuples import split_with_instance + +# Solving the import cycle: +import mypy.type_visitor # ruff: isort: skip + +# WARNING: these functions should never (directly or indirectly) depend on +# is_subtype(), meet_types(), join_types() etc. +# TODO: add a static dependency test for this. @overload -def expand_type( - typ: ProperType, env: Mapping[TypeVarId, Type], allow_erased_callables: bool = ... -) -> ProperType: - ... +def expand_type(typ: CallableType, env: Mapping[TypeVarId, Type]) -> CallableType: ... @overload -def expand_type( - typ: Type, env: Mapping[TypeVarId, Type], allow_erased_callables: bool = ... -) -> Type: - ... +def expand_type(typ: ProperType, env: Mapping[TypeVarId, Type]) -> ProperType: ... -def expand_type( - typ: Type, env: Mapping[TypeVarId, Type], allow_erased_callables: bool = False -) -> Type: +@overload +def expand_type(typ: Type, env: Mapping[TypeVarId, Type]) -> Type: ... + + +def expand_type(typ: Type, env: Mapping[TypeVarId, Type]) -> Type: """Substitute any type variable references in a type given by a type environment. """ - return typ.accept(ExpandTypeVisitor(env, allow_erased_callables)) + return typ.accept(ExpandTypeVisitor(env)) @overload -def expand_type_by_instance(typ: ProperType, instance: Instance) -> ProperType: - ... +def expand_type_by_instance(typ: CallableType, instance: Instance) -> CallableType: ... @overload -def expand_type_by_instance(typ: Type, instance: Instance) -> Type: - ... +def expand_type_by_instance(typ: ProperType, instance: Instance) -> ProperType: ... + + +@overload +def expand_type_by_instance(typ: Type, instance: Instance) -> Type: ... def expand_type_by_instance(typ: Type, instance: Instance) -> Type: """Substitute type variables in type using values from an Instance. Type variables are considered to be bound by the class declaration.""" - if not instance.args: + if not instance.args and not instance.type.has_type_var_tuple_type: return typ else: variables: dict[TypeVarId, Type] = {} @@ -107,6 +108,7 @@ def expand_type_by_instance(typ: Type, instance: Instance) -> Type: instance_args = instance.args for binder, arg in zip(tvars, instance_args): + assert isinstance(binder, TypeVarLikeType) variables[binder.id] = arg return expand_type(typ, variables) @@ -123,17 +125,10 @@ def freshen_function_type_vars(callee: F) -> F: tvs = [] tvmap: dict[TypeVarId, Type] = {} for v in callee.variables: - if isinstance(v, TypeVarType): - tv: TypeVarLikeType = TypeVarType.new_unification_variable(v) - elif isinstance(v, TypeVarTupleType): - assert isinstance(v, TypeVarTupleType) - tv = TypeVarTupleType.new_unification_variable(v) - else: - assert isinstance(v, ParamSpecType) - tv = ParamSpecType.new_unification_variable(v) + tv = v.new_unification_variable(v) tvs.append(tv) tvmap[v.id] = tv - fresh = cast(CallableType, expand_type(callee, tvmap)).copy_modified(variables=tvs) + fresh = expand_type(callee, tvmap).copy_modified(variables=tvs) return cast(F, fresh) else: assert isinstance(callee, Overloaded) @@ -167,7 +162,7 @@ def freshen_all_functions_type_vars(t: T) -> T: return result -class FreshenCallableVisitor(TypeTranslator): +class FreshenCallableVisitor(mypy.type_visitor.TypeTranslator): def visit_callable_type(self, t: CallableType) -> Type: result = super().visit_callable_type(t) assert isinstance(result, ProperType) and isinstance(result, CallableType) @@ -178,16 +173,14 @@ def visit_type_alias_type(self, t: TypeAliasType) -> Type: return t.copy_modified(args=[arg.accept(self) for arg in t.args]) -class ExpandTypeVisitor(TypeVisitor[Type]): +class ExpandTypeVisitor(TrivialSyntheticTypeTranslator): """Visitor that substitutes type variables with values.""" variables: Mapping[TypeVarId, Type] # TypeVar id -> TypeVar value - def __init__( - self, variables: Mapping[TypeVarId, Type], allow_erased_callables: bool = False - ) -> None: + def __init__(self, variables: Mapping[TypeVarId, Type]) -> None: self.variables = variables - self.allow_erased_callables = allow_erased_callables + self.recursive_tvar_guard: dict[TypeVarId, Type | None] = {} def visit_unbound_type(self, t: UnboundType) -> Type: return t @@ -205,21 +198,25 @@ def visit_deleted_type(self, t: DeletedType) -> Type: return t def visit_erased_type(self, t: ErasedType) -> Type: - if not self.allow_erased_callables: - raise RuntimeError() # This may happen during type inference if some function argument # type is a generic callable, and its erased form will appear in inferred # constraints, then solver may check subtyping between them, which will trigger - # unify_generic_callables(), this is why we can get here. In all other cases it - # is a sign of a bug, since should never appear in any stored types. + # unify_generic_callables(), this is why we can get here. Another example is + # when inferring type of lambda in generic context, the lambda body contains + # a generic method in generic class. return t def visit_instance(self, t: Instance) -> Type: args = self.expand_types_with_unpack(list(t.args)) - if isinstance(args, list): - return t.copy_modified(args=args) - else: - return args + if t.type.fullname == "builtins.tuple": + # Normalize Tuple[*Tuple[X, ...], ...] -> Tuple[X, ...] + arg = args[0] + if isinstance(arg, UnpackType): + unpacked = get_proper_type(arg.type) + if isinstance(unpacked, Instance): + assert unpacked.type.fullname == "builtins.tuple" + args = list(unpacked.args) + return t.copy_modified(args=args) def visit_type_var(self, t: TypeVarType) -> Type: # Normally upper bounds can't contain other type variables, the only exception is @@ -231,125 +228,117 @@ def visit_type_var(self, t: TypeVarType) -> Type: # TODO: do we really need to do this? # If I try to remove this special-casing ~40 tests fail on reveal_type(). return repl.copy_modified(last_known_value=None) + if isinstance(repl, TypeVarType) and repl.has_default(): + if (tvar_id := repl.id) in self.recursive_tvar_guard: + return self.recursive_tvar_guard[tvar_id] or repl + self.recursive_tvar_guard[tvar_id] = None + repl = repl.accept(self) + if isinstance(repl, TypeVarType): + repl.default = repl.default.accept(self) + self.recursive_tvar_guard[tvar_id] = repl return repl def visit_param_spec(self, t: ParamSpecType) -> Type: - repl = get_proper_type(self.variables.get(t.id, t)) - if isinstance(repl, Instance): - # TODO: what does prefix mean in this case? - # TODO: why does this case even happen? Instances aren't plural. - return repl - elif isinstance(repl, (ParamSpecType, Parameters, CallableType)): - return expand_param_spec(t, repl) + # Set prefix to something empty, so we don't duplicate it below. + repl = self.variables.get(t.id, t.copy_modified(prefix=Parameters([], [], []))) + if isinstance(repl, ParamSpecType): + return repl.copy_modified( + flavor=t.flavor, + prefix=t.prefix.copy_modified( + arg_types=self.expand_types(t.prefix.arg_types) + repl.prefix.arg_types, + arg_kinds=t.prefix.arg_kinds + repl.prefix.arg_kinds, + arg_names=t.prefix.arg_names + repl.prefix.arg_names, + ), + ) + elif isinstance(repl, Parameters): + assert t.flavor == ParamSpecFlavor.BARE + return Parameters( + self.expand_types(t.prefix.arg_types) + repl.arg_types, + t.prefix.arg_kinds + repl.arg_kinds, + t.prefix.arg_names + repl.arg_names, + variables=[*t.prefix.variables, *repl.variables], + imprecise_arg_kinds=repl.imprecise_arg_kinds, + ) else: - # TODO: should this branch be removed? better not to fail silently + # We could encode Any as trivial parameters etc., but it would be too verbose. + # TODO: assert this is a trivial type, like Any, Never, or object. return repl def visit_type_var_tuple(self, t: TypeVarTupleType) -> Type: + # Sometimes solver may need to expand a type variable with (a copy of) itself + # (usually together with other TypeVars, but it is hard to filter out TypeVarTuples). + repl = self.variables.get(t.id, t) + if isinstance(repl, TypeVarTupleType): + return repl raise NotImplementedError def visit_unpack_type(self, t: UnpackType) -> Type: - # It is impossible to reasonally implement visit_unpack_type, because + # It is impossible to reasonably implement visit_unpack_type, because # unpacking inherently expands to something more like a list of types. # # Relevant sections that can call unpack should call expand_unpack() # instead. - assert False, "Mypy bug: unpacking must happen at a higher level" - - def expand_unpack(self, t: UnpackType) -> list[Type] | Instance | AnyType | None: - return expand_unpack_with_variables(t, self.variables) + # However, if the item is a variadic tuple, we can simply carry it over. + # In particular, if we expand A[*tuple[T, ...]] with substitutions {T: str}, + # it is hard to assert this without getting proper type. Another important + # example is non-normalized types when called from semanal.py. + return UnpackType(t.type.accept(self)) + + def expand_unpack(self, t: UnpackType) -> list[Type]: + assert isinstance(t.type, TypeVarTupleType) + repl = get_proper_type(self.variables.get(t.type.id, t.type)) + if isinstance(repl, UnpackType): + repl = get_proper_type(repl.type) + if isinstance(repl, TupleType): + return repl.items + elif ( + isinstance(repl, Instance) + and repl.type.fullname == "builtins.tuple" + or isinstance(repl, TypeVarTupleType) + ): + return [UnpackType(typ=repl)] + elif isinstance(repl, (AnyType, UninhabitedType)): + # Replace *Ts = Any with *Ts = *tuple[Any, ...] and same for Never. + # These types may appear here as a result of user error or failed inference. + return [UnpackType(t.type.tuple_fallback.copy_modified(args=[repl]))] + else: + raise RuntimeError(f"Invalid type replacement to expand: {repl}") def visit_parameters(self, t: Parameters) -> Type: return t.copy_modified(arg_types=self.expand_types(t.arg_types)) - def interpolate_args_for_unpack( - self, t: CallableType, var_arg: UnpackType - ) -> tuple[list[str | None], list[ArgKind], list[Type]]: + def interpolate_args_for_unpack(self, t: CallableType, var_arg: UnpackType) -> list[Type]: star_index = t.arg_kinds.index(ARG_STAR) - - # We have something like Unpack[Tuple[X1, X2, Unpack[Ts], Y1, Y2]] - if isinstance(get_proper_type(var_arg.type), TupleType): - expanded_tuple = get_proper_type(var_arg.type.accept(self)) - # TODO: handle the case that expanded_tuple is a variable length tuple. - assert isinstance(expanded_tuple, TupleType) + prefix = self.expand_types(t.arg_types[:star_index]) + suffix = self.expand_types(t.arg_types[star_index + 1 :]) + + var_arg_type = get_proper_type(var_arg.type) + new_unpack: Type + if isinstance(var_arg_type, Instance): + # we have something like Unpack[Tuple[Any, ...]] + new_unpack = var_arg + elif isinstance(var_arg_type, TupleType): + # We have something like Unpack[Tuple[Unpack[Ts], X1, X2]] + expanded_tuple = var_arg_type.accept(self) + assert isinstance(expanded_tuple, ProperType) and isinstance(expanded_tuple, TupleType) expanded_items = expanded_tuple.items + fallback = var_arg_type.partial_fallback + new_unpack = UnpackType(TupleType(expanded_items, fallback)) + elif isinstance(var_arg_type, TypeVarTupleType): + # We have plain Unpack[Ts] + fallback = var_arg_type.tuple_fallback + expanded_items = self.expand_unpack(var_arg) + new_unpack = UnpackType(TupleType(expanded_items, fallback)) else: - expanded_items_res = self.expand_unpack(var_arg) - if isinstance(expanded_items_res, list): - expanded_items = expanded_items_res - elif ( - isinstance(expanded_items_res, Instance) - and expanded_items_res.type.fullname == "builtins.tuple" - ): - # TODO: We shouldnt't simply treat this as a *arg because of suffix handling - # (there cannot be positional args after a *arg) - arg_types = ( - t.arg_types[:star_index] - + [expanded_items_res.args[0]] - + t.arg_types[star_index + 1 :] - ) - return (t.arg_names, t.arg_kinds, arg_types) - else: - return (t.arg_names, t.arg_kinds, t.arg_types) - - expanded_unpack_index = find_unpack_in_list(expanded_items) - # This is the case where we just have Unpack[Tuple[X1, X2, X3]] - # (for example if either the tuple had no unpacks, or the unpack in the - # tuple got fully expanded to something with fixed length) - if expanded_unpack_index is None: - arg_names = ( - t.arg_names[:star_index] - + [None] * len(expanded_items) - + t.arg_names[star_index + 1 :] - ) - arg_kinds = ( - t.arg_kinds[:star_index] - + [ARG_POS] * len(expanded_items) - + t.arg_kinds[star_index + 1 :] - ) - arg_types = ( - self.expand_types(t.arg_types[:star_index]) - + expanded_items - + self.expand_types(t.arg_types[star_index + 1 :]) - ) - else: - # If Unpack[Ts] simplest form still has an unpack or is a - # homogenous tuple, then only the prefix can be represented as - # positional arguments, and we pass Tuple[Unpack[Ts-1], Y1, Y2] - # as the star arg, for example. - expanded_unpack = get_proper_type(expanded_items[expanded_unpack_index]) - assert isinstance(expanded_unpack, UnpackType) - - # Extract the typevartuple so we can get a tuple fallback from it. - expanded_unpacked_tvt = get_proper_type(expanded_unpack.type) - assert isinstance(expanded_unpacked_tvt, TypeVarTupleType) - - prefix_len = expanded_unpack_index - arg_names = t.arg_names[:star_index] + [None] * prefix_len + t.arg_names[star_index:] - arg_kinds = ( - t.arg_kinds[:star_index] + [ARG_POS] * prefix_len + t.arg_kinds[star_index:] - ) - arg_types = ( - self.expand_types(t.arg_types[:star_index]) - + expanded_items[:prefix_len] - # Constructing the Unpack containing the tuple without the prefix. - + [ - UnpackType( - TupleType( - expanded_items[prefix_len:], expanded_unpacked_tvt.tuple_fallback - ) - ) - if len(expanded_items) - prefix_len > 1 - else expanded_items[0] - ] - + self.expand_types(t.arg_types[star_index + 1 :]) - ) - return (arg_names, arg_kinds, arg_types) + # We have invalid type in Unpack. This can happen when expanding aliases + # to Callable[[*Invalid], Ret] + new_unpack = AnyType(TypeOfAny.from_error, line=var_arg.line, column=var_arg.column) + return prefix + [new_unpack] + suffix - def visit_callable_type(self, t: CallableType) -> Type: + def visit_callable_type(self, t: CallableType) -> CallableType: param_spec = t.param_spec() if param_spec is not None: - repl = get_proper_type(self.variables.get(param_spec.id)) + repl = self.variables.get(param_spec.id) # If a ParamSpec in a callable type is substituted with a # callable type, we can't use normal substitution logic, # since ParamSpec is actually split into two components @@ -357,35 +346,53 @@ def visit_callable_type(self, t: CallableType) -> Type: # must expand both of them with all the argument types, # kinds and names in the replacement. The return type in # the replacement is ignored. - if isinstance(repl, CallableType) or isinstance(repl, Parameters): - # Substitute *args: P.args, **kwargs: P.kwargs - prefix = param_spec.prefix - # we need to expand the types in the prefix, so might as well - # not get them in the first place - t = t.expand_param_spec(repl, no_prefix=True) + if isinstance(repl, Parameters): + # We need to expand both the types in the prefix and the ParamSpec itself return t.copy_modified( - arg_types=self.expand_types(prefix.arg_types) + t.arg_types, - arg_kinds=prefix.arg_kinds + t.arg_kinds, - arg_names=prefix.arg_names + t.arg_names, + arg_types=self.expand_types(t.arg_types[:-2]) + repl.arg_types, + arg_kinds=t.arg_kinds[:-2] + repl.arg_kinds, + arg_names=t.arg_names[:-2] + repl.arg_names, ret_type=t.ret_type.accept(self), type_guard=(t.type_guard.accept(self) if t.type_guard is not None else None), + type_is=(t.type_is.accept(self) if t.type_is is not None else None), + imprecise_arg_kinds=(t.imprecise_arg_kinds or repl.imprecise_arg_kinds), + variables=[*repl.variables, *t.variables], + ) + elif isinstance(repl, ParamSpecType): + # We're substituting one ParamSpec for another; this can mean that the prefix + # changes, e.g. substitute Concatenate[int, P] in place of Q. + prefix = repl.prefix + clean_repl = repl.copy_modified(prefix=Parameters([], [], [])) + return t.copy_modified( + arg_types=self.expand_types(t.arg_types[:-2]) + + prefix.arg_types + + [ + clean_repl.with_flavor(ParamSpecFlavor.ARGS), + clean_repl.with_flavor(ParamSpecFlavor.KWARGS), + ], + arg_kinds=t.arg_kinds[:-2] + prefix.arg_kinds + t.arg_kinds[-2:], + arg_names=t.arg_names[:-2] + prefix.arg_names + t.arg_names[-2:], + ret_type=t.ret_type.accept(self), + from_concatenate=t.from_concatenate or bool(repl.prefix.arg_types), + imprecise_arg_kinds=(t.imprecise_arg_kinds or prefix.imprecise_arg_kinds), ) var_arg = t.var_arg() + needs_normalization = False if var_arg is not None and isinstance(var_arg.typ, UnpackType): - arg_names, arg_kinds, arg_types = self.interpolate_args_for_unpack(t, var_arg.typ) + needs_normalization = True + arg_types = self.interpolate_args_for_unpack(t, var_arg.typ) else: - arg_names = t.arg_names - arg_kinds = t.arg_kinds arg_types = self.expand_types(t.arg_types) - - return t.copy_modified( + expanded = t.copy_modified( arg_types=arg_types, - arg_names=arg_names, - arg_kinds=arg_kinds, ret_type=t.ret_type.accept(self), type_guard=(t.type_guard.accept(self) if t.type_guard is not None else None), + type_is=(t.type_is.accept(self) if t.type_is is not None else None), ) + if needs_normalization: + return expanded.with_normalized_var_args() + return expanded def visit_overloaded(self, t: Overloaded) -> Type: items: list[CallableType] = [] @@ -396,52 +403,37 @@ def visit_overloaded(self, t: Overloaded) -> Type: items.append(new_item) return Overloaded(items) - def expand_types_with_unpack( - self, typs: Sequence[Type] - ) -> list[Type] | AnyType | UninhabitedType | Instance: - """Expands a list of types that has an unpack. - - In corner cases, this can return a type rather than a list, in which case this - indicates use of Any or some error occurred earlier. In this case callers should - simply propagate the resulting type. - """ + def expand_types_with_unpack(self, typs: Sequence[Type]) -> list[Type]: + """Expands a list of types that has an unpack.""" items: list[Type] = [] for item in typs: - if isinstance(item, UnpackType): - unpacked_items = self.expand_unpack(item) - if unpacked_items is None: - # TODO: better error, something like tuple of unknown? - return UninhabitedType() - elif isinstance(unpacked_items, Instance): - if len(typs) == 1: - return unpacked_items - else: - assert False, "Invalid unpack of variable length tuple" - elif isinstance(unpacked_items, AnyType): - return unpacked_items - else: - items.extend(unpacked_items) + if isinstance(item, UnpackType) and isinstance(item.type, TypeVarTupleType): + items.extend(self.expand_unpack(item)) else: - # Must preserve original aliases when possible. items.append(item.accept(self)) return items def visit_tuple_type(self, t: TupleType) -> Type: items = self.expand_types_with_unpack(t.items) - if isinstance(items, list): - fallback = t.partial_fallback.accept(self) - fallback = get_proper_type(fallback) - if not isinstance(fallback, Instance): - fallback = t.partial_fallback - return t.copy_modified(items=items, fallback=fallback) - else: - return items + if len(items) == 1: + # Normalize Tuple[*Tuple[X, ...]] -> Tuple[X, ...] + item = items[0] + if isinstance(item, UnpackType): + unpacked = get_proper_type(item.type) + if isinstance(unpacked, Instance): + assert unpacked.type.fullname == "builtins.tuple" + if t.partial_fallback.type.fullname != "builtins.tuple": + # If it is a subtype (like named tuple) we need to preserve it, + # this essentially mimics the logic in tuple_fallback(). + return t.partial_fallback.accept(self) + return unpacked + fallback = t.partial_fallback.accept(self) + assert isinstance(fallback, ProperType) and isinstance(fallback, Instance) + return t.copy_modified(items=items, fallback=fallback) def visit_typeddict_type(self, t: TypedDictType) -> Type: fallback = t.fallback.accept(self) - fallback = get_proper_type(fallback) - if not isinstance(fallback, Instance): - fallback = t.fallback + assert isinstance(fallback, ProperType) and isinstance(fallback, Instance) return t.copy_modified(item_types=self.expand_types(t.items.values()), fallback=fallback) def visit_literal_type(self, t: LiteralType) -> Type: @@ -453,9 +445,15 @@ def visit_union_type(self, t: UnionType) -> Type: # After substituting for type variables in t.items, some resulting types # might be subtypes of others, however calling make_simplified_union() # can cause recursion, so we just remove strict duplicates. - return UnionType.make_union( + simplified = UnionType.make_union( remove_trivial(flatten_nested_unions(expanded)), t.line, t.column ) + # This call to get_proper_type() is unfortunate but is required to preserve + # the invariant that ProperType will stay ProperType after applying expand_type(), + # otherwise a single item union of a type alias will break it. Note this should not + # cause infinite recursion since pathological aliases like A = Union[A, B] are + # banned at the semantic analysis level. + return get_proper_type(simplified) def visit_partial_type(self, t: PartialType) -> Type: return t @@ -470,7 +468,9 @@ def visit_type_type(self, t: TypeType) -> Type: def visit_type_alias_type(self, t: TypeAliasType) -> Type: # Target of the type alias cannot contain type variables (not bound by the type # alias itself), so we just expand the arguments. - return t.copy_modified(args=self.expand_types(t.args)) + args = self.expand_types_with_unpack(t.args) + # TODO: normalize if target is Tuple, and args are [*tuple[X, ...]]? + return t.copy_modified(args=args) def expand_types(self, types: Iterable[Type]) -> list[Type]: a: list[Type] = [] @@ -479,42 +479,12 @@ def expand_types(self, types: Iterable[Type]) -> list[Type]: return a -def expand_unpack_with_variables( - t: UnpackType, variables: Mapping[TypeVarId, Type] -) -> list[Type] | Instance | AnyType | None: - """May return either a list of types to unpack to, any, or a single - variable length tuple. The latter may not be valid in all contexts. - """ - if isinstance(t.type, TypeVarTupleType): - repl = get_proper_type(variables.get(t.type.id, t)) - if isinstance(repl, TupleType): - return repl.items - elif isinstance(repl, Instance) and repl.type.fullname == "builtins.tuple": - return repl - elif isinstance(repl, AnyType): - # tuple[Any, ...] would be better, but we don't have - # the type info to construct that type here. - return repl - elif isinstance(repl, TypeVarTupleType): - return [UnpackType(typ=repl)] - elif isinstance(repl, UnpackType): - return [repl] - elif isinstance(repl, UninhabitedType): - return None - else: - raise NotImplementedError(f"Invalid type replacement to expand: {repl}") - else: - raise NotImplementedError(f"Invalid type to expand: {t.type}") - - @overload -def expand_self_type(var: Var, typ: ProperType, replacement: ProperType) -> ProperType: - ... +def expand_self_type(var: Var, typ: ProperType, replacement: ProperType) -> ProperType: ... @overload -def expand_self_type(var: Var, typ: Type, replacement: Type) -> Type: - ... +def expand_self_type(var: Var, typ: Type, replacement: Type) -> Type: ... def expand_self_type(var: Var, typ: Type, replacement: Type) -> Type: @@ -522,3 +492,33 @@ def expand_self_type(var: Var, typ: Type, replacement: Type) -> Type: if var.info.self_type is not None and not var.is_property: return expand_type(typ, {var.info.self_type.id: replacement}) return typ + + +def remove_trivial(types: Iterable[Type]) -> list[Type]: + """Make trivial simplifications on a list of types without calling is_subtype(). + + This makes following simplifications: + * Remove bottom types (taking into account strict optional setting) + * Remove everything else if there is an `object` + * Remove strict duplicate types + """ + removed_none = False + new_types = [] + all_types = set() + for t in types: + p_t = get_proper_type(t) + if isinstance(p_t, UninhabitedType): + continue + if isinstance(p_t, NoneType) and not state.strict_optional: + removed_none = True + continue + if isinstance(p_t, Instance) and p_t.type.fullname == "builtins.object": + return [p_t] + if p_t not in all_types: + new_types.append(t) + all_types.add(p_t) + if new_types: + return new_types + if removed_none: + return [NoneType()] + return [UninhabitedType()] diff --git a/mypy/exprtotype.py b/mypy/exprtotype.py index bbc284a5188a..2218a950788c 100644 --- a/mypy/exprtotype.py +++ b/mypy/exprtotype.py @@ -17,6 +17,7 @@ NameExpr, OpExpr, RefExpr, + StarExpr, StrExpr, TupleExpr, UnaryExpr, @@ -35,6 +36,7 @@ TypeOfAny, UnboundType, UnionType, + UnpackType, ) @@ -56,6 +58,7 @@ def expr_to_unanalyzed_type( options: Options | None = None, allow_new_syntax: bool = False, _parent: Expression | None = None, + allow_unpack: bool = False, ) -> ProperType: """Translate an expression to the corresponding type. @@ -100,7 +103,10 @@ def expr_to_unanalyzed_type( return expr_to_unanalyzed_type(args[0], options, allow_new_syntax, expr) else: base.args = tuple( - expr_to_unanalyzed_type(arg, options, allow_new_syntax, expr) for arg in args + expr_to_unanalyzed_type( + arg, options, allow_new_syntax, expr, allow_unpack=True + ) + for arg in args ) if not base.args: base.empty_tuple_index = True @@ -163,7 +169,10 @@ def expr_to_unanalyzed_type( return CallableArgument(typ, name, arg_const, expr.line, expr.column) elif isinstance(expr, ListExpr): return TypeList( - [expr_to_unanalyzed_type(t, options, allow_new_syntax, expr) for t in expr.items], + [ + expr_to_unanalyzed_type(t, options, allow_new_syntax, expr, allow_unpack=True) + for t in expr.items + ], line=expr.line, column=expr.column, ) @@ -174,9 +183,12 @@ def expr_to_unanalyzed_type( elif isinstance(expr, UnaryExpr): typ = expr_to_unanalyzed_type(expr.expr, options, allow_new_syntax) if isinstance(typ, RawExpressionType): - if isinstance(typ.literal_value, int) and expr.op == "-": - typ.literal_value *= -1 - return typ + if isinstance(typ.literal_value, int): + if expr.op == "-": + typ.literal_value *= -1 + return typ + elif expr.op == "+": + return typ raise TypeTranslationError() elif isinstance(expr, IntExpr): return RawExpressionType(expr.value, "builtins.int", line=expr.line, column=expr.column) @@ -189,5 +201,9 @@ def expr_to_unanalyzed_type( return RawExpressionType(None, "builtins.complex", line=expr.line, column=expr.column) elif isinstance(expr, EllipsisExpr): return EllipsisType(expr.line) + elif allow_unpack and isinstance(expr, StarExpr): + return UnpackType( + expr_to_unanalyzed_type(expr.expr, options, allow_new_syntax), from_star_syntax=True + ) else: raise TypeTranslationError() diff --git a/mypy/fastparse.py b/mypy/fastparse.py index ef1fdf61af2e..ee042b96339f 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -4,11 +4,12 @@ import re import sys import warnings -from typing import Any, Callable, List, Optional, Sequence, TypeVar, Union, cast -from typing_extensions import Final, Literal, overload +from typing import Any, Callable, Final, List, Optional, Sequence, TypeVar, Union, cast +from typing_extensions import Literal, overload from mypy import defaults, errorcodes as codes, message_registry from mypy.errors import Errors +from mypy.message_registry import ErrorMessage from mypy.nodes import ( ARG_NAMED, ARG_NAMED_OPT, @@ -16,6 +17,9 @@ ARG_POS, ARG_STAR, ARG_STAR2, + PARAM_SPEC_KIND, + TYPE_VAR_KIND, + TYPE_VAR_TUPLE_KIND, ArgKind, Argument, AssertStmt, @@ -78,6 +82,8 @@ TempNode, TryStmt, TupleExpr, + TypeAliasStmt, + TypeParam, UnaryExpr, Var, WhileStmt, @@ -86,7 +92,7 @@ YieldFromExpr, check_arg_names, ) -from mypy.options import Options +from mypy.options import NEW_GENERIC_SYNTAX, Options from mypy.patterns import ( AsPattern, ClassPattern, @@ -99,6 +105,7 @@ ) from mypy.reachability import infer_reachability_of_if_statement, mark_block_unreachable from mypy.sharedparse import argument_elide_name, special_function_elide_names +from mypy.traverser import TraverserVisitor from mypy.types import ( AnyType, CallableArgument, @@ -113,126 +120,71 @@ TypeOfAny, UnboundType, UnionType, + UnpackType, ) from mypy.util import bytes_to_human_readable_repr, unnamed_function -try: - # pull this into a final variable to make mypyc be quiet about the - # the default argument warning - PY_MINOR_VERSION: Final = sys.version_info[1] - - # Check if we can use the stdlib ast module instead of typed_ast. - if sys.version_info >= (3, 8): - import ast as ast3 - - assert ( - "kind" in ast3.Constant._fields - ), f"This 3.8.0 alpha ({sys.version.split()[0]}) is too old; 3.8.0a3 required" - # TODO: Num, Str, Bytes, NameConstant, Ellipsis are deprecated in 3.8. - # TODO: Index, ExtSlice are deprecated in 3.9. - from ast import ( - AST, - Attribute, - Bytes, - Call, - Ellipsis as ast3_Ellipsis, - Expression as ast3_Expression, - FunctionType, - Index, - Name, - NameConstant, - Num, - Starred, - Str, - UnaryOp, - USub, - ) - - def ast3_parse( - source: str | bytes, filename: str, mode: str, feature_version: int = PY_MINOR_VERSION - ) -> AST: - return ast3.parse( - source, - filename, - mode, - type_comments=True, # This works the magic - feature_version=feature_version, - ) - - NamedExpr = ast3.NamedExpr - Constant = ast3.Constant - else: - from typed_ast import ast3 - from typed_ast.ast3 import ( - AST, - Attribute, - Bytes, - Call, - Ellipsis as ast3_Ellipsis, - Expression as ast3_Expression, - FunctionType, - Index, - Name, - NameConstant, - Num, - Starred, - Str, - UnaryOp, - USub, - ) - - def ast3_parse( - source: str | bytes, filename: str, mode: str, feature_version: int = PY_MINOR_VERSION - ) -> AST: - return ast3.parse(source, filename, mode, feature_version=feature_version) - - # These don't exist before 3.8 - NamedExpr = Any - Constant = Any - - if sys.version_info >= (3, 10): - Match = ast3.Match - MatchValue = ast3.MatchValue - MatchSingleton = ast3.MatchSingleton - MatchSequence = ast3.MatchSequence - MatchStar = ast3.MatchStar - MatchMapping = ast3.MatchMapping - MatchClass = ast3.MatchClass - MatchAs = ast3.MatchAs - MatchOr = ast3.MatchOr - AstNode = Union[ast3.expr, ast3.stmt, ast3.pattern, ast3.ExceptHandler] - else: - Match = Any - MatchValue = Any - MatchSingleton = Any - MatchSequence = Any - MatchStar = Any - MatchMapping = Any - MatchClass = Any - MatchAs = Any - MatchOr = Any - AstNode = Union[ast3.expr, ast3.stmt, ast3.ExceptHandler] - if sys.version_info >= (3, 11): - TryStar = ast3.TryStar - else: - TryStar = Any -except ImportError: - try: - from typed_ast import ast35 # type: ignore[attr-defined] # noqa: F401 - except ImportError: - print( - "The typed_ast package is not installed.\n" - "You can install it with `python3 -m pip install typed-ast`.", - file=sys.stderr, - ) - else: - print( - "You need a more recent version of the typed_ast package.\n" - "You can update to the latest version with " - "`python3 -m pip install -U typed-ast`.", - file=sys.stderr, - ) - sys.exit(1) +# pull this into a final variable to make mypyc be quiet about the +# the default argument warning +PY_MINOR_VERSION: Final = sys.version_info[1] + +import ast as ast3 + +# TODO: Index, ExtSlice are deprecated in 3.9. +from ast import AST, Attribute, Call, FunctionType, Index, Name, Starred, UAdd, UnaryOp, USub + + +def ast3_parse( + source: str | bytes, filename: str, mode: str, feature_version: int = PY_MINOR_VERSION +) -> AST: + return ast3.parse( + source, + filename, + mode, + type_comments=True, # This works the magic + feature_version=feature_version, + ) + + +NamedExpr = ast3.NamedExpr +Constant = ast3.Constant + +if sys.version_info >= (3, 10): + Match = ast3.Match + MatchValue = ast3.MatchValue + MatchSingleton = ast3.MatchSingleton + MatchSequence = ast3.MatchSequence + MatchStar = ast3.MatchStar + MatchMapping = ast3.MatchMapping + MatchClass = ast3.MatchClass + MatchAs = ast3.MatchAs + MatchOr = ast3.MatchOr + AstNode = Union[ast3.expr, ast3.stmt, ast3.pattern, ast3.ExceptHandler] +else: + Match = Any + MatchValue = Any + MatchSingleton = Any + MatchSequence = Any + MatchStar = Any + MatchMapping = Any + MatchClass = Any + MatchAs = Any + MatchOr = Any + AstNode = Union[ast3.expr, ast3.stmt, ast3.ExceptHandler] + +if sys.version_info >= (3, 11): + TryStar = ast3.TryStar +else: + TryStar = Any + +if sys.version_info >= (3, 12): + ast_TypeAlias = ast3.TypeAlias + ast_ParamSpec = ast3.ParamSpec + ast_TypeVarTuple = ast3.TypeVarTuple +else: + ast_TypeAlias = Any + ast_ParamSpec = Any + ast_TypeVarTuple = Any N = TypeVar("N", bound=Node) @@ -241,10 +193,6 @@ def ast3_parse( MISSING_FALLBACK: Final = FakeInfo("fallback can't be filled out until semanal") _dummy_fallback: Final = Instance(MISSING_FALLBACK, [], -1) -TYPE_COMMENT_SYNTAX_ERROR: Final = "syntax error in type comment" - -INVALID_TYPE_IGNORE: Final = 'Invalid "type: ignore" comment' - TYPE_IGNORE_PATTERN: Final = re.compile(r"[^#]*#\s*type:\s*ignore\s*(.*)") @@ -252,21 +200,22 @@ def parse( source: str | bytes, fnam: str, module: str | None, - errors: Errors | None = None, + errors: Errors, options: Options | None = None, ) -> MypyFile: - """Parse a source file, without doing any semantic analysis. Return the parse tree. If errors is not provided, raise ParseError on failure. Otherwise, use the errors object to report parse errors. """ - raise_on_error = False + ignore_errors = (options is not None and options.ignore_errors) or ( + fnam in errors.ignored_files + ) + # If errors are ignored, we can drop many function bodies to speed up type checking. + strip_function_bodies = ignore_errors and (options is None or not options.preserve_asts) + if options is None: options = Options() - if errors is None: - errors = Errors(hide_error_codes=options.hide_error_codes) - raise_on_error = True errors.set_file(fnam, module, options=options) is_stub_file = fnam.endswith(".pyi") if is_stub_file: @@ -282,9 +231,13 @@ def parse( warnings.filterwarnings("ignore", category=DeprecationWarning) ast = ast3_parse(source, fnam, "exec", feature_version=feature_version) - tree = ASTConverter(options=options, is_stub=is_stub_file, errors=errors).visit(ast) - tree.path = fnam - tree.is_stub = is_stub_file + tree = ASTConverter( + options=options, + is_stub=is_stub_file, + errors=errors, + strip_function_bodies=strip_function_bodies, + path=fnam, + ).visit(ast) except SyntaxError as e: # alias to please mypyc is_py38_or_earlier = sys.version_info < (3, 9) @@ -306,9 +259,6 @@ def parse( ) tree = MypyFile([], [], False, {}) - if raise_on_error and errors.is_errors(): - errors.raise_error() - assert isinstance(tree, MypyFile) return tree @@ -343,25 +293,26 @@ def parse_type_comment( except SyntaxError: if errors is not None: stripped_type = type_comment.split("#", 2)[0].strip() - err_msg = f'{TYPE_COMMENT_SYNTAX_ERROR} "{stripped_type}"' - errors.report(line, column, err_msg, blocker=True, code=codes.SYNTAX) + err_msg = message_registry.TYPE_COMMENT_SYNTAX_ERROR_VALUE.format(stripped_type) + errors.report(line, column, err_msg.value, blocker=True, code=err_msg.code) return None, None else: raise else: extra_ignore = TYPE_IGNORE_PATTERN.match(type_comment) if extra_ignore: - # Typeshed has a non-optional return type for group! - tag: str | None = cast(Any, extra_ignore).group(1) + tag: str | None = extra_ignore.group(1) ignored: list[str] | None = parse_type_ignore_tag(tag) if ignored is None: if errors is not None: - errors.report(line, column, INVALID_TYPE_IGNORE, code=codes.SYNTAX) + errors.report( + line, column, message_registry.INVALID_TYPE_IGNORE.value, code=codes.SYNTAX + ) else: raise SyntaxError else: ignored = None - assert isinstance(typ, ast3_Expression) + assert isinstance(typ, ast3.Expression) converted = TypeConverter( errors, line=line, override_column=column, is_evaluated=False ).visit(typ.body) @@ -377,15 +328,8 @@ def parse_type_string( string expression "blah" using this function. """ try: - _, node = parse_type_comment(expr_string.strip(), line=line, column=column, errors=None) - if isinstance(node, UnboundType) and node.original_str_expr is None: - node.original_str_expr = expr_string - node.original_str_fallback = expr_fallback_name - return node - elif isinstance(node, UnionType): - return node - else: - return RawExpressionType(expr_string, expr_fallback_name, line, column) + _, node = parse_type_comment(f"({expr_string})", line=line, column=column, errors=None) + return RawExpressionType(expr_string, expr_fallback_name, line, column, node=node) except (SyntaxError, ValueError): # Note: the parser will raise a `ValueError` instead of a SyntaxError if # the string happens to contain things like \x00. @@ -402,14 +346,24 @@ def is_no_type_check_decorator(expr: ast3.expr) -> bool: class ASTConverter: - def __init__(self, options: Options, is_stub: bool, errors: Errors) -> None: - # 'C' for class, 'F' for function - self.class_and_function_stack: list[Literal["C", "F"]] = [] + def __init__( + self, + options: Options, + is_stub: bool, + errors: Errors, + *, + strip_function_bodies: bool, + path: str, + ) -> None: + # 'C' for class, 'D' for function signature, 'F' for function, 'L' for lambda + self.class_and_function_stack: list[Literal["C", "D", "F", "L"]] = [] self.imports: list[ImportBase] = [] self.options = options self.is_stub = is_stub self.errors = errors + self.strip_function_bodies = strip_function_bodies + self.path = path self.type_ignores: dict[int, list[str]] = {} @@ -419,24 +373,20 @@ def __init__(self, options: Options, is_stub: bool, errors: Errors) -> None: def note(self, msg: str, line: int, column: int) -> None: self.errors.report(line, column, msg, severity="note", code=codes.SYNTAX) - def fail( - self, - msg: str, - line: int, - column: int, - blocker: bool = True, - code: codes.ErrorCode = codes.SYNTAX, - ) -> None: + def fail(self, msg: ErrorMessage, line: int, column: int, blocker: bool = True) -> None: if blocker or not self.options.ignore_errors: - self.errors.report(line, column, msg, blocker=blocker, code=code) + # Make sure self.errors reflects any type ignores that we have parsed + self.errors.set_file_ignored_lines( + self.path, self.type_ignores, self.options.ignore_errors + ) + self.errors.report(line, column, msg.value, blocker=blocker, code=msg.code) def fail_merge_overload(self, node: IfStmt) -> None: self.fail( - "Condition can't be inferred, unable to merge overloads", + message_registry.FAILED_TO_MERGE_OVERLOADS, line=node.line, column=node.column, blocker=False, - code=codes.MISC, ) def visit(self, node: AST | None) -> Any: @@ -477,7 +427,12 @@ def get_lineno(self, node: ast3.expr | ast3.stmt) -> int: return node.lineno def translate_stmt_list( - self, stmts: Sequence[ast3.stmt], ismodule: bool = False + self, + stmts: Sequence[ast3.stmt], + *, + ismodule: bool = False, + can_strip: bool = False, + is_coroutine: bool = False, ) -> list[Statement]: # A "# type: ignore" comment before the first statement of a module # ignores the whole module: @@ -487,12 +442,11 @@ def translate_stmt_list( and self.type_ignores and min(self.type_ignores) < self.get_lineno(stmts[0]) ): - if self.type_ignores[min(self.type_ignores)]: + ignores = self.type_ignores[min(self.type_ignores)] + if ignores: + joined_ignores = ", ".join(ignores) self.fail( - ( - "type ignore with error code is not supported for modules; " - "use `# mypy: disable-error-code=...`" - ), + message_registry.TYPE_IGNORE_WITH_ERRCODE_ON_MODULE.format(joined_ignores), line=min(self.type_ignores), column=0, blocker=False, @@ -501,14 +455,53 @@ def translate_stmt_list( codes.FILE.code ) block = Block(self.fix_function_overloads(self.translate_stmt_list(stmts))) + self.set_block_lines(block, stmts) mark_block_unreachable(block) return [block] + stack = self.class_and_function_stack + # Fast case for stripping function bodies + if ( + can_strip + and self.strip_function_bodies + and len(stack) == 1 + and stack[0] == "F" + and not is_coroutine + ): + return [] + res: list[Statement] = [] for stmt in stmts: node = self.visit(stmt) res.append(node) + # Slow case for stripping function bodies + if can_strip and self.strip_function_bodies: + if stack[-2:] == ["C", "F"]: + if is_possible_trivial_body(res): + can_strip = False + else: + # We only strip method bodies if they don't assign to an attribute, as + # this may define an attribute which has an externally visible effect. + visitor = FindAttributeAssign() + for s in res: + s.accept(visitor) + if visitor.found: + can_strip = False + break + + if can_strip and stack[-1] == "F" and is_coroutine: + # Yields inside an async function affect the return type and should not + # be stripped. + yield_visitor = FindYield() + for s in res: + s.accept(yield_visitor) + if yield_visitor.found: + can_strip = False + break + + if can_strip: + return [] return res def translate_type_comment( @@ -566,19 +559,38 @@ def from_comp_operator(self, op: ast3.cmpop) -> str: else: return op_name - def as_block(self, stmts: list[ast3.stmt], lineno: int) -> Block | None: + def set_block_lines(self, b: Block, stmts: Sequence[ast3.stmt]) -> None: + first, last = stmts[0], stmts[-1] + b.line = first.lineno + b.column = first.col_offset + b.end_line = getattr(last, "end_lineno", None) + b.end_column = getattr(last, "end_col_offset", None) + if not b.body: + return + new_first = b.body[0] + if isinstance(new_first, (Decorator, OverloadedFuncDef)): + # Decorated function lines are different between Python versions. + # copy the normalization we do for them to block first lines. + b.line = new_first.line + b.column = new_first.column + + def as_block(self, stmts: list[ast3.stmt]) -> Block | None: b = None if stmts: b = Block(self.fix_function_overloads(self.translate_stmt_list(stmts))) - b.set_line(lineno) + self.set_block_lines(b, stmts) return b - def as_required_block(self, stmts: list[ast3.stmt], lineno: int) -> Block: + def as_required_block( + self, stmts: list[ast3.stmt], *, can_strip: bool = False, is_coroutine: bool = False + ) -> Block: assert stmts # must be non-empty - b = Block(self.fix_function_overloads(self.translate_stmt_list(stmts))) - # TODO: in most call sites line is wrong (includes first line of enclosing statement) - # TODO: also we need to set the column, and the end position here. - b.set_line(lineno) + b = Block( + self.fix_function_overloads( + self.translate_stmt_list(stmts, can_strip=can_strip, is_coroutine=is_coroutine) + ) + ) + self.set_block_lines(b, stmts) return b def fix_function_overloads(self, stmts: list[Statement]) -> list[Statement]: @@ -599,10 +611,9 @@ def fix_function_overloads(self, stmts: list[Statement]) -> list[Statement]: # Check IfStmt block to determine if function overloads can be merged if_overload_name = self._check_ifstmt_for_overloads(stmt, current_overload_name) if if_overload_name is not None: - ( - if_block_with_overload, - if_unknown_truth_value, - ) = self._get_executable_if_block_with_overloads(stmt) + (if_block_with_overload, if_unknown_truth_value) = ( + self._get_executable_if_block_with_overloads(stmt) + ) if ( current_overload_name is not None @@ -664,7 +675,9 @@ def fix_function_overloads(self, stmts: list[Statement]) -> list[Statement]: if current_overload and current_overload_name == last_if_stmt_overload_name: # Remove last stmt (IfStmt) from ret if the overload names matched # Only happens if no executable block had been found in IfStmt - skipped_if_stmts.append(cast(IfStmt, ret.pop())) + popped = ret.pop() + assert isinstance(popped, IfStmt) + skipped_if_stmts.append(popped) if current_overload and skipped_if_stmts: # Add bare IfStmt (without overloads) to ret # Required for mypy to be able to still check conditions @@ -829,9 +842,6 @@ def _is_stripped_if_stmt(self, stmt: Statement) -> bool: # For elif, IfStmt are stored recursively in else_body return self._is_stripped_if_stmt(stmt.else_body.body[0]) - def in_method_scope(self) -> bool: - return self.class_and_function_stack[-2:] == ["C", "F"] - def translate_module_id(self, id: str) -> str: """Return the actual, internal module id for a source text id.""" if id == self.options.custom_typing_module: @@ -841,13 +851,18 @@ def translate_module_id(self, id: str) -> str: def visit_Module(self, mod: ast3.Module) -> MypyFile: self.type_ignores = {} for ti in mod.type_ignores: - parsed = parse_type_ignore_tag(ti.tag) # type: ignore[attr-defined] + parsed = parse_type_ignore_tag(ti.tag) if parsed is not None: self.type_ignores[ti.lineno] = parsed else: - self.fail(INVALID_TYPE_IGNORE, ti.lineno, -1, blocker=False) + self.fail(message_registry.INVALID_TYPE_IGNORE, ti.lineno, -1, blocker=False) + body = self.fix_function_overloads(self.translate_stmt_list(mod.body, ismodule=True)) - return MypyFile(body, self.imports, False, self.type_ignores) + + ret = MypyFile(body, self.imports, False, ignored_lines=self.type_ignores) + ret.is_stub = self.is_stub + ret.path = self.path + return ret # --- stmt --- # FunctionDef(identifier name, arguments args, @@ -866,7 +881,7 @@ def do_func_def( self, n: ast3.FunctionDef | ast3.AsyncFunctionDef, is_coroutine: bool = False ) -> FuncDef | Decorator: """Helper shared between visit_FunctionDef and visit_AsyncFunctionDef.""" - self.class_and_function_stack.append("F") + self.class_and_function_stack.append("D") no_type_check = bool( n.decorator_list and any(is_no_type_check_decorator(d) for d in n.decorator_list) ) @@ -879,6 +894,8 @@ def do_func_def( arg_kinds = [arg.kind for arg in args] arg_names = [None if arg.pos_only else arg.variable.name for arg in args] + # Type parameters, if using new syntax for generics (PEP 695) + explicit_type_params: list[TypeParam] | None = None arg_types: list[Type | None] = [] if no_type_check: @@ -889,16 +906,20 @@ def do_func_def( func_type_ast = ast3_parse(n.type_comment, "", "func_type") assert isinstance(func_type_ast, FunctionType) # for ellipsis arg - if len(func_type_ast.argtypes) == 1 and isinstance( - func_type_ast.argtypes[0], ast3_Ellipsis + if ( + len(func_type_ast.argtypes) == 1 + and isinstance(func_type_ast.argtypes[0], Constant) + and func_type_ast.argtypes[0].value is Ellipsis ): if n.returns: # PEP 484 disallows both type annotations and type comments self.fail(message_registry.DUPLICATE_TYPE_SIGNATURES, lineno, n.col_offset) arg_types = [ - a.type_annotation - if a.type_annotation is not None - else AnyType(TypeOfAny.unannotated) + ( + a.type_annotation + if a.type_annotation is not None + else AnyType(TypeOfAny.unannotated) + ) for a in args ] else: @@ -913,11 +934,12 @@ def do_func_def( return_type = TypeConverter(self.errors, line=lineno).visit(func_type_ast.returns) # add implicit self type - if self.in_method_scope() and len(arg_types) < len(args): + in_method_scope = self.class_and_function_stack[-2:] == ["C", "D"] + if in_method_scope and len(arg_types) < len(args): arg_types.insert(0, AnyType(TypeOfAny.special_form)) except SyntaxError: stripped_type = n.type_comment.split("#", 2)[0].strip() - err_msg = f'{TYPE_COMMENT_SYNTAX_ERROR} "{stripped_type}"' + err_msg = message_registry.TYPE_COMMENT_SYNTAX_ERROR_VALUE.format(stripped_type) self.fail(err_msg, lineno, n.col_offset) if n.type_comment and n.type_comment[0] not in ["(", "#"]: self.note( @@ -926,6 +948,19 @@ def do_func_def( arg_types = [AnyType(TypeOfAny.from_error)] * len(args) return_type = AnyType(TypeOfAny.from_error) else: + if sys.version_info >= (3, 12) and n.type_params: + if NEW_GENERIC_SYNTAX in self.options.enable_incomplete_feature: + explicit_type_params = self.translate_type_params(n.type_params) + else: + self.fail( + ErrorMessage( + "PEP 695 generics are not yet supported", code=codes.VALID_TYPE + ), + n.type_params[0].lineno, + n.type_params[0].col_offset, + blocker=False, + ) + arg_types = [a.type_annotation for a in args] return_type = TypeConverter( self.errors, line=n.returns.lineno if n.returns else lineno @@ -937,18 +972,20 @@ def do_func_def( func_type = None if any(arg_types) or return_type: if len(arg_types) != 1 and any(isinstance(t, EllipsisType) for t in arg_types): + self.fail(message_registry.ELLIPSIS_WITH_OTHER_TYPEARGS, lineno, n.col_offset) + elif len(arg_types) > len(arg_kinds): self.fail( - "Ellipses cannot accompany other argument types in function type signature", + message_registry.TYPE_SIGNATURE_TOO_MANY_ARGS, lineno, n.col_offset, - ) - elif len(arg_types) > len(arg_kinds): - self.fail( - "Type signature has too many arguments", lineno, n.col_offset, blocker=False + blocker=False, ) elif len(arg_types) < len(arg_kinds): self.fail( - "Type signature has too few arguments", lineno, n.col_offset, blocker=False + message_registry.TYPE_SIGNATURE_TOO_FEW_ARGS, + lineno, + n.col_offset, + blocker=False, ) else: func_type = CallableType( @@ -963,7 +1000,10 @@ def do_func_def( end_line = getattr(n, "end_lineno", None) end_column = getattr(n, "end_col_offset", None) - func_def = FuncDef(n.name, args, self.as_required_block(n.body, lineno), func_type) + self.class_and_function_stack.pop() + self.class_and_function_stack.append("F") + body = self.as_required_block(n.body, can_strip=True, is_coroutine=is_coroutine) + func_def = FuncDef(n.name, args, body, func_type, explicit_type_params) if isinstance(func_def.type, CallableType): # semanal.py does some in-place modifications we want to avoid func_def.unanalyzed_type = func_def.type.copy_modified() @@ -974,15 +1014,9 @@ def do_func_def( func_type.line = lineno if n.decorator_list: - if sys.version_info < (3, 8): - # Before 3.8, [typed_]ast the line number points to the first decorator. - # In 3.8, it points to the 'def' line, where we want it. - deco_line = lineno - lineno += len(n.decorator_list) # this is only approximately true - else: - # Set deco_line to the old pre-3.8 lineno, in order to keep - # existing "# type: ignore" comments working: - deco_line = n.decorator_list[0].lineno + # Set deco_line to the old pre-3.8 lineno, in order to keep + # existing "# type: ignore" comments working: + deco_line = n.decorator_list[0].lineno var = Var(func_def.name) var.is_ready = False @@ -991,9 +1025,6 @@ def do_func_def( func_def.is_decorated = True func_def.deco_line = deco_line func_def.set_line(lineno, n.col_offset, end_line, end_column) - # Set the line again after we updated it (to make value same in Python 3.7/3.8) - # Note that TODOs in as_required_block() apply here as well. - func_def.body.set_line(lineno) deco = Decorator(func_def, self.translate_expr_list(n.decorator_list), var) first = n.decorator_list[0] @@ -1003,6 +1034,8 @@ def do_func_def( # FuncDef overrides set_line -- can't use self.set_line func_def.set_line(lineno, n.col_offset, end_line, end_column) retval = func_def + if self.options.include_docstrings: + func_def.docstring = ast3.get_docstring(n, clean=False) self.class_and_function_stack.pop() return retval @@ -1011,6 +1044,8 @@ def set_type_optional(self, type: Type | None, initializer: Expression | None) - return # Indicate that type should be wrapped in an Optional if arg is initialized to None. optional = isinstance(initializer, NameExpr) and initializer.name == "None" + if isinstance(type, RawExpressionType) and type.node is not None: + type = type.node if isinstance(type, UnboundType): type.optional = optional @@ -1091,7 +1126,7 @@ def make_argument( return argument def fail_arg(self, msg: str, arg: ast3.arg) -> None: - self.fail(msg, arg.lineno, arg.col_offset) + self.fail(ErrorMessage(msg), arg.lineno, arg.col_offset) # ClassDef(identifier name, # expr* bases, @@ -1102,29 +1137,61 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef: self.class_and_function_stack.append("C") keywords = [(kw.arg, self.visit(kw.value)) for kw in n.keywords if kw.arg] + # Type parameters, if using new syntax for generics (PEP 695) + explicit_type_params: list[TypeParam] | None = None + + if sys.version_info >= (3, 12) and n.type_params: + if NEW_GENERIC_SYNTAX in self.options.enable_incomplete_feature: + explicit_type_params = self.translate_type_params(n.type_params) + else: + self.fail( + ErrorMessage("PEP 695 generics are not yet supported", code=codes.VALID_TYPE), + n.type_params[0].lineno, + n.type_params[0].col_offset, + blocker=False, + ) + cdef = ClassDef( n.name, - self.as_required_block(n.body, n.lineno), + self.as_required_block(n.body), None, self.translate_expr_list(n.bases), metaclass=dict(keywords).get("metaclass"), keywords=keywords, + type_args=explicit_type_params, ) cdef.decorators = self.translate_expr_list(n.decorator_list) # Set lines to match the old mypy 0.700 lines, in order to keep # existing "# type: ignore" comments working: - if sys.version_info < (3, 8): - cdef.line = n.lineno + len(n.decorator_list) - cdef.deco_line = n.lineno - else: - cdef.line = n.lineno - cdef.deco_line = n.decorator_list[0].lineno if n.decorator_list else None + cdef.line = n.lineno + cdef.deco_line = n.decorator_list[0].lineno if n.decorator_list else None + + if self.options.include_docstrings: + cdef.docstring = ast3.get_docstring(n, clean=False) cdef.column = n.col_offset cdef.end_line = getattr(n, "end_lineno", None) cdef.end_column = getattr(n, "end_col_offset", None) self.class_and_function_stack.pop() return cdef + def translate_type_params(self, type_params: list[Any]) -> list[TypeParam]: + explicit_type_params = [] + for p in type_params: + bound = None + values: list[Type] = [] + if isinstance(p, ast_ParamSpec): # type: ignore[misc] + explicit_type_params.append(TypeParam(p.name, PARAM_SPEC_KIND, None, [])) + elif isinstance(p, ast_TypeVarTuple): # type: ignore[misc] + explicit_type_params.append(TypeParam(p.name, TYPE_VAR_TUPLE_KIND, None, [])) + else: + if isinstance(p.bound, ast3.Tuple): + conv = TypeConverter(self.errors, line=p.lineno) + values = [conv.visit(t) for t in p.bound.elts] + elif p.bound is not None: + bound = TypeConverter(self.errors, line=p.lineno).visit(p.bound) + explicit_type_params.append(TypeParam(p.name, TYPE_VAR_KIND, bound, values)) + return explicit_type_params + # Return(expr? value) def visit_Return(self, n: ast3.Return) -> ReturnStmt: node = ReturnStmt(self.visit(n.value)) @@ -1176,8 +1243,8 @@ def visit_For(self, n: ast3.For) -> ForStmt: node = ForStmt( self.visit(n.target), self.visit(n.iter), - self.as_required_block(n.body, n.lineno), - self.as_block(n.orelse, n.lineno), + self.as_required_block(n.body), + self.as_block(n.orelse), target_type, ) return self.set_line(node, n) @@ -1188,8 +1255,8 @@ def visit_AsyncFor(self, n: ast3.AsyncFor) -> ForStmt: node = ForStmt( self.visit(n.target), self.visit(n.iter), - self.as_required_block(n.body, n.lineno), - self.as_block(n.orelse, n.lineno), + self.as_required_block(n.body), + self.as_block(n.orelse), target_type, ) node.is_async = True @@ -1198,19 +1265,14 @@ def visit_AsyncFor(self, n: ast3.AsyncFor) -> ForStmt: # While(expr test, stmt* body, stmt* orelse) def visit_While(self, n: ast3.While) -> WhileStmt: node = WhileStmt( - self.visit(n.test), - self.as_required_block(n.body, n.lineno), - self.as_block(n.orelse, n.lineno), + self.visit(n.test), self.as_required_block(n.body), self.as_block(n.orelse) ) return self.set_line(node, n) # If(expr test, stmt* body, stmt* orelse) def visit_If(self, n: ast3.If) -> IfStmt: - lineno = n.lineno node = IfStmt( - [self.visit(n.test)], - [self.as_required_block(n.body, lineno)], - self.as_block(n.orelse, lineno), + [self.visit(n.test)], [self.as_required_block(n.body)], self.as_block(n.orelse) ) return self.set_line(node, n) @@ -1220,7 +1282,7 @@ def visit_With(self, n: ast3.With) -> WithStmt: node = WithStmt( [self.visit(i.context_expr) for i in n.items], [self.visit(i.optional_vars) for i in n.items], - self.as_required_block(n.body, n.lineno), + self.as_required_block(n.body), target_type, ) return self.set_line(node, n) @@ -1231,7 +1293,7 @@ def visit_AsyncWith(self, n: ast3.AsyncWith) -> WithStmt: s = WithStmt( [self.visit(i.context_expr) for i in n.items], [self.visit(i.optional_vars) for i in n.items], - self.as_required_block(n.body, n.lineno), + self.as_required_block(n.body), target_type, ) s.is_async = True @@ -1248,15 +1310,15 @@ def visit_Try(self, n: ast3.Try) -> TryStmt: self.set_line(NameExpr(h.name), h) if h.name is not None else None for h in n.handlers ] types = [self.visit(h.type) for h in n.handlers] - handlers = [self.as_required_block(h.body, h.lineno) for h in n.handlers] + handlers = [self.as_required_block(h.body) for h in n.handlers] node = TryStmt( - self.as_required_block(n.body, n.lineno), + self.as_required_block(n.body), vs, types, handlers, - self.as_block(n.orelse, n.lineno), - self.as_block(n.finalbody, n.lineno), + self.as_block(n.orelse), + self.as_block(n.finalbody), ) return self.set_line(node, n) @@ -1265,15 +1327,15 @@ def visit_TryStar(self, n: TryStar) -> TryStmt: self.set_line(NameExpr(h.name), h) if h.name is not None else None for h in n.handlers ] types = [self.visit(h.type) for h in n.handlers] - handlers = [self.as_required_block(h.body, h.lineno) for h in n.handlers] + handlers = [self.as_required_block(h.body) for h in n.handlers] node = TryStmt( - self.as_required_block(n.body, n.lineno), + self.as_required_block(n.body), vs, types, handlers, - self.as_block(n.orelse, n.lineno), - self.as_block(n.finalbody, n.lineno), + self.as_block(n.orelse), + self.as_block(n.finalbody), ) node.is_star = True return self.set_line(node, n) @@ -1407,9 +1469,9 @@ def visit_Lambda(self, n: ast3.Lambda) -> LambdaExpr: body.lineno = n.body.lineno body.col_offset = n.body.col_offset - e = LambdaExpr( - self.transform_args(n.args, n.lineno), self.as_required_block([body], n.lineno) - ) + self.class_and_function_stack.append("L") + e = LambdaExpr(self.transform_args(n.args, n.lineno), self.as_required_block([body])) + self.class_and_function_stack.pop() e.set_line(n.lineno, n.col_offset) # Overrides set_line -- can't use self.set_line return e @@ -1510,9 +1572,9 @@ def visit_Constant(self, n: Constant) -> Any: if val is None: e = NameExpr("None") elif isinstance(val, str): - e = StrExpr(n.s) + e = StrExpr(val) elif isinstance(val, bytes): - e = BytesExpr(bytes_to_human_readable_repr(n.s)) + e = BytesExpr(bytes_to_human_readable_repr(val)) elif isinstance(val, bool): # Must check before int! e = NameExpr(str(val)) elif isinstance(val, int): @@ -1527,28 +1589,6 @@ def visit_Constant(self, n: Constant) -> Any: raise RuntimeError("Constant not implemented for " + str(type(val))) return self.set_line(e, n) - # Num(object n) -- a number as a PyObject. - def visit_Num(self, n: ast3.Num) -> IntExpr | FloatExpr | ComplexExpr: - # The n field has the type complex, but complex isn't *really* - # a parent of int and float, and this causes isinstance below - # to think that the complex branch is always picked. Avoid - # this by throwing away the type. - val: object = n.n - if isinstance(val, int): - e: IntExpr | FloatExpr | ComplexExpr = IntExpr(val) - elif isinstance(val, float): - e = FloatExpr(val) - elif isinstance(val, complex): - e = ComplexExpr(val) - else: - raise RuntimeError("num not implemented for " + str(type(val))) - return self.set_line(e, n) - - # Str(string s) - def visit_Str(self, n: Str) -> StrExpr: - e = StrExpr(n.s) - return self.set_line(e, n) - # JoinedStr(expr* values) def visit_JoinedStr(self, n: ast3.JoinedStr) -> Expression: # Each of n.values is a str or FormattedValue; we just concatenate @@ -1560,6 +1600,12 @@ def visit_JoinedStr(self, n: ast3.JoinedStr) -> Expression: # Don't make unnecessary join call if there is only one str to join if len(strs_to_join.items) == 1: return self.set_line(strs_to_join.items[0], n) + elif len(strs_to_join.items) > 1: + last = strs_to_join.items[-1] + if isinstance(last, StrExpr) and last.value == "": + # 3.12 can add an empty literal at the end. Delete it for consistency + # between Python versions. + del strs_to_join.items[-1:] join_method = MemberExpr(empty_string, "join") join_method.set_line(empty_string) result_expression = CallExpr(join_method, [strs_to_join], [ARG_POS], [None]) @@ -1573,7 +1619,7 @@ def visit_FormattedValue(self, n: ast3.FormattedValue) -> Expression: # to allow mypyc to support f-strings with format specifiers and conversions. val_exp = self.visit(n.value) val_exp.set_line(n.lineno, n.col_offset) - conv_str = "" if n.conversion is None or n.conversion < 0 else "!" + chr(n.conversion) + conv_str = "" if n.conversion < 0 else "!" + chr(n.conversion) format_string = StrExpr("{" + conv_str + ":{}}") format_spec_exp = self.visit(n.format_spec) if n.format_spec is not None else StrExpr("") format_string.set_line(n.lineno, n.col_offset) @@ -1584,21 +1630,6 @@ def visit_FormattedValue(self, n: ast3.FormattedValue) -> Expression: ) return self.set_line(result_expression, n) - # Bytes(bytes s) - def visit_Bytes(self, n: ast3.Bytes) -> BytesExpr | StrExpr: - e = BytesExpr(bytes_to_human_readable_repr(n.s)) - return self.set_line(e, n) - - # NameConstant(singleton value) - def visit_NameConstant(self, n: NameConstant) -> NameExpr: - e = NameExpr(str(n.value)) - return self.set_line(e, n) - - # Ellipsis - def visit_Ellipsis(self, n: ast3_Ellipsis) -> EllipsisExpr: - e = EllipsisExpr() - return self.set_line(e, n) - # Attribute(expr value, identifier attr, expr_context ctx) def visit_Attribute(self, n: Attribute) -> MemberExpr | SuperExpr: value = n.value @@ -1680,7 +1711,7 @@ def visit_Match(self, n: Match) -> MatchStmt: self.visit(n.subject), [self.visit(c.pattern) for c in n.cases], [self.visit(c.guard) for c in n.cases], - [self.as_required_block(c.body, n.lineno) for c in n.cases], + [self.as_required_block(c.body) for c in n.cases], ) return self.set_line(node, n) @@ -1704,7 +1735,8 @@ def visit_MatchStar(self, n: MatchStar) -> StarredPattern: if n.name is None: node = StarredPattern(None) else: - node = StarredPattern(NameExpr(n.name)) + name = self.set_line(NameExpr(n.name), n) + node = StarredPattern(name) return self.set_line(node, n) @@ -1745,6 +1777,24 @@ def visit_MatchOr(self, n: MatchOr) -> OrPattern: node = OrPattern([self.visit(pattern) for pattern in n.patterns]) return self.set_line(node, n) + # TypeAlias(identifier name, type_param* type_params, expr value) + def visit_TypeAlias(self, n: ast_TypeAlias) -> TypeAliasStmt | AssignmentStmt: + node: TypeAliasStmt | AssignmentStmt + if NEW_GENERIC_SYNTAX in self.options.enable_incomplete_feature: + type_params = self.translate_type_params(n.type_params) + value = self.visit(n.value) + node = TypeAliasStmt(self.visit_Name(n.name), type_params, value) + return self.set_line(node, n) + else: + self.fail( + ErrorMessage("PEP 695 type aliases are not yet supported", code=codes.VALID_TYPE), + n.lineno, + n.col_offset, + blocker=False, + ) + node = AssignmentStmt([NameExpr(n.name.id)], self.visit(n.value)) + return self.set_line(node, n) + class TypeConverter: def __init__( @@ -1786,12 +1836,10 @@ def invalid_type(self, node: AST, note: str | None = None) -> RawExpressionType: ) @overload - def visit(self, node: ast3.expr) -> ProperType: - ... + def visit(self, node: ast3.expr) -> ProperType: ... @overload - def visit(self, node: AST | None) -> ProperType | None: - ... + def visit(self, node: AST | None) -> ProperType | None: ... def visit(self, node: AST | None) -> ProperType | None: """Modified visit -- keep track of the stack of nodes""" @@ -1816,9 +1864,9 @@ def parent(self) -> AST | None: return None return self.node_stack[-2] - def fail(self, msg: str, line: int, column: int) -> None: + def fail(self, msg: ErrorMessage, line: int, column: int) -> None: if self.errors: - self.errors.report(line, column, msg, blocker=True, code=codes.SYNTAX) + self.errors.report(line, column, msg.value, blocker=True, code=msg.code) def note(self, msg: str, line: int, column: int) -> None: if self.errors: @@ -1838,7 +1886,7 @@ def visit_Call(self, e: Call) -> Type: note = "Suggestion: use {0}[...] instead of {0}(...)".format(constructor) return self.invalid_type(e, note=note) if not constructor: - self.fail("Expected arg constructor name", e.lineno, e.col_offset) + self.fail(message_registry.ARG_CONSTRUCTOR_NAME_EXPECTED, e.lineno, e.col_offset) name: str | None = None default_type = AnyType(TypeOfAny.special_form) @@ -1851,15 +1899,13 @@ def visit_Call(self, e: Call) -> Type: elif i == 1: name = self._extract_argument_name(arg) else: - self.fail("Too many arguments for argument constructor", f.lineno, f.col_offset) + self.fail(message_registry.ARG_CONSTRUCTOR_TOO_MANY_ARGS, f.lineno, f.col_offset) for k in e.keywords: value = k.value if k.arg == "name": if name is not None: self.fail( - '"{}" gets multiple values for keyword argument "name"'.format( - constructor - ), + message_registry.MULTIPLE_VALUES_FOR_NAME_KWARG.format(constructor), f.lineno, f.col_offset, ) @@ -1867,9 +1913,7 @@ def visit_Call(self, e: Call) -> Type: elif k.arg == "type": if typ is not default_type: self.fail( - '"{}" gets multiple values for keyword argument "type"'.format( - constructor - ), + message_registry.MULTIPLE_VALUES_FOR_TYPE_KWARG.format(constructor), f.lineno, f.col_offset, ) @@ -1878,7 +1922,7 @@ def visit_Call(self, e: Call) -> Type: typ = converted else: self.fail( - f'Unexpected argument "{k.arg}" for argument constructor', + message_registry.ARG_CONSTRUCTOR_UNEXPECTED_ARG.format(k.arg), value.lineno, value.col_offset, ) @@ -1888,12 +1932,14 @@ def translate_argument_list(self, l: Sequence[ast3.expr]) -> TypeList: return TypeList([self.visit(e) for e in l], line=self.line) def _extract_argument_name(self, n: ast3.expr) -> str | None: - if isinstance(n, Str): - return n.s.strip() - elif isinstance(n, NameConstant) and str(n.value) == "None": + if isinstance(n, Constant) and isinstance(n.value, str): + return n.value.strip() + elif isinstance(n, Constant) and n.value is None: return None self.fail( - f"Expected string literal for argument name, got {type(n).__name__}", self.line, 0 + message_registry.ARG_NAME_EXPECTED_STRING_LITERAL.format(type(n).__name__), + self.line, + 0, ) return None @@ -1914,13 +1960,6 @@ def visit_BinOp(self, n: ast3.BinOp) -> Type: uses_pep604_syntax=True, ) - def visit_NameConstant(self, n: NameConstant) -> Type: - if isinstance(n.value, bool): - return RawExpressionType(n.value, "builtins.bool", line=self.line) - else: - return UnboundType(str(n.value), line=self.line, column=n.col_offset) - - # Only for 3.8 and newer def visit_Constant(self, n: Constant) -> Type: val = n.value if val is None: @@ -1928,7 +1967,7 @@ def visit_Constant(self, n: Constant) -> Type: return UnboundType("None", line=self.line) if isinstance(val, str): # Parse forward reference. - return parse_type_string(n.s, "builtins.str", self.line, n.col_offset) + return parse_type_string(val, "builtins.str", self.line, n.col_offset) if val is Ellipsis: # '...' is valid in some types. return EllipsisType(line=self.line) @@ -1945,13 +1984,19 @@ def visit_Constant(self, n: Constant) -> Type: # UnaryOp(op, operand) def visit_UnaryOp(self, n: UnaryOp) -> Type: - # We support specifically Literal[-4] and nothing else. - # For example, Literal[+4] or Literal[~6] is not supported. + # We support specifically Literal[-4], Literal[+4], and nothing else. + # For example, Literal[~6] or Literal[not False] is not supported. typ = self.visit(n.operand) - if isinstance(typ, RawExpressionType) and isinstance(n.op, USub): - if isinstance(typ.literal_value, int): + if ( + isinstance(typ, RawExpressionType) + # Use type() because we do not want to allow bools. + and type(typ.literal_value) is int # noqa: E721 + ): + if isinstance(n.op, USub): typ.literal_value *= -1 return typ + if isinstance(n.op, UAdd): + return typ return self.invalid_type(n) def numeric_type(self, value: object, n: AST) -> Type: @@ -1972,23 +2017,6 @@ def numeric_type(self, value: object, n: AST) -> Type: numeric_value, type_name, line=self.line, column=getattr(n, "col_offset", -1) ) - # These next three methods are only used if we are on python < - # 3.8, using typed_ast. They are defined unconditionally because - # mypyc can't handle conditional method definitions. - - # Num(number n) - def visit_Num(self, n: Num) -> Type: - return self.numeric_type(n.n, n) - - # Str(string s) - def visit_Str(self, n: Str) -> Type: - return parse_type_string(n.s, "builtins.str", self.line, n.col_offset) - - # Bytes(bytes s) - def visit_Bytes(self, n: Bytes) -> Type: - contents = bytes_to_human_readable_repr(n.s) - return RawExpressionType(contents, "builtins.bytes", self.line, column=n.col_offset) - def visit_Index(self, n: ast3.Index) -> Type: # cast for mypyc's benefit on Python 3.9 value = self.visit(cast(Any, n).value) @@ -2017,10 +2045,10 @@ def visit_Subscript(self, n: ast3.Subscript) -> Type: for s in dims: if getattr(s, "col_offset", None) is None: if isinstance(s, ast3.Index): - s.col_offset = s.value.col_offset # type: ignore[attr-defined] + s.col_offset = s.value.col_offset elif isinstance(s, ast3.Slice): assert s.lower is not None - s.col_offset = s.lower.col_offset # type: ignore[attr-defined] + s.col_offset = s.lower.col_offset sliceval = ast3.Tuple(dims, n.ctx) empty_tuple_index = False @@ -2061,14 +2089,15 @@ def visit_Attribute(self, n: Attribute) -> Type: else: return self.invalid_type(n) - # Ellipsis - def visit_Ellipsis(self, n: ast3_Ellipsis) -> Type: - return EllipsisType(line=self.line) + # Used for Callable[[X *Ys, Z], R] etc. + def visit_Starred(self, n: ast3.Starred) -> Type: + return UnpackType(self.visit(n.value), from_star_syntax=True) # List(expr* elts, expr_context ctx) def visit_List(self, n: ast3.List) -> Type: assert isinstance(n.ctx, ast3.Load) - return self.translate_argument_list(n.elts) + result = self.translate_argument_list(n.elts) + return result def stringify_name(n: AST) -> str | None: @@ -2079,3 +2108,85 @@ def stringify_name(n: AST) -> str | None: if sv is not None: return f"{sv}.{n.attr}" return None # Can't do it. + + +class FindAttributeAssign(TraverserVisitor): + """Check if an AST contains attribute assignments (e.g. self.x = 0).""" + + def __init__(self) -> None: + self.lvalue = False + self.found = False + + def visit_assignment_stmt(self, s: AssignmentStmt) -> None: + self.lvalue = True + for lv in s.lvalues: + lv.accept(self) + self.lvalue = False + + def visit_with_stmt(self, s: WithStmt) -> None: + self.lvalue = True + for lv in s.target: + if lv is not None: + lv.accept(self) + self.lvalue = False + s.body.accept(self) + + def visit_for_stmt(self, s: ForStmt) -> None: + self.lvalue = True + s.index.accept(self) + self.lvalue = False + s.body.accept(self) + if s.else_body: + s.else_body.accept(self) + + def visit_expression_stmt(self, s: ExpressionStmt) -> None: + # No need to look inside these + pass + + def visit_call_expr(self, e: CallExpr) -> None: + # No need to look inside these + pass + + def visit_index_expr(self, e: IndexExpr) -> None: + # No need to look inside these + pass + + def visit_member_expr(self, e: MemberExpr) -> None: + if self.lvalue: + self.found = True + + +class FindYield(TraverserVisitor): + """Check if an AST contains yields or yield froms.""" + + def __init__(self) -> None: + self.found = False + + def visit_yield_expr(self, e: YieldExpr) -> None: + self.found = True + + def visit_yield_from_expr(self, e: YieldFromExpr) -> None: + self.found = True + + +def is_possible_trivial_body(s: list[Statement]) -> bool: + """Could the statements form a "trivial" function body, such as 'pass'? + + This mimics mypy.semanal.is_trivial_body, but this runs before + semantic analysis so some checks must be conservative. + """ + l = len(s) + if l == 0: + return False + i = 0 + if isinstance(s[0], ExpressionStmt) and isinstance(s[0].expr, StrExpr): + # Skip docstring + i += 1 + if i == l: + return True + if l > i + 1: + return False + stmt = s[i] + return isinstance(stmt, (PassStmt, RaiseStmt)) or ( + isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, EllipsisExpr) + ) diff --git a/mypy/find_sources.py b/mypy/find_sources.py index a3ef2d3db052..3565fc4609cd 100644 --- a/mypy/find_sources.py +++ b/mypy/find_sources.py @@ -4,8 +4,7 @@ import functools import os -from typing import Sequence -from typing_extensions import Final +from typing import Final, Sequence from mypy.fscache import FileSystemCache from mypy.modulefinder import PYTHON_EXTENSIONS, BuildSource, matches_exclude, mypy_path @@ -160,7 +159,7 @@ def crawl_up(self, path: str) -> tuple[str, str]: def crawl_up_dir(self, dir: str) -> tuple[str, str]: return self._crawl_up_helper(dir) or ("", dir) - @functools.lru_cache() # noqa: B019 + @functools.lru_cache # noqa: B019 def _crawl_up_helper(self, dir: str) -> tuple[str, str] | None: """Given a directory, maybe returns module and base directory. diff --git a/mypy/fixup.py b/mypy/fixup.py index 7b0f5f433d72..849a6483d724 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -2,8 +2,7 @@ from __future__ import annotations -from typing import Any -from typing_extensions import Final +from typing import Any, Final from mypy.lookup import lookup_fully_qualified from mypy.nodes import ( @@ -82,11 +81,17 @@ def visit_type_info(self, info: TypeInfo) -> None: info.update_tuple_type(info.tuple_type) if info.special_alias: info.special_alias.alias_tvars = list(info.defn.type_vars) + for i, t in enumerate(info.defn.type_vars): + if isinstance(t, TypeVarTupleType): + info.special_alias.tvar_tuple_index = i if info.typeddict_type: info.typeddict_type.accept(self.type_fixer) info.update_typeddict_type(info.typeddict_type) if info.special_alias: info.special_alias.alias_tvars = list(info.defn.type_vars) + for i, t in enumerate(info.defn.type_vars): + if isinstance(t, TypeVarTupleType): + info.special_alias.tvar_tuple_index = i if info.declared_metaclass: info.declared_metaclass.accept(self.type_fixer) if info.metaclass_type: @@ -123,8 +128,23 @@ def visit_symbol_table(self, symtab: SymbolTable, table_fullname: str) -> None: cross_ref, self.modules, raise_on_missing=not self.allow_missing ) if stnode is not None: - assert stnode.node is not None, (table_fullname + "." + key, cross_ref) - value.node = stnode.node + if stnode is value: + # The node seems to refer to itself, which can mean that + # the target is a deleted submodule of the current module, + # and thus lookup falls back to the symbol table of the parent + # package. Here's how this may happen: + # + # pkg/__init__.py: + # from pkg import sub + # + # Now if pkg.sub is deleted, the pkg.sub symbol table entry + # appears to refer to itself. Replace the entry with a + # placeholder to avoid a crash. We can't delete the entry, + # as it would stop dependency propagation. + value.node = Var(key + "@deleted") + else: + assert stnode.node is not None, (table_fullname + "." + key, cross_ref) + value.node = stnode.node elif not self.allow_missing: assert False, f"Could not find cross-ref {cross_ref}" else: @@ -167,21 +187,22 @@ def visit_decorator(self, d: Decorator) -> None: def visit_class_def(self, c: ClassDef) -> None: for v in c.type_vars: - if isinstance(v, TypeVarType): - for value in v.values: - value.accept(self.type_fixer) - v.upper_bound.accept(self.type_fixer) + v.accept(self.type_fixer) def visit_type_var_expr(self, tv: TypeVarExpr) -> None: for value in tv.values: value.accept(self.type_fixer) tv.upper_bound.accept(self.type_fixer) + tv.default.accept(self.type_fixer) def visit_paramspec_expr(self, p: ParamSpecExpr) -> None: p.upper_bound.accept(self.type_fixer) + p.default.accept(self.type_fixer) def visit_type_var_tuple_expr(self, tv: TypeVarTupleExpr) -> None: tv.upper_bound.accept(self.type_fixer) + tv.tuple_fallback.accept(self.type_fixer) + tv.default.accept(self.type_fixer) def visit_var(self, v: Var) -> None: if self.current_info is not None: @@ -249,6 +270,8 @@ def visit_callable_type(self, ct: CallableType) -> None: arg.accept(self) if ct.type_guard is not None: ct.type_guard.accept(self) + if ct.type_is is not None: + ct.type_is.accept(self) def visit_overloaded(self, t: Overloaded) -> None: for ct in t.items: @@ -303,14 +326,17 @@ def visit_type_var(self, tvt: TypeVarType) -> None: if tvt.values: for vt in tvt.values: vt.accept(self) - if tvt.upper_bound is not None: - tvt.upper_bound.accept(self) + tvt.upper_bound.accept(self) + tvt.default.accept(self) def visit_param_spec(self, p: ParamSpecType) -> None: p.upper_bound.accept(self) + p.default.accept(self) def visit_type_var_tuple(self, t: TypeVarTupleType) -> None: + t.tuple_fallback.accept(self) t.upper_bound.accept(self) + t.default.accept(self) def visit_unpack_type(self, u: UnpackType) -> None: u.type.accept(self) @@ -331,9 +357,6 @@ def visit_union_type(self, ut: UnionType) -> None: for it in ut.items: it.accept(self) - def visit_void(self, o: Any) -> None: - pass # Nothing to descend into. - def visit_type_type(self, t: TypeType) -> None: t.item.accept(self) diff --git a/mypy/graph_utils.py b/mypy/graph_utils.py new file mode 100644 index 000000000000..399301a6b0fd --- /dev/null +++ b/mypy/graph_utils.py @@ -0,0 +1,112 @@ +"""Helpers for manipulations with graphs.""" + +from __future__ import annotations + +from typing import AbstractSet, Iterable, Iterator, TypeVar + +T = TypeVar("T") + + +def strongly_connected_components( + vertices: AbstractSet[T], edges: dict[T, list[T]] +) -> Iterator[set[T]]: + """Compute Strongly Connected Components of a directed graph. + + Args: + vertices: the labels for the vertices + edges: for each vertex, gives the target vertices of its outgoing edges + + Returns: + An iterator yielding strongly connected components, each + represented as a set of vertices. Each input vertex will occur + exactly once; vertices not part of a SCC are returned as + singleton sets. + + From https://code.activestate.com/recipes/578507/. + """ + identified: set[T] = set() + stack: list[T] = [] + index: dict[T, int] = {} + boundaries: list[int] = [] + + def dfs(v: T) -> Iterator[set[T]]: + index[v] = len(stack) + stack.append(v) + boundaries.append(index[v]) + + for w in edges[v]: + if w not in index: + yield from dfs(w) + elif w not in identified: + while index[w] < boundaries[-1]: + boundaries.pop() + + if boundaries[-1] == index[v]: + boundaries.pop() + scc = set(stack[index[v] :]) + del stack[index[v] :] + identified.update(scc) + yield scc + + for v in vertices: + if v not in index: + yield from dfs(v) + + +def prepare_sccs( + sccs: list[set[T]], edges: dict[T, list[T]] +) -> dict[AbstractSet[T], set[AbstractSet[T]]]: + """Use original edges to organize SCCs in a graph by dependencies between them.""" + sccsmap = {v: frozenset(scc) for scc in sccs for v in scc} + data: dict[AbstractSet[T], set[AbstractSet[T]]] = {} + for scc in sccs: + deps: set[AbstractSet[T]] = set() + for v in scc: + deps.update(sccsmap[x] for x in edges[v]) + data[frozenset(scc)] = deps + return data + + +def topsort(data: dict[T, set[T]]) -> Iterable[set[T]]: + """Topological sort. + + Args: + data: A map from vertices to all vertices that it has an edge + connecting it to. NOTE: This data structure + is modified in place -- for normalization purposes, + self-dependencies are removed and entries representing + orphans are added. + + Returns: + An iterator yielding sets of vertices that have an equivalent + ordering. + + Example: + Suppose the input has the following structure: + + {A: {B, C}, B: {D}, C: {D}} + + This is normalized to: + + {A: {B, C}, B: {D}, C: {D}, D: {}} + + The algorithm will yield the following values: + + {D} + {B, C} + {A} + + From https://code.activestate.com/recipes/577413/. + """ + # TODO: Use a faster algorithm? + for k, v in data.items(): + v.discard(k) # Ignore self dependencies. + for item in set.union(*data.values()) - set(data.keys()): + data[item] = set() + while True: + ready = {item for item, dep in data.items() if not dep} + if not ready: + break + yield ready + data = {item: (dep - ready) for item, dep in data.items() if item not in ready} + assert not data, f"A cyclic dependency exists amongst {data!r}" diff --git a/mypy/indirection.py b/mypy/indirection.py index eef7601d6aae..00356d7a4ddb 100644 --- a/mypy/indirection.py +++ b/mypy/indirection.py @@ -64,13 +64,13 @@ def visit_deleted_type(self, t: types.DeletedType) -> set[str]: return set() def visit_type_var(self, t: types.TypeVarType) -> set[str]: - return self._visit(t.values) | self._visit(t.upper_bound) + return self._visit(t.values) | self._visit(t.upper_bound) | self._visit(t.default) def visit_param_spec(self, t: types.ParamSpecType) -> set[str]: - return set() + return self._visit(t.upper_bound) | self._visit(t.default) def visit_type_var_tuple(self, t: types.TypeVarTupleType) -> set[str]: - return self._visit(t.upper_bound) + return self._visit(t.upper_bound) | self._visit(t.default) def visit_unpack_type(self, t: types.UnpackType) -> set[str]: return t.type.accept(self) diff --git a/mypy/infer.py b/mypy/infer.py index fbec3d7c4278..bcf0c95808ab 100644 --- a/mypy/infer.py +++ b/mypy/infer.py @@ -12,7 +12,7 @@ ) from mypy.nodes import ArgKind from mypy.solve import solve_constraints -from mypy.types import CallableType, Instance, Type, TypeVarId +from mypy.types import CallableType, Instance, Type, TypeVarLikeType class ArgumentInferContext(NamedTuple): @@ -33,10 +33,12 @@ def infer_function_type_arguments( callee_type: CallableType, arg_types: Sequence[Type | None], arg_kinds: list[ArgKind], + arg_names: Sequence[str | None] | None, formal_to_actual: list[list[int]], context: ArgumentInferContext, strict: bool = True, -) -> list[Type | None]: + allow_polymorphic: bool = False, +) -> tuple[list[Type | None], list[TypeVarLikeType]]: """Infer the type arguments of a generic function. Return an array of lower bound types for the type variables -1 (at @@ -52,18 +54,22 @@ def infer_function_type_arguments( """ # Infer constraints. constraints = infer_constraints_for_callable( - callee_type, arg_types, arg_kinds, formal_to_actual, context + callee_type, arg_types, arg_kinds, arg_names, formal_to_actual, context ) # Solve constraints. - type_vars = callee_type.type_var_ids() - return solve_constraints(type_vars, constraints, strict) + type_vars = callee_type.variables + return solve_constraints(type_vars, constraints, strict, allow_polymorphic) def infer_type_arguments( - type_var_ids: list[TypeVarId], template: Type, actual: Type, is_supertype: bool = False + type_vars: Sequence[TypeVarLikeType], + template: Type, + actual: Type, + is_supertype: bool = False, + skip_unsatisfied: bool = False, ) -> list[Type | None]: # Like infer_function_type_arguments, but only match a single type # against a generic type. constraints = infer_constraints(template, actual, SUPERTYPE_OF if is_supertype else SUBTYPE_OF) - return solve_constraints(type_var_ids, constraints) + return solve_constraints(type_vars, constraints, skip_unsatisfied=skip_unsatisfied)[0] diff --git a/mypy/inspections.py b/mypy/inspections.py index d99e087b93a1..3e660a0bd7a6 100644 --- a/mypy/inspections.py +++ b/mypy/inspections.py @@ -6,7 +6,6 @@ from typing import Callable from mypy.build import State -from mypy.find_sources import InvalidSourceList, SourceFinder from mypy.messages import format_type from mypy.modulefinder import PYTHON_EXTENSIONS from mypy.nodes import ( @@ -206,9 +205,6 @@ def __init__( force_reload: bool = False, ) -> None: self.fg_manager = fg_manager - self.finder = SourceFinder( - self.fg_manager.manager.fscache, self.fg_manager.manager.options - ) self.verbosity = verbosity self.limit = limit self.include_span = include_span @@ -219,13 +215,6 @@ def __init__( # Module for which inspection was requested. self.module: State | None = None - def parse_location(self, location: str) -> tuple[str, list[int]]: - if location.count(":") not in [2, 4]: - raise ValueError("Format should be file:line:column[:end_line:end_column]") - parts = location.split(":") - module, *rest = parts - return module, [int(p) for p in rest] - def reload_module(self, state: State) -> None: """Reload given module while temporary exporting types.""" old = self.fg_manager.manager.options.export_types @@ -247,7 +236,9 @@ def expr_type(self, expression: Expression) -> tuple[str, bool]: if expr_type is None: return self.missing_type(expression), False - type_str = format_type(expr_type, verbosity=self.verbosity) + type_str = format_type( + expr_type, self.fg_manager.manager.options, verbosity=self.verbosity + ) return self.add_prefixes(type_str, expression), True def object_type(self) -> Instance: @@ -559,16 +550,14 @@ def find_module(self, file: str) -> tuple[State | None, dict[str, object]]: if not any(file.endswith(ext) for ext in PYTHON_EXTENSIONS): return None, {"error": "Source file is not a Python file"} - try: - module, _ = self.finder.crawl_up(os.path.normpath(file)) - except InvalidSourceList: - return None, {"error": "Invalid source file name: " + file} - - state = self.fg_manager.graph.get(module) + # We are using a bit slower but robust way to find a module by path, + # to be sure that namespace packages are handled properly. + abs_path = os.path.abspath(file) + state = next((s for s in self.fg_manager.graph.values() if s.abspath == abs_path), None) self.module = state return ( state, - {"out": f"Unknown module: {module}", "err": "", "status": 1} if state is None else {}, + {"out": f"Unknown module: {file}", "err": "", "status": 1} if state is None else {}, ) def run_inspection( @@ -579,7 +568,7 @@ def run_inspection( This can be re-used by various simple inspections. """ try: - file, pos = self.parse_location(location) + file, pos = parse_location(location) except ValueError as err: return {"error": str(err)} @@ -621,3 +610,18 @@ def get_definition(self, location: str) -> dict[str, object]: result["out"] = f"No name or member expressions at {location}" result["status"] = 1 return result + + +def parse_location(location: str) -> tuple[str, list[int]]: + if location.count(":") < 2: + raise ValueError("Format should be file:line:column[:end_line:end_column]") + parts = location.rsplit(":", maxsplit=2) + start, *rest = parts + # Note: we must allow drive prefix like `C:` on Windows. + if start.count(":") < 2: + return start, [int(p) for p in rest] + parts = start.rsplit(":", maxsplit=2) + start, *start_rest = parts + if start.count(":") < 2: + return start, [int(p) for p in start_rest + rest] + raise ValueError("Format should be file:line:column[:end_line:end_column]") diff --git a/mypy/ipc.py b/mypy/ipc.py index f07616df0fd0..ab01f1b79e7d 100644 --- a/mypy/ipc.py +++ b/mypy/ipc.py @@ -7,13 +7,13 @@ from __future__ import annotations import base64 +import codecs import os import shutil import sys import tempfile from types import TracebackType -from typing import Callable -from typing_extensions import Final +from typing import Callable, Final if sys.platform == "win32": # This may be private, but it is needed for IPC on Windows, and is basically stable @@ -41,6 +41,10 @@ class IPCBase: This contains logic shared between the client and server, such as reading and writing. + We want to be able to send multiple "messages" over a single connection and + to be able to separate the messages. We do this by encoding the messages + in an alphabet that does not contain spaces, then adding a space for + separation. The last framed message is also followed by a space. """ connection: _IPCHandle @@ -48,12 +52,30 @@ class IPCBase: def __init__(self, name: str, timeout: float | None) -> None: self.name = name self.timeout = timeout + self.buffer = bytearray() - def read(self, size: int = 100000) -> bytes: - """Read bytes from an IPC connection until its empty.""" - bdata = bytearray() + def frame_from_buffer(self) -> bytearray | None: + """Return a full frame from the bytes we have in the buffer.""" + space_pos = self.buffer.find(b" ") + if space_pos == -1: + return None + # We have a full frame + bdata = self.buffer[:space_pos] + self.buffer = self.buffer[space_pos + 1 :] + return bdata + + def read(self, size: int = 100000) -> str: + """Read bytes from an IPC connection until we have a full frame.""" + bdata: bytearray | None = bytearray() if sys.platform == "win32": while True: + # Check if we already have a message in the buffer before + # receiving any more data from the socket. + bdata = self.frame_from_buffer() + if bdata is not None: + break + + # Receive more data into the buffer. ov, err = _winapi.ReadFile(self.connection, size, overlapped=True) try: if err == _winapi.ERROR_IO_PENDING: @@ -67,7 +89,10 @@ def read(self, size: int = 100000) -> bytes: _, err = ov.GetOverlappedResult(True) more = ov.getbuffer() if more: - bdata.extend(more) + self.buffer.extend(more) + bdata = self.frame_from_buffer() + if bdata is not None: + break if err == 0: # we are done! break @@ -78,17 +103,34 @@ def read(self, size: int = 100000) -> bytes: raise IPCException("ReadFile operation aborted.") else: while True: + # Check if we already have a message in the buffer before + # receiving any more data from the socket. + bdata = self.frame_from_buffer() + if bdata is not None: + break + + # Receive more data into the buffer. more = self.connection.recv(size) if not more: + # Connection closed break - bdata.extend(more) - return bytes(bdata) + self.buffer.extend(more) + + if not bdata: + # Socket was empty and we didn't get any frame. + # This should only happen if the socket was closed. + return "" + return codecs.decode(bdata, "base64").decode("utf8") + + def write(self, data: str) -> None: + """Write to an IPC connection.""" + + # Frame the data by urlencoding it and separating by space. + encoded_data = codecs.encode(data.encode("utf8"), "base64") + b" " - def write(self, data: bytes) -> None: - """Write bytes to an IPC connection.""" if sys.platform == "win32": try: - ov, err = _winapi.WriteFile(self.connection, data, overlapped=True) + ov, err = _winapi.WriteFile(self.connection, encoded_data, overlapped=True) try: if err == _winapi.ERROR_IO_PENDING: timeout = int(self.timeout * 1000) if self.timeout else _winapi.INFINITE @@ -102,12 +144,11 @@ def write(self, data: bytes) -> None: raise bytes_written, err = ov.GetOverlappedResult(True) assert err == 0, err - assert bytes_written == len(data) + assert bytes_written == len(encoded_data) except OSError as e: raise IPCException(f"Failed to write with error: {e.winerror}") from e else: - self.connection.sendall(data) - self.connection.shutdown(socket.SHUT_WR) + self.connection.sendall(encoded_data) def close(self) -> None: if sys.platform == "win32": @@ -169,7 +210,6 @@ def __exit__( class IPCServer(IPCBase): - BUFFER_SIZE: Final = 2**16 def __init__(self, name: str, timeout: float | None = None) -> None: diff --git a/mypy/join.py b/mypy/join.py index 62d256f4440f..7e0ff301ebf8 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -6,7 +6,7 @@ import mypy.typeops from mypy.maptype import map_instance_to_supertype -from mypy.nodes import CONTRAVARIANT, COVARIANT, INVARIANT +from mypy.nodes import CONTRAVARIANT, COVARIANT, INVARIANT, VARIANCE_NOT_READY from mypy.state import state from mypy.subtypes import ( SubtypeContext, @@ -29,7 +29,6 @@ Parameters, ParamSpecType, PartialType, - PlaceholderType, ProperType, TupleType, Type, @@ -37,6 +36,7 @@ TypedDictType, TypeOfAny, TypeType, + TypeVarLikeType, TypeVarTupleType, TypeVarType, TypeVisitor, @@ -44,8 +44,10 @@ UninhabitedType, UnionType, UnpackType, + find_unpack_in_list, get_proper_type, get_proper_types, + split_with_prefix_and_suffix, ) @@ -68,7 +70,25 @@ def join_instances(self, t: Instance, s: Instance) -> ProperType: args: list[Type] = [] # N.B: We use zip instead of indexing because the lengths might have # mismatches during daemon reprocessing. - for ta, sa, type_var in zip(t.args, s.args, t.type.defn.type_vars): + if t.type.has_type_var_tuple_type: + # We handle joins of variadic instances by simply creating correct mapping + # for type arguments and compute the individual joins same as for regular + # instances. All the heavy lifting is done in the join of tuple types. + assert s.type.type_var_tuple_prefix is not None + assert s.type.type_var_tuple_suffix is not None + prefix = s.type.type_var_tuple_prefix + suffix = s.type.type_var_tuple_suffix + tvt = s.type.defn.type_vars[prefix] + assert isinstance(tvt, TypeVarTupleType) + fallback = tvt.tuple_fallback + s_prefix, s_middle, s_suffix = split_with_prefix_and_suffix(s.args, prefix, suffix) + t_prefix, t_middle, t_suffix = split_with_prefix_and_suffix(t.args, prefix, suffix) + s_args = s_prefix + (TupleType(list(s_middle), fallback),) + s_suffix + t_args = t_prefix + (TupleType(list(t_middle), fallback),) + t_suffix + else: + t_args = t.args + s_args = s.args + for ta, sa, type_var in zip(t_args, s_args, t.type.defn.type_vars): ta_proper = get_proper_type(ta) sa_proper = get_proper_type(sa) new_type: Type | None = None @@ -77,7 +97,7 @@ def join_instances(self, t: Instance, s: Instance) -> ProperType: elif isinstance(sa_proper, AnyType): new_type = AnyType(TypeOfAny.from_another_any, sa_proper) elif isinstance(type_var, TypeVarType): - if type_var.variance == COVARIANT: + if type_var.variance in (COVARIANT, VARIANCE_NOT_READY): new_type = join_types(ta, sa, self) if len(type_var.values) != 0 and new_type not in type_var.values: self.seen_instances.pop() @@ -88,12 +108,29 @@ def join_instances(self, t: Instance, s: Instance) -> ProperType: # TODO: contravariant case should use meet but pass seen instances as # an argument to keep track of recursive checks. elif type_var.variance in (INVARIANT, CONTRAVARIANT): - if not is_equivalent(ta, sa): + if isinstance(ta_proper, UninhabitedType) and not ta_proper.is_noreturn: + new_type = sa + elif isinstance(sa_proper, UninhabitedType) and not sa_proper.is_noreturn: + new_type = ta + elif not is_equivalent(ta, sa): self.seen_instances.pop() return object_from_instance(t) - # If the types are different but equivalent, then an Any is involved - # so using a join in the contravariant case is also OK. - new_type = join_types(ta, sa, self) + else: + # If the types are different but equivalent, then an Any is involved + # so using a join in the contravariant case is also OK. + new_type = join_types(ta, sa, self) + elif isinstance(type_var, TypeVarTupleType): + new_type = get_proper_type(join_types(ta, sa, self)) + # Put the joined arguments back into instance in the normal form: + # a) Tuple[X, Y, Z] -> [X, Y, Z] + # b) tuple[X, ...] -> [*tuple[X, ...]] + if isinstance(new_type, Instance): + assert new_type.type.fullname == "builtins.tuple" + new_type = UnpackType(new_type) + else: + assert isinstance(new_type, TupleType) + args.extend(new_type.items) + continue else: # ParamSpec type variables behave the same, independent of variance if not is_equivalent(ta, sa): @@ -205,13 +242,11 @@ def trivial_join(s: Type, t: Type) -> Type: @overload def join_types( s: ProperType, t: ProperType, instance_joiner: InstanceJoiner | None = None -) -> ProperType: - ... +) -> ProperType: ... @overload -def join_types(s: Type, t: Type, instance_joiner: InstanceJoiner | None = None) -> Type: - ... +def join_types(s: Type, t: Type, instance_joiner: InstanceJoiner | None = None) -> Type: ... def join_types(s: Type, t: Type, instance_joiner: InstanceJoiner | None = None) -> Type: @@ -246,14 +281,6 @@ def join_types(s: Type, t: Type, instance_joiner: InstanceJoiner | None = None) if isinstance(s, UninhabitedType) and not isinstance(t, UninhabitedType): s, t = t, s - # We shouldn't run into PlaceholderTypes here, but in practice we can encounter them - # here in the presence of undefined names - if isinstance(t, PlaceholderType) and not isinstance(s, PlaceholderType): - # mypyc does not allow switching the values like above. - return s.accept(TypeJoinVisitor(t)) - elif isinstance(t, PlaceholderType): - return AnyType(TypeOfAny.from_error) - # Meets/joins require callable type normalization. s, t = normalize_callables(s, t) @@ -324,8 +351,17 @@ def visit_unpack_type(self, t: UnpackType) -> UnpackType: raise NotImplementedError def visit_parameters(self, t: Parameters) -> ProperType: - if self.s == t: - return t + if isinstance(self.s, Parameters): + if len(t.arg_types) != len(self.s.arg_types): + return self.default(self.s) + from mypy.meet import meet_types + + return t.copy_modified( + arg_types=[ + meet_types(s_a, t_a) for s_a, t_a in zip(self.s.arg_types, t.arg_types) + ], + arg_names=combine_arg_names(self.s, t), + ) else: return self.default(self.s) @@ -443,6 +479,113 @@ def visit_overloaded(self, t: Overloaded) -> ProperType: return join_types(t, call) return join_types(t.fallback, s) + def join_tuples(self, s: TupleType, t: TupleType) -> list[Type] | None: + """Join two tuple types while handling variadic entries. + + This is surprisingly tricky, and we don't handle some tricky corner cases. + Most of the trickiness comes from the variadic tuple items like *tuple[X, ...] + since they can have arbitrary partial overlaps (while *Ts can't be split). + """ + s_unpack_index = find_unpack_in_list(s.items) + t_unpack_index = find_unpack_in_list(t.items) + if s_unpack_index is None and t_unpack_index is None: + if s.length() == t.length(): + items: list[Type] = [] + for i in range(t.length()): + items.append(join_types(t.items[i], s.items[i])) + return items + return None + if s_unpack_index is not None and t_unpack_index is not None: + # The most complex case: both tuples have an upack item. + s_unpack = s.items[s_unpack_index] + assert isinstance(s_unpack, UnpackType) + s_unpacked = get_proper_type(s_unpack.type) + t_unpack = t.items[t_unpack_index] + assert isinstance(t_unpack, UnpackType) + t_unpacked = get_proper_type(t_unpack.type) + if s.length() == t.length() and s_unpack_index == t_unpack_index: + # We can handle a case where arity is perfectly aligned, e.g. + # join(Tuple[X1, *tuple[Y1, ...], Z1], Tuple[X2, *tuple[Y2, ...], Z2]). + # We can essentially perform the join elementwise. + prefix_len = t_unpack_index + suffix_len = t.length() - t_unpack_index - 1 + items = [] + for si, ti in zip(s.items[:prefix_len], t.items[:prefix_len]): + items.append(join_types(si, ti)) + joined = join_types(s_unpacked, t_unpacked) + if isinstance(joined, TypeVarTupleType): + items.append(UnpackType(joined)) + elif isinstance(joined, Instance) and joined.type.fullname == "builtins.tuple": + items.append(UnpackType(joined)) + else: + if isinstance(t_unpacked, Instance): + assert t_unpacked.type.fullname == "builtins.tuple" + tuple_instance = t_unpacked + else: + assert isinstance(t_unpacked, TypeVarTupleType) + tuple_instance = t_unpacked.tuple_fallback + items.append( + UnpackType( + tuple_instance.copy_modified( + args=[object_from_instance(tuple_instance)] + ) + ) + ) + if suffix_len: + for si, ti in zip(s.items[-suffix_len:], t.items[-suffix_len:]): + items.append(join_types(si, ti)) + return items + if s.length() == 1 or t.length() == 1: + # Another case we can handle is when one of tuple is purely variadic + # (i.e. a non-normalized form of tuple[X, ...]), in this case the join + # will be again purely variadic. + if not (isinstance(s_unpacked, Instance) and isinstance(t_unpacked, Instance)): + return None + assert s_unpacked.type.fullname == "builtins.tuple" + assert t_unpacked.type.fullname == "builtins.tuple" + mid_joined = join_types(s_unpacked.args[0], t_unpacked.args[0]) + t_other = [a for i, a in enumerate(t.items) if i != t_unpack_index] + s_other = [a for i, a in enumerate(s.items) if i != s_unpack_index] + other_joined = join_type_list(s_other + t_other) + mid_joined = join_types(mid_joined, other_joined) + return [UnpackType(s_unpacked.copy_modified(args=[mid_joined]))] + # TODO: are there other case we can handle (e.g. both prefix/suffix are shorter)? + return None + if s_unpack_index is not None: + variadic = s + unpack_index = s_unpack_index + fixed = t + else: + assert t_unpack_index is not None + variadic = t + unpack_index = t_unpack_index + fixed = s + # Case where one tuple has variadic item and the other one doesn't. The join will + # be variadic, since fixed tuple is a subtype of variadic, but not vice versa. + unpack = variadic.items[unpack_index] + assert isinstance(unpack, UnpackType) + unpacked = get_proper_type(unpack.type) + if not isinstance(unpacked, Instance): + return None + if fixed.length() < variadic.length() - 1: + # There are no non-trivial types that are supertype of both. + return None + prefix_len = unpack_index + suffix_len = variadic.length() - prefix_len - 1 + prefix, middle, suffix = split_with_prefix_and_suffix( + tuple(fixed.items), prefix_len, suffix_len + ) + items = [] + for fi, vi in zip(prefix, variadic.items[:prefix_len]): + items.append(join_types(fi, vi)) + mid_joined = join_type_list(list(middle)) + mid_joined = join_types(mid_joined, unpacked.args[0]) + items.append(UnpackType(unpacked.copy_modified(args=[mid_joined]))) + if suffix_len: + for fi, vi in zip(suffix, variadic.items[-suffix_len:]): + items.append(join_types(fi, vi)) + return items + def visit_tuple_type(self, t: TupleType) -> ProperType: # When given two fixed-length tuples: # * If they have the same length, join their subtypes item-wise: @@ -455,19 +598,22 @@ def visit_tuple_type(self, t: TupleType) -> ProperType: # Tuple[int, bool] + Tuple[bool, ...] becomes Tuple[int, ...] # * Joining with any Sequence also returns a Sequence: # Tuple[int, bool] + List[bool] becomes Sequence[int] - if isinstance(self.s, TupleType) and self.s.length() == t.length(): + if isinstance(self.s, TupleType): if self.instance_joiner is None: self.instance_joiner = InstanceJoiner() fallback = self.instance_joiner.join_instances( mypy.typeops.tuple_fallback(self.s), mypy.typeops.tuple_fallback(t) ) assert isinstance(fallback, Instance) - if self.s.length() == t.length(): - items: list[Type] = [] - for i in range(t.length()): - items.append(join_types(t.items[i], self.s.items[i])) + items = self.join_tuples(self.s, t) + if items is not None: return TupleType(items, fallback) else: + # TODO: should this be a default fallback behaviour like for meet? + if is_proper_subtype(self.s, t): + return t + if is_proper_subtype(t, self.s): + return self.s return fallback else: return join_types(self.s, mypy.typeops.tuple_fallback(t)) @@ -573,11 +719,9 @@ def is_similar_callables(t: CallableType, s: CallableType) -> bool: def join_similar_callables(t: CallableType, s: CallableType) -> CallableType: - from mypy.meet import meet_types - arg_types: list[Type] = [] for i in range(len(t.arg_types)): - arg_types.append(meet_types(t.arg_types[i], s.arg_types[i])) + arg_types.append(safe_meet(t.arg_types[i], s.arg_types[i])) # TODO in combine_similar_callables also applies here (names and kinds; user metaclasses) # The fallback type can be either 'function', 'type', or some user-provided metaclass. # The result should always use 'function' as a fallback if either operands are using it. @@ -594,10 +738,42 @@ def join_similar_callables(t: CallableType, s: CallableType) -> CallableType: ) +def safe_join(t: Type, s: Type) -> Type: + # This is a temporary solution to prevent crashes in combine_similar_callables() etc., + # until relevant TODOs on handling arg_kinds will be addressed there. + if not isinstance(t, UnpackType) and not isinstance(s, UnpackType): + return join_types(t, s) + if isinstance(t, UnpackType) and isinstance(s, UnpackType): + return UnpackType(join_types(t.type, s.type)) + return object_or_any_from_type(get_proper_type(t)) + + +def safe_meet(t: Type, s: Type) -> Type: + # Similar to above but for meet_types(). + from mypy.meet import meet_types + + if not isinstance(t, UnpackType) and not isinstance(s, UnpackType): + return meet_types(t, s) + if isinstance(t, UnpackType) and isinstance(s, UnpackType): + unpacked = get_proper_type(t.type) + if isinstance(unpacked, TypeVarTupleType): + fallback_type = unpacked.tuple_fallback.type + elif isinstance(unpacked, TupleType): + fallback_type = unpacked.partial_fallback.type + else: + assert isinstance(unpacked, Instance) and unpacked.type.fullname == "builtins.tuple" + fallback_type = unpacked.type + res = meet_types(t.type, s.type) + if isinstance(res, UninhabitedType): + res = Instance(fallback_type, [res]) + return UnpackType(res) + return UninhabitedType() + + def combine_similar_callables(t: CallableType, s: CallableType) -> CallableType: arg_types: list[Type] = [] for i in range(len(t.arg_types)): - arg_types.append(join_types(t.arg_types[i], s.arg_types[i])) + arg_types.append(safe_join(t.arg_types[i], s.arg_types[i])) # TODO kinds and argument names # TODO what should happen if one fallback is 'type' and the other is a user-provided metaclass? # The fallback type can be either 'function', 'type', or some user-provided metaclass. @@ -615,7 +791,9 @@ def combine_similar_callables(t: CallableType, s: CallableType) -> CallableType: ) -def combine_arg_names(t: CallableType, s: CallableType) -> list[str | None]: +def combine_arg_names( + t: CallableType | Parameters, s: CallableType | Parameters +) -> list[str | None]: """Produces a list of argument names compatible with both callables. For example, suppose 't' and 's' have the following signatures: @@ -662,7 +840,7 @@ def object_or_any_from_type(typ: ProperType) -> ProperType: return object_from_instance(typ.partial_fallback) elif isinstance(typ, TypeType): return object_or_any_from_type(typ.item) - elif isinstance(typ, TypeVarType) and isinstance(typ.upper_bound, ProperType): + elif isinstance(typ, TypeVarLikeType) and isinstance(typ.upper_bound, ProperType): return object_or_any_from_type(typ.upper_bound) elif isinstance(typ, UnionType): for item in typ.items: @@ -670,6 +848,8 @@ def object_or_any_from_type(typ: ProperType) -> ProperType: candidate = object_or_any_from_type(item) if isinstance(candidate, Instance): return candidate + elif isinstance(typ, UnpackType): + object_or_any_from_type(get_proper_type(typ.type)) return AnyType(TypeOfAny.implementation_artifact) diff --git a/mypy/literals.py b/mypy/literals.py index 9d91cf728b06..cba5712644be 100644 --- a/mypy/literals.py +++ b/mypy/literals.py @@ -1,7 +1,7 @@ from __future__ import annotations -from typing import Any, Iterable, Optional, Tuple -from typing_extensions import Final, TypeAlias as _TypeAlias +from typing import Any, Final, Iterable, Optional, Tuple +from typing_extensions import TypeAlias as _TypeAlias from mypy.nodes import ( LITERAL_NO, @@ -139,6 +139,16 @@ def literal_hash(e: Expression) -> Key | None: return e.accept(_hasher) +def extract_var_from_literal_hash(key: Key) -> Var | None: + """If key refers to a Var node, return it. + + Return None otherwise. + """ + if len(key) == 2 and key[0] == "Var" and isinstance(key[1], Var): + return key[1] + return None + + class _Hasher(ExpressionVisitor[Optional[Key]]): def visit_int_expr(self, e: IntExpr) -> Key: return ("Literal", e.value) diff --git a/mypy/main.py b/mypy/main.py index 47dea2ae9797..489ef8fd9a7b 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -7,18 +7,24 @@ import subprocess import sys import time +from collections import defaultdict from gettext import gettext -from typing import IO, Any, NoReturn, Sequence, TextIO -from typing_extensions import Final +from typing import IO, Any, Final, NoReturn, Sequence, TextIO from mypy import build, defaults, state, util -from mypy.config_parser import get_config_module_names, parse_config_file, parse_version +from mypy.config_parser import ( + get_config_module_names, + parse_config_file, + parse_version, + validate_package_allow_list, +) +from mypy.error_formatter import OUTPUT_CHOICES from mypy.errorcodes import error_codes from mypy.errors import CompileError from mypy.find_sources import InvalidSourceList, create_source_list from mypy.fscache import FileSystemCache from mypy.modulefinder import BuildSource, FindModuleCache, SearchPaths, get_search_dirs, mypy_path -from mypy.options import INCOMPLETE_FEATURES, BuildType, Options +from mypy.options import COMPLETE_FEATURES, INCOMPLETE_FEATURES, BuildType, Options from mypy.split_namespace import SplitNamespace from mypy.version import __version__ @@ -29,7 +35,7 @@ def stat_proxy(path: str) -> os.stat_result: try: st = orig_stat(path) - except os.error as err: + except OSError as err: print(f"stat({path!r}) -> {err}") raise else: @@ -67,7 +73,9 @@ def main( if clean_exit: options.fast_exit = False - formatter = util.FancyFormatter(stdout, stderr, options.hide_error_codes) + formatter = util.FancyFormatter( + stdout, stderr, options.hide_error_codes, hide_success=bool(options.output) + ) if options.install_types and (stdout is not sys.stdout or stderr is not sys.stderr): # Since --install-types performs user input, we want regular stdout and stderr. @@ -140,7 +148,7 @@ def main( sys.exit(code) # HACK: keep res alive so that mypyc won't free it before the hard_exit - list([res]) + list([res]) # noqa: C410 def run_build( @@ -151,14 +159,19 @@ def run_build( stdout: TextIO, stderr: TextIO, ) -> tuple[build.BuildResult | None, list[str], bool]: - formatter = util.FancyFormatter(stdout, stderr, options.hide_error_codes) + formatter = util.FancyFormatter( + stdout, stderr, options.hide_error_codes, hide_success=bool(options.output) + ) messages = [] + messages_by_file = defaultdict(list) - def flush_errors(new_messages: list[str], serious: bool) -> None: + def flush_errors(filename: str | None, new_messages: list[str], serious: bool) -> None: if options.pretty: new_messages = formatter.fit_in_terminal(new_messages) messages.extend(new_messages) + if new_messages: + messages_by_file[filename].extend(new_messages) if options.non_interactive: # Collect messages and possibly show them later. return @@ -183,8 +196,7 @@ def flush_errors(new_messages: list[str], serious: bool) -> None: and not options.non_interactive ): print( - "Warning: unused section(s) in %s: %s" - % ( + "Warning: unused section(s) in {}: {}".format( options.config_file, get_config_module_names( options.config_file, @@ -197,7 +209,7 @@ def flush_errors(new_messages: list[str], serious: bool) -> None: ), file=stderr, ) - maybe_write_junit_xml(time.time() - t0, serious, messages, options) + maybe_write_junit_xml(time.time() - t0, serious, messages, messages_by_file, options) return res, messages, blockers @@ -335,14 +347,13 @@ def infer_python_executable(options: Options, special_opts: argparse.Namespace) class CapturableArgumentParser(argparse.ArgumentParser): - """Override ArgumentParser methods that use sys.stdout/sys.stderr directly. This is needed because hijacking sys.std* is not thread-safe, yet output must be captured to properly support mypy.api.run. """ - def __init__(self, *args: Any, **kwargs: Any): + def __init__(self, *args: Any, **kwargs: Any) -> None: self.stdout = kwargs.pop("stdout", sys.stdout) self.stderr = kwargs.pop("stderr", sys.stderr) super().__init__(*args, **kwargs) @@ -389,7 +400,6 @@ def error(self, message: str) -> NoReturn: class CapturableVersionAction(argparse.Action): - """Supplement CapturableArgumentParser to handle --version. This is nearly identical to argparse._VersionAction except, @@ -408,7 +418,7 @@ def __init__( default: str = argparse.SUPPRESS, help: str = "show program's version number and exit", stdout: IO[str] | None = None, - ): + ) -> None: super().__init__( option_strings=option_strings, dest=dest, default=default, nargs=0, help=help ) @@ -520,6 +530,14 @@ def add_invertible_flag( stdout=stdout, ) + general_group.add_argument( + "-O", + "--output", + metavar="FORMAT", + help="Set a custom output format", + choices=OUTPUT_CHOICES, + ) + config_group = parser.add_argument_group( title="Config file", description="Use a config file instead of command line arguments. " @@ -595,14 +613,6 @@ def add_invertible_flag( help="Type check code assuming it will be running on Python x.y", dest="special-opts:python_version", ) - platform_group.add_argument( - "-2", - "--py2", - dest="special-opts:python_version", - action="store_const", - const=defaults.PYTHON2_VERSION, - help="Use Python 2 mode (same as --python-version 2.7)", - ) platform_group.add_argument( "--platform", action="store", @@ -684,6 +694,14 @@ def add_invertible_flag( " from functions with type annotations", group=untyped_group, ) + untyped_group.add_argument( + "--untyped-calls-exclude", + metavar="MODULE", + action="append", + default=[], + help="Disable --disallow-untyped-calls for functions/methods coming" + " from specific package, module, or class", + ) add_invertible_flag( "--disallow-untyped-defs", default=False, @@ -696,7 +714,8 @@ def add_invertible_flag( "--disallow-incomplete-defs", default=False, strict_flag=True, - help="Disallow defining functions with incomplete type annotations", + help="Disallow defining functions with incomplete type annotations " + "(while still allowing entirely unannotated definitions)", group=untyped_group, ) add_invertible_flag( @@ -734,6 +753,14 @@ def add_invertible_flag( help="Disable strict Optional checks (inverse: --strict-optional)", ) + add_invertible_flag( + "--force-uppercase-builtins", default=False, help=argparse.SUPPRESS, group=none_group + ) + + add_invertible_flag( + "--force-union-syntax", default=False, help=argparse.SUPPRESS, group=none_group + ) + lint_group = parser.add_argument_group( title="Configuring warnings", description="Detect code that is sound but redundant or problematic.", @@ -817,10 +844,12 @@ def add_invertible_flag( ) add_invertible_flag( - "--strict-concatenate", + "--extra-checks", default=False, strict_flag=True, - help="Make arguments prepended via Concatenate be truly positional-only", + help="Enable additional checks that are technically correct but may be impractical " + "in real code. For example, this prohibits partial overlap in TypedDict updates, " + "and makes arguments prepended via Concatenate positional-only", group=strictness_group, ) @@ -876,6 +905,12 @@ def add_invertible_flag( help="Hide error codes in error messages", group=error_group, ) + add_invertible_flag( + "--show-error-code-links", + default=False, + help="Show links to error code documentation", + group=error_group, + ) add_invertible_flag( "--pretty", default=False, @@ -975,18 +1010,18 @@ def add_invertible_flag( help="Use a custom typing module", ) internals_group.add_argument( - "--disable-recursive-aliases", + "--old-type-inference", action="store_true", - help="Disable experimental support for recursive type aliases", + help="Disable new experimental type inference algorithm", ) # Deprecated reverse variant of the above. internals_group.add_argument( - "--enable-recursive-aliases", action="store_true", help=argparse.SUPPRESS + "--new-type-inference", action="store_true", help=argparse.SUPPRESS ) parser.add_argument( "--enable-incomplete-feature", action="append", - metavar="FEATURE", + metavar="{" + ",".join(sorted(INCOMPLETE_FEATURES)) + "}", help="Enable support of incomplete/experimental features for early preview", ) internals_group.add_argument( @@ -1017,6 +1052,8 @@ def add_invertible_flag( add_invertible_flag( "--allow-empty-bodies", default=False, help=argparse.SUPPRESS, group=internals_group ) + # This undocumented feature exports limited line-level dependency information. + internals_group.add_argument("--export-ref-info", action="store_true", help=argparse.SUPPRESS) report_group = parser.add_argument_group( title="Report generation", description="Generate a report in the specified format." @@ -1032,6 +1069,12 @@ def add_invertible_flag( other_group = parser.add_argument_group(title="Miscellaneous") other_group.add_argument("--quickstart-file", help=argparse.SUPPRESS) other_group.add_argument("--junit-xml", help="Write junit.xml to the given file") + imports_group.add_argument( + "--junit-format", + choices=["global", "per_file"], + default="global", + help="If --junit-xml is set, specifies format. global: single test with all errors; per_file: one test entry per file with failures", + ) other_group.add_argument( "--find-occurrences", metavar="CLASS.MEMBER", @@ -1103,6 +1146,8 @@ def add_invertible_flag( parser.add_argument("--dump-graph", action="store_true", help=argparse.SUPPRESS) # --semantic-analysis-only does exactly that. parser.add_argument("--semantic-analysis-only", action="store_true", help=argparse.SUPPRESS) + # Some tests use this to tell mypy that we are running a test. + parser.add_argument("--test-env", action="store_true", help=argparse.SUPPRESS) # --local-partial-types disallows partial types spanning module top level and a function # (implicitly defined in fine-grained incremental mode) parser.add_argument("--local-partial-types", action="store_true", help=argparse.SUPPRESS) @@ -1129,16 +1174,15 @@ def add_invertible_flag( # --debug-serialize will run tree.serialize() even if cache generation is disabled. # Useful for mypy_primer to detect serialize errors earlier. parser.add_argument("--debug-serialize", action="store_true", help=argparse.SUPPRESS) - # This one is deprecated, but we will keep it for few releases. - parser.add_argument( - "--enable-incomplete-features", action="store_true", help=argparse.SUPPRESS - ) + parser.add_argument( "--disable-bytearray-promotion", action="store_true", help=argparse.SUPPRESS ) parser.add_argument( "--disable-memoryview-promotion", action="store_true", help=argparse.SUPPRESS ) + # This flag is deprecated, it has been moved to --extra-checks + parser.add_argument("--strict-concatenate", action="store_true", help=argparse.SUPPRESS) # options specifying code to check code_group = parser.add_argument_group( @@ -1210,8 +1254,11 @@ def add_invertible_flag( parser.error(f"Cannot find config file '{config_file}'") options = Options() + strict_option_set = False def set_strict_flags() -> None: + nonlocal strict_option_set + strict_option_set = True for dest, value in strict_flag_assignments: setattr(options, dest, value) @@ -1287,6 +1334,8 @@ def set_strict_flags() -> None: % ", ".join(sorted(overlap)) ) + validate_package_allow_list(options.untyped_calls_exclude) + # Process `--enable-error-code` and `--disable-error-code` flags disabled_codes = set(options.disable_error_code) enabled_codes = set(options.enable_error_code) @@ -1305,14 +1354,10 @@ def set_strict_flags() -> None: # Validate incomplete features. for feature in options.enable_incomplete_feature: - if feature not in INCOMPLETE_FEATURES: + if feature not in INCOMPLETE_FEATURES | COMPLETE_FEATURES: parser.error(f"Unknown incomplete feature: {feature}") - if options.enable_incomplete_features: - print( - "Warning: --enable-incomplete-features is deprecated, use" - " --enable-incomplete-feature=FEATURE instead" - ) - options.enable_incomplete_feature = list(INCOMPLETE_FEATURES) + if feature in COMPLETE_FEATURES: + print(f"Warning: {feature} is already enabled by default") # Compute absolute path for custom typeshed (if present). if options.custom_typeshed_dir is not None: @@ -1320,12 +1365,12 @@ def set_strict_flags() -> None: # Set build flags. if special_opts.find_occurrences: - state.find_occurrences = special_opts.find_occurrences.split(".") - assert state.find_occurrences is not None - if len(state.find_occurrences) < 2: + _find_occurrences = tuple(special_opts.find_occurrences.split(".")) + if len(_find_occurrences) < 2: parser.error("Can only find occurrences of class members.") - if len(state.find_occurrences) != 2: + if len(_find_occurrences) != 2: parser.error("Can only find occurrences of non-nested class members.") + state.find_occurrences = _find_occurrences # Set reports. for flag, val in vars(special_opts).items(): @@ -1358,12 +1403,15 @@ def set_strict_flags() -> None: if options.logical_deps: options.cache_fine_grained = True - if options.enable_recursive_aliases: + if options.new_type_inference: print( - "Warning: --enable-recursive-aliases is deprecated;" - " recursive types are enabled by default" + "Warning: --new-type-inference flag is deprecated;" + " new type inference algorithm is already enabled by default" ) + if options.strict_concatenate and not strict_option_set: + print("Warning: --strict-concatenate is deprecated; use --extra-checks instead") + # Set target. if special_opts.modules + special_opts.packages: options.build_type = BuildType.MODULE @@ -1458,18 +1506,37 @@ def process_cache_map( options.cache_map[source] = (meta_file, data_file) -def maybe_write_junit_xml(td: float, serious: bool, messages: list[str], options: Options) -> None: +def maybe_write_junit_xml( + td: float, + serious: bool, + all_messages: list[str], + messages_by_file: dict[str | None, list[str]], + options: Options, +) -> None: if options.junit_xml: py_version = f"{options.python_version[0]}_{options.python_version[1]}" - util.write_junit_xml( - td, serious, messages, options.junit_xml, py_version, options.platform - ) + if options.junit_format == "global": + util.write_junit_xml( + td, + serious, + {None: all_messages} if all_messages else {}, + options.junit_xml, + py_version, + options.platform, + ) + else: + # per_file + util.write_junit_xml( + td, serious, messages_by_file, options.junit_xml, py_version, options.platform + ) def fail(msg: str, stderr: TextIO, options: Options) -> NoReturn: """Fail with a serious error.""" stderr.write(f"{msg}\n") - maybe_write_junit_xml(0.0, serious=True, messages=[msg], options=options) + maybe_write_junit_xml( + 0.0, serious=True, all_messages=[msg], messages_by_file={None: [msg]}, options=options + ) sys.exit(2) @@ -1488,7 +1555,7 @@ def read_types_packages_to_install(cache_dir: str, after_run: bool) -> list[str] # No missing stubs. return [] with open(fnam) as f: - return [line.strip() for line in f.readlines()] + return [line.strip() for line in f] def install_types( diff --git a/mypy/maptype.py b/mypy/maptype.py index cae904469fed..59ecb2bc9993 100644 --- a/mypy/maptype.py +++ b/mypy/maptype.py @@ -1,8 +1,8 @@ from __future__ import annotations -from mypy.expandtype import expand_type +from mypy.expandtype import expand_type_by_instance from mypy.nodes import TypeInfo -from mypy.types import AnyType, Instance, TupleType, Type, TypeOfAny, TypeVarId, has_type_vars +from mypy.types import AnyType, Instance, TupleType, TypeOfAny, has_type_vars def map_instance_to_supertype(instance: Instance, superclass: TypeInfo) -> Instance: @@ -25,13 +25,15 @@ def map_instance_to_supertype(instance: Instance, superclass: TypeInfo) -> Insta if not alias._is_recursive: # Unfortunately we can't support this for generic recursive tuples. # If we skip this special casing we will fall back to tuple[Any, ...]. - env = instance_to_type_environment(instance) - tuple_type = expand_type(instance.type.tuple_type, env) + tuple_type = expand_type_by_instance(instance.type.tuple_type, instance) if isinstance(tuple_type, TupleType): # Make the import here to avoid cyclic imports. import mypy.typeops return mypy.typeops.tuple_fallback(tuple_type) + elif isinstance(tuple_type, Instance): + # This can happen after normalizing variadic tuples. + return tuple_type if not superclass.type_vars: # Fast path: `superclass` has no type variables to map to. @@ -91,8 +93,7 @@ def map_instance_to_direct_supertypes(instance: Instance, supertype: TypeInfo) - for b in typ.bases: if b.type == supertype: - env = instance_to_type_environment(instance) - t = expand_type(b, env) + t = expand_type_by_instance(b, instance) assert isinstance(t, Instance) result.append(t) @@ -103,16 +104,3 @@ def map_instance_to_direct_supertypes(instance: Instance, supertype: TypeInfo) - # type arguments implicitly. any_type = AnyType(TypeOfAny.unannotated) return [Instance(supertype, [any_type] * len(supertype.type_vars))] - - -def instance_to_type_environment(instance: Instance) -> dict[TypeVarId, Type]: - """Given an Instance, produce the resulting type environment for type - variables bound by the Instance's class definition. - - An Instance is a type application of a class (a TypeInfo) to its - required number of type arguments. So this environment consists - of the class's type variables mapped to the Instance's actual - arguments. The type variables are mapped by their `id`. - - """ - return {binder.id: arg for binder, arg in zip(instance.type.defn.type_vars, instance.args)} diff --git a/mypy/meet.py b/mypy/meet.py index d99e1a92d2eb..df8b960cdf3f 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -16,6 +16,7 @@ from mypy.typeops import is_recursive_pair, make_simplified_union, tuple_fallback from mypy.types import ( MYPYC_NATIVE_INT_NAMES, + TUPLE_LIKE_INSTANCE_NAMES, AnyType, CallableType, DeletedType, @@ -44,8 +45,10 @@ UninhabitedType, UnionType, UnpackType, + find_unpack_in_list, get_proper_type, get_proper_types, + split_with_prefix_and_suffix, ) # TODO Describe this module. @@ -219,6 +222,8 @@ def get_possible_variants(typ: Type) -> list[Type]: return [typ.upper_bound] elif isinstance(typ, ParamSpecType): return [typ.upper_bound] + elif isinstance(typ, TypeVarTupleType): + return [typ.upper_bound] elif isinstance(typ, UnionType): return list(typ.items) elif isinstance(typ, Overloaded): @@ -257,6 +262,7 @@ def is_overlapping_types( ignore_promotions: bool = False, prohibit_none_typevar_overlap: bool = False, ignore_uninhabited: bool = False, + seen_types: set[tuple[Type, Type]] | None = None, ) -> bool: """Can a value of type 'left' also be of type 'right' or vice-versa? @@ -270,18 +276,27 @@ def is_overlapping_types( # A type guard forces the new type even if it doesn't overlap the old. return True + if seen_types is None: + seen_types = set() + if (left, right) in seen_types: + return True + if isinstance(left, TypeAliasType) and isinstance(right, TypeAliasType): + seen_types.add((left, right)) + left, right = get_proper_types((left, right)) def _is_overlapping_types(left: Type, right: Type) -> bool: """Encode the kind of overlapping check to perform. - This function mostly exists so we don't have to repeat keyword arguments everywhere.""" + This function mostly exists, so we don't have to repeat keyword arguments everywhere. + """ return is_overlapping_types( left, right, ignore_promotions=ignore_promotions, prohibit_none_typevar_overlap=prohibit_none_typevar_overlap, ignore_uninhabited=ignore_uninhabited, + seen_types=seen_types.copy(), ) # We should never encounter this type. @@ -342,7 +357,22 @@ def _is_overlapping_types(left: Type, right: Type) -> bool: left_possible = get_possible_variants(left) right_possible = get_possible_variants(right) - # We start by checking multi-variant types like Unions first. We also perform + # First handle special cases relating to PEP 612: + # - comparing a `Parameters` to a `Parameters` + # - comparing a `Parameters` to a `ParamSpecType` + # - comparing a `ParamSpecType` to a `ParamSpecType` + # + # These should all always be considered overlapping equality checks. + # These need to be done before we move on to other TypeVarLike comparisons. + if isinstance(left, (Parameters, ParamSpecType)) and isinstance( + right, (Parameters, ParamSpecType) + ): + return True + # A `Parameters` does not overlap with anything else, however + if isinstance(left, Parameters) or isinstance(right, Parameters): + return False + + # Now move on to checking multi-variant types like Unions. We also perform # the same logic if either type happens to be a TypeVar/ParamSpec/TypeVarTuple. # # Handling the TypeVarLikes now lets us simulate having them bind to the corresponding @@ -443,6 +473,7 @@ def _type_object_overlap(left: Type, right: Type) -> bool: left, right, is_compat=_is_overlapping_types, + is_proper_subtype=False, ignore_pos_arg_names=True, allow_partial_overlap=True, ) @@ -677,8 +708,8 @@ def visit_param_spec(self, t: ParamSpecType) -> ProperType: return self.default(self.s) def visit_type_var_tuple(self, t: TypeVarTupleType) -> ProperType: - if self.s == t: - return self.s + if isinstance(self.s, TypeVarTupleType) and self.s.id == t.id: + return self.s if self.s.min_len > t.min_len else t else: return self.default(self.s) @@ -686,12 +717,13 @@ def visit_unpack_type(self, t: UnpackType) -> ProperType: raise NotImplementedError def visit_parameters(self, t: Parameters) -> ProperType: - # TODO: is this the right variance? - if isinstance(self.s, Parameters) or isinstance(self.s, CallableType): + if isinstance(self.s, Parameters): if len(t.arg_types) != len(self.s.arg_types): return self.default(self.s) + from mypy.join import join_types + return t.copy_modified( - arg_types=[meet_types(s_a, t_a) for s_a, t_a in zip(self.s.arg_types, t.arg_types)] + arg_types=[join_types(s_a, t_a) for s_a, t_a in zip(self.s.arg_types, t.arg_types)] ) else: return self.default(self.s) @@ -705,8 +737,41 @@ def visit_instance(self, t: Instance) -> ProperType: args: list[Type] = [] # N.B: We use zip instead of indexing because the lengths might have # mismatches during daemon reprocessing. - for ta, sia in zip(t.args, self.s.args): - args.append(self.meet(ta, sia)) + if t.type.has_type_var_tuple_type: + # We handle meet of variadic instances by simply creating correct mapping + # for type arguments and compute the individual meets same as for regular + # instances. All the heavy lifting is done in the meet of tuple types. + s = self.s + assert s.type.type_var_tuple_prefix is not None + assert s.type.type_var_tuple_suffix is not None + prefix = s.type.type_var_tuple_prefix + suffix = s.type.type_var_tuple_suffix + tvt = s.type.defn.type_vars[prefix] + assert isinstance(tvt, TypeVarTupleType) + fallback = tvt.tuple_fallback + s_prefix, s_middle, s_suffix = split_with_prefix_and_suffix( + s.args, prefix, suffix + ) + t_prefix, t_middle, t_suffix = split_with_prefix_and_suffix( + t.args, prefix, suffix + ) + s_args = s_prefix + (TupleType(list(s_middle), fallback),) + s_suffix + t_args = t_prefix + (TupleType(list(t_middle), fallback),) + t_suffix + else: + t_args = t.args + s_args = self.s.args + for ta, sa, tv in zip(t_args, s_args, t.type.defn.type_vars): + meet = self.meet(ta, sa) + if isinstance(tv, TypeVarTupleType): + # Correctly unpack possible outcomes of meets of tuples: it can be + # either another tuple type or Never (normalized as *tuple[Never, ...]) + if isinstance(meet, TupleType): + args.extend(meet.items) + continue + else: + assert isinstance(meet, UninhabitedType) + meet = UnpackType(tv.tuple_fallback.copy_modified(args=[meet])) + args.append(meet) return Instance(t.type, args) else: if state.strict_optional: @@ -795,31 +860,113 @@ def visit_overloaded(self, t: Overloaded) -> ProperType: return meet_types(t, call) return meet_types(t.fallback, s) + def meet_tuples(self, s: TupleType, t: TupleType) -> list[Type] | None: + """Meet two tuple types while handling variadic entries. + + This is surprisingly tricky, and we don't handle some tricky corner cases. + Most of the trickiness comes from the variadic tuple items like *tuple[X, ...] + since they can have arbitrary partial overlaps (while *Ts can't be split). This + function is roughly a mirror of join_tuples() w.r.t. to the fact that fixed + tuples are subtypes of variadic ones but not vice versa. + """ + s_unpack_index = find_unpack_in_list(s.items) + t_unpack_index = find_unpack_in_list(t.items) + if s_unpack_index is None and t_unpack_index is None: + if s.length() == t.length(): + items: list[Type] = [] + for i in range(t.length()): + items.append(self.meet(t.items[i], s.items[i])) + return items + return None + if s_unpack_index is not None and t_unpack_index is not None: + # The only simple case we can handle if both tuples are variadic + # is when their structure fully matches. Other cases are tricky because + # a variadic item is effectively a union of tuples of all length, thus + # potentially causing overlap between a suffix in `s` and a prefix + # in `t` (see how this is handled in is_subtype() for details). + # TODO: handle more cases (like when both prefix/suffix are shorter in s or t). + if s.length() == t.length() and s_unpack_index == t_unpack_index: + unpack_index = s_unpack_index + s_unpack = s.items[unpack_index] + assert isinstance(s_unpack, UnpackType) + s_unpacked = get_proper_type(s_unpack.type) + t_unpack = t.items[unpack_index] + assert isinstance(t_unpack, UnpackType) + t_unpacked = get_proper_type(t_unpack.type) + if not (isinstance(s_unpacked, Instance) and isinstance(t_unpacked, Instance)): + return None + meet = self.meet(s_unpacked, t_unpacked) + if not isinstance(meet, Instance): + return None + m_prefix: list[Type] = [] + for si, ti in zip(s.items[:unpack_index], t.items[:unpack_index]): + m_prefix.append(meet_types(si, ti)) + m_suffix: list[Type] = [] + for si, ti in zip(s.items[unpack_index + 1 :], t.items[unpack_index + 1 :]): + m_suffix.append(meet_types(si, ti)) + return m_prefix + [UnpackType(meet)] + m_suffix + return None + if s_unpack_index is not None: + variadic = s + unpack_index = s_unpack_index + fixed = t + else: + assert t_unpack_index is not None + variadic = t + unpack_index = t_unpack_index + fixed = s + # If one tuple is variadic one, and the other one is fixed, the meet will be fixed. + unpack = variadic.items[unpack_index] + assert isinstance(unpack, UnpackType) + unpacked = get_proper_type(unpack.type) + if not isinstance(unpacked, Instance): + return None + if fixed.length() < variadic.length() - 1: + return None + prefix_len = unpack_index + suffix_len = variadic.length() - prefix_len - 1 + prefix, middle, suffix = split_with_prefix_and_suffix( + tuple(fixed.items), prefix_len, suffix_len + ) + items = [] + for fi, vi in zip(prefix, variadic.items[:prefix_len]): + items.append(self.meet(fi, vi)) + for mi in middle: + items.append(self.meet(mi, unpacked.args[0])) + if suffix_len: + for fi, vi in zip(suffix, variadic.items[-suffix_len:]): + items.append(self.meet(fi, vi)) + return items + def visit_tuple_type(self, t: TupleType) -> ProperType: - if isinstance(self.s, TupleType) and self.s.length() == t.length(): - items: list[Type] = [] - for i in range(t.length()): - items.append(self.meet(t.items[i], self.s.items[i])) + if isinstance(self.s, TupleType): + items = self.meet_tuples(self.s, t) + if items is None: + return self.default(self.s) # TODO: What if the fallbacks are different? return TupleType(items, tuple_fallback(t)) elif isinstance(self.s, Instance): # meet(Tuple[t1, t2, <...>], Tuple[s, ...]) == Tuple[meet(t1, s), meet(t2, s), <...>]. - if self.s.type.fullname == "builtins.tuple" and self.s.args: + if self.s.type.fullname in TUPLE_LIKE_INSTANCE_NAMES and self.s.args: return t.copy_modified(items=[meet_types(it, self.s.args[0]) for it in t.items]) elif is_proper_subtype(t, self.s): # A named tuple that inherits from a normal class return t + elif self.s.type.has_type_var_tuple_type and is_subtype(t, self.s): + # This is a bit ad-hoc but more principled handling is tricky, and this + # special case is important for type narrowing in binder to work. + return t return self.default(self.s) def visit_typeddict_type(self, t: TypedDictType) -> ProperType: if isinstance(self.s, TypedDictType): - for (name, l, r) in self.s.zip(t): + for name, l, r in self.s.zip(t): if not is_equivalent(l, r) or (name in t.required_keys) != ( name in self.s.required_keys ): return self.default(self.s) item_list: list[tuple[str, Type]] = [] - for (item_name, s_item_type, t_item_type) in self.s.zipall(t): + for item_name, s_item_type, t_item_type in self.s.zipall(t): if s_item_type is not None: item_list.append((item_name, s_item_type)) else: @@ -877,11 +1024,11 @@ def default(self, typ: Type) -> ProperType: def meet_similar_callables(t: CallableType, s: CallableType) -> CallableType: - from mypy.join import join_types + from mypy.join import safe_join arg_types: list[Type] = [] for i in range(len(t.arg_types)): - arg_types.append(join_types(t.arg_types[i], s.arg_types[i])) + arg_types.append(safe_join(t.arg_types[i], s.arg_types[i])) # TODO in combine_similar_callables also applies here (names and kinds) # The fallback type can be either 'function' or 'type'. The result should have 'function' as # fallback only if both operands have it as 'function'. @@ -952,11 +1099,11 @@ def typed_dict_mapping_overlap( As usual empty, dictionaries lie in a gray area. In general, List[str] and List[str] are considered non-overlapping despite empty list belongs to both. However, List[int] - and List[] are considered overlapping. + and List[Never] are considered overlapping. So here we follow the same logic: a TypedDict with no required keys is considered non-overlapping with Mapping[str, ], but is considered overlapping with - Mapping[, ]. This way we avoid false positives for overloads, and also + Mapping[Never, Never]. This way we avoid false positives for overloads, and also avoid false positives for comparisons like SomeTypedDict == {} under --strict-equality. """ left, right = get_proper_types((left, right)) diff --git a/mypy/memprofile.py b/mypy/memprofile.py index 20e18c3c0bf2..48c0cb5ce022 100644 --- a/mypy/memprofile.py +++ b/mypy/memprofile.py @@ -103,7 +103,7 @@ def visit(o: object) -> None: objs.append(o) seen.add(id(o)) - for obj in objs[:]: + for obj in objs.copy(): if type(obj) is FakeInfo: # Processing these would cause a crash. continue diff --git a/mypy/message_registry.py b/mypy/message_registry.py index e00aca2869bd..ccc1443dacf0 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -8,8 +8,7 @@ from __future__ import annotations -from typing import NamedTuple -from typing_extensions import Final +from typing import Final, NamedTuple from mypy import errorcodes as codes @@ -43,7 +42,9 @@ def with_additional_msg(self, info: str) -> ErrorMessage: RETURN_VALUE_EXPECTED: Final = ErrorMessage("Return value expected", codes.RETURN_VALUE) NO_RETURN_EXPECTED: Final = ErrorMessage("Return statement in function which does not return") INVALID_EXCEPTION: Final = ErrorMessage("Exception must be derived from BaseException") -INVALID_EXCEPTION_TYPE: Final = ErrorMessage("Exception type must be derived from BaseException") +INVALID_EXCEPTION_TYPE: Final = ErrorMessage( + "Exception type must be derived from BaseException (or be a tuple of exception classes)" +) INVALID_EXCEPTION_GROUP: Final = ErrorMessage( "Exception type in except* cannot derive from BaseExceptionGroup" ) @@ -51,7 +52,7 @@ def with_additional_msg(self, info: str) -> ErrorMessage: '"return" with value in async generator is not allowed' ) INVALID_RETURN_TYPE_FOR_GENERATOR: Final = ErrorMessage( - 'The return type of a generator function should be "Generator"' " or one of its supertypes" + 'The return type of a generator function should be "Generator" or one of its supertypes' ) INVALID_RETURN_TYPE_FOR_ASYNC_GENERATOR: Final = ErrorMessage( 'The return type of an async generator function should be "AsyncGenerator" or one of its ' @@ -62,6 +63,9 @@ def with_additional_msg(self, info: str) -> ErrorMessage: INCOMPATIBLE_TYPES_IN_ASSIGNMENT: Final = ErrorMessage( "Incompatible types in assignment", code=codes.ASSIGNMENT ) +COVARIANT_OVERRIDE_OF_MUTABLE_ATTRIBUTE: Final = ErrorMessage( + "Covariant override of a mutable attribute", code=codes.MUTABLE_OVERRIDE +) INCOMPATIBLE_TYPES_IN_AWAIT: Final = ErrorMessage('Incompatible types in "await"') INCOMPATIBLE_REDEFINITION: Final = ErrorMessage("Incompatible redefinition") INCOMPATIBLE_TYPES_IN_ASYNC_WITH_AENTER: Final = ( @@ -82,7 +86,11 @@ def with_additional_msg(self, info: str) -> ErrorMessage: INCOMPATIBLE_TYPES_IN_CAPTURE: Final = ErrorMessage("Incompatible types in capture pattern") MUST_HAVE_NONE_RETURN_TYPE: Final = ErrorMessage('The return type of "{}" must be None') TUPLE_INDEX_OUT_OF_RANGE: Final = ErrorMessage("Tuple index out of range") -INVALID_SLICE_INDEX: Final = ErrorMessage("Slice index must be an integer or None") +AMBIGUOUS_SLICE_OF_VARIADIC_TUPLE: Final = ErrorMessage("Ambiguous slice of a variadic tuple") +TOO_MANY_TARGETS_FOR_VARIADIC_UNPACK: Final = ErrorMessage( + "Too many assignment targets for variadic unpack" +) +INVALID_SLICE_INDEX: Final = ErrorMessage("Slice index must be an integer, SupportsIndex or None") CANNOT_INFER_LAMBDA_TYPE: Final = ErrorMessage("Cannot infer type of lambda") CANNOT_ACCESS_INIT: Final = ( 'Accessing "__init__" on an instance is unsound, since instance.__init__ could be from' @@ -131,7 +139,7 @@ def with_additional_msg(self, info: str) -> ErrorMessage: "Expected TypedDict key to be string literal" ) MALFORMED_ASSERT: Final = ErrorMessage("Assertion is always true, perhaps remove parentheses?") -DUPLICATE_TYPE_SIGNATURES: Final = "Function has duplicate type signatures" +DUPLICATE_TYPE_SIGNATURES: Final = ErrorMessage("Function has duplicate type signatures") DESCRIPTOR_SET_NOT_CALLABLE: Final = ErrorMessage("{}.__set__ is not callable") DESCRIPTOR_GET_NOT_CALLABLE: Final = "{}.__get__ is not callable" MODULE_LEVEL_GETATTRIBUTE: Final = ErrorMessage( @@ -170,7 +178,8 @@ def with_additional_msg(self, info: str) -> ErrorMessage: IMPLICIT_GENERIC_ANY_BUILTIN: Final = ( 'Implicit generic "Any". Use "{}" and specify generic parameters' ) -INVALID_UNPACK = "{} cannot be unpacked (must be tuple or TypeVarTuple)" +INVALID_UNPACK: Final = "{} cannot be unpacked (must be tuple or TypeVarTuple)" +INVALID_UNPACK_POSITION: Final = "Unpack is only valid in a variadic position" # TypeVar INCOMPATIBLE_TYPEVAR_VALUE: Final = 'Value of type variable "{}" of {} cannot be {}' @@ -179,7 +188,7 @@ def with_additional_msg(self, info: str) -> ErrorMessage: INVALID_TYPEVAR_ARG_BOUND: Final = 'Type argument {} of "{}" must be a subtype of {}' INVALID_TYPEVAR_ARG_VALUE: Final = 'Invalid type argument value for "{}"' TYPEVAR_VARIANCE_DEF: Final = 'TypeVar "{}" may only be a literal bool' -TYPEVAR_BOUND_MUST_BE_TYPE: Final = 'TypeVar "bound" must be a type' +TYPEVAR_ARG_MUST_BE_TYPE: Final = '{} "{}" must be a type' TYPEVAR_UNEXPECTED_ARGUMENT: Final = 'Unexpected argument to "TypeVar()"' UNBOUND_TYPEVAR: Final = ( "A function returning TypeVar should receive at least " @@ -200,10 +209,10 @@ def with_additional_msg(self, info: str) -> ErrorMessage: ) TARGET_CLASS_HAS_NO_BASE_CLASS: Final = ErrorMessage("Target class has no base class") SUPER_OUTSIDE_OF_METHOD_NOT_SUPPORTED: Final = ErrorMessage( - "super() outside of a method is not supported" + '"super()" outside of a method is not supported' ) SUPER_ENCLOSING_POSITIONAL_ARGS_REQUIRED: Final = ErrorMessage( - "super() requires one or more positional arguments in enclosing function" + '"super()" requires one or two positional arguments in enclosing function' ) # Self-type @@ -253,7 +262,7 @@ def with_additional_msg(self, info: str) -> ErrorMessage: CONTIGUOUS_ITERABLE_EXPECTED: Final = ErrorMessage("Contiguous iterable with same type expected") ITERABLE_TYPE_EXPECTED: Final = ErrorMessage("Invalid type '{}' for *expr (iterable expected)") -TYPE_GUARD_POS_ARG_REQUIRED: Final = ErrorMessage("Type guard requires positional argument") +TYPE_GUARD_POS_ARG_REQUIRED: Final = ErrorMessage("Type {} requires positional argument") # Match Statement MISSING_MATCH_ARGS: Final = 'Class "{}" doesn\'t define "__match_args__"' @@ -268,9 +277,53 @@ def with_additional_msg(self, info: str) -> ErrorMessage: ) CLASS_PATTERN_DUPLICATE_KEYWORD_PATTERN: Final = 'Duplicate keyword pattern "{}"' CLASS_PATTERN_UNKNOWN_KEYWORD: Final = 'Class "{}" has no attribute "{}"' +CLASS_PATTERN_CLASS_OR_STATIC_METHOD: Final = "Cannot have both classmethod and staticmethod" MULTIPLE_ASSIGNMENTS_IN_PATTERN: Final = 'Multiple assignments to name "{}" in pattern' CANNOT_MODIFY_MATCH_ARGS: Final = 'Cannot assign to "__match_args__"' DATACLASS_FIELD_ALIAS_MUST_BE_LITERAL: Final = ( '"alias" argument to dataclass field must be a string literal' ) +DATACLASS_POST_INIT_MUST_BE_A_FUNCTION: Final = '"__post_init__" method must be an instance method' + +# fastparse +FAILED_TO_MERGE_OVERLOADS: Final = ErrorMessage( + "Condition can't be inferred, unable to merge overloads" +) +TYPE_IGNORE_WITH_ERRCODE_ON_MODULE: Final = ErrorMessage( + "type ignore with error code is not supported for modules; " + 'use `# mypy: disable-error-code="{}"`', + codes.SYNTAX, +) +INVALID_TYPE_IGNORE: Final = ErrorMessage('Invalid "type: ignore" comment', codes.SYNTAX) +TYPE_COMMENT_SYNTAX_ERROR_VALUE: Final = ErrorMessage( + 'Syntax error in type comment "{}"', codes.SYNTAX +) +ELLIPSIS_WITH_OTHER_TYPEARGS: Final = ErrorMessage( + "Ellipses cannot accompany other argument types in function type signature", codes.SYNTAX +) +TYPE_SIGNATURE_TOO_MANY_ARGS: Final = ErrorMessage( + "Type signature has too many arguments", codes.SYNTAX +) +TYPE_SIGNATURE_TOO_FEW_ARGS: Final = ErrorMessage( + "Type signature has too few arguments", codes.SYNTAX +) +ARG_CONSTRUCTOR_NAME_EXPECTED: Final = ErrorMessage("Expected arg constructor name", codes.SYNTAX) +ARG_CONSTRUCTOR_TOO_MANY_ARGS: Final = ErrorMessage( + "Too many arguments for argument constructor", codes.SYNTAX +) +MULTIPLE_VALUES_FOR_NAME_KWARG: Final = ErrorMessage( + '"{}" gets multiple values for keyword argument "name"', codes.SYNTAX +) +MULTIPLE_VALUES_FOR_TYPE_KWARG: Final = ErrorMessage( + '"{}" gets multiple values for keyword argument "type"', codes.SYNTAX +) +ARG_CONSTRUCTOR_UNEXPECTED_ARG: Final = ErrorMessage( + 'Unexpected argument "{}" for argument constructor', codes.SYNTAX +) +ARG_NAME_EXPECTED_STRING_LITERAL: Final = ErrorMessage( + "Expected string literal for argument name, got {}", codes.SYNTAX +) +NARROWED_TYPE_NOT_SUBTYPE: Final = ErrorMessage( + "Narrowed type {} is not a subtype of input type {}", codes.NARROWED_TYPE_NOT_SUBTYPE +) diff --git a/mypy/messages.py b/mypy/messages.py index ba2508033790..199b7c42b11b 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -16,9 +16,9 @@ import re from contextlib import contextmanager from textwrap import dedent -from typing import Any, Callable, Collection, Iterable, Iterator, List, Sequence, cast -from typing_extensions import Final +from typing import Any, Callable, Collection, Final, Iterable, Iterator, List, Sequence, cast +import mypy.typeops from mypy import errorcodes as codes, message_registry from mypy.erasetype import erase_type from mypy.errorcodes import ErrorCode @@ -51,6 +51,7 @@ reverse_builtin_aliases, ) from mypy.operators import op_methods, op_methods_to_symbols +from mypy.options import Options from mypy.subtypes import ( IS_CLASS_OR_STATIC, IS_CLASSVAR, @@ -80,6 +81,7 @@ TypeAliasType, TypedDictType, TypeOfAny, + TypeStrVisitor, TypeType, TypeVarTupleType, TypeVarType, @@ -134,6 +136,14 @@ "typing._SpecialForm": "typing-medium.pyi", } +UNSUPPORTED_NUMBERS_TYPES: Final = { + "numbers.Number", + "numbers.Complex", + "numbers.Real", + "numbers.Rational", + "numbers.Integral", +} + class MessageBuilder: """Helper class for reporting type checker error messages with parameters. @@ -157,6 +167,7 @@ class MessageBuilder: def __init__(self, errors: Errors, modules: dict[str, MypyFile]) -> None: self.errors = errors + self.options = errors.options self.modules = modules self._disable_type_names = [] @@ -223,6 +234,8 @@ def span_from_context(ctx: Context) -> Iterable[int]: Current logic is a bit tricky, to keep as much backwards compatibility as possible. We may reconsider this to always be a single line (or otherwise simplify it) when we drop Python 3.7. + + TODO: address this in follow up PR """ if isinstance(ctx, (ClassDef, FuncDef)): return range(ctx.deco_line or ctx.line, ctx.line + 1) @@ -342,7 +355,7 @@ def has_no_attr( member: str, context: Context, module_symbol_table: SymbolTable | None = None, - ) -> Type: + ) -> ErrorCode | None: """Report a missing or non-accessible member. original_type is the top-level type on which the error occurred. @@ -357,69 +370,77 @@ def has_no_attr( directly available on original_type If member corresponds to an operator, use the corresponding operator - name in the messages. Return type Any. + name in the messages. Return the error code that was produced, if any. """ original_type = get_proper_type(original_type) typ = get_proper_type(typ) if isinstance(original_type, Instance) and original_type.type.has_readable_member(member): self.fail(f'Member "{member}" is not assignable', context) + return None elif member == "__contains__": self.fail( - f"Unsupported right operand type for in ({format_type(original_type)})", + f"Unsupported right operand type for in ({format_type(original_type, self.options)})", context, code=codes.OPERATOR, ) + return codes.OPERATOR elif member in op_methods.values(): # Access to a binary operator member (e.g. _add). This case does # not handle indexing operations. for op, method in op_methods.items(): if method == member: self.unsupported_left_operand(op, original_type, context) - break + return codes.OPERATOR elif member == "__neg__": self.fail( - f"Unsupported operand type for unary - ({format_type(original_type)})", + f"Unsupported operand type for unary - ({format_type(original_type, self.options)})", context, code=codes.OPERATOR, ) + return codes.OPERATOR elif member == "__pos__": self.fail( - f"Unsupported operand type for unary + ({format_type(original_type)})", + f"Unsupported operand type for unary + ({format_type(original_type, self.options)})", context, code=codes.OPERATOR, ) + return codes.OPERATOR elif member == "__invert__": self.fail( - f"Unsupported operand type for ~ ({format_type(original_type)})", + f"Unsupported operand type for ~ ({format_type(original_type, self.options)})", context, code=codes.OPERATOR, ) + return codes.OPERATOR elif member == "__getitem__": # Indexed get. # TODO: Fix this consistently in format_type - if isinstance(original_type, CallableType) and original_type.is_type_obj(): + if isinstance(original_type, FunctionLike) and original_type.is_type_obj(): self.fail( "The type {} is not generic and not indexable".format( - format_type(original_type) + format_type(original_type, self.options) ), context, ) + return None else: self.fail( - f"Value of type {format_type(original_type)} is not indexable", + f"Value of type {format_type(original_type, self.options)} is not indexable", context, code=codes.INDEX, ) + return codes.INDEX elif member == "__setitem__": # Indexed set. self.fail( "Unsupported target for indexed assignment ({})".format( - format_type(original_type) + format_type(original_type, self.options) ), context, code=codes.INDEX, ) + return codes.INDEX elif member == "__call__": if isinstance(original_type, Instance) and ( original_type.type.fullname == "builtins.function" @@ -427,12 +448,14 @@ def has_no_attr( # "'function' not callable" is a confusing error message. # Explain that the problem is that the type of the function is not known. self.fail("Cannot call function of unknown type", context, code=codes.OPERATOR) + return codes.OPERATOR else: self.fail( - message_registry.NOT_CALLABLE.format(format_type(original_type)), + message_registry.NOT_CALLABLE.format(format_type(original_type, self.options)), context, code=codes.OPERATOR, ) + return codes.OPERATOR else: # The non-special case: a missing ordinary attribute. extra = "" @@ -449,7 +472,7 @@ def has_no_attr( and not module_symbol_table[member].module_public ): self.fail( - f"{format_type(original_type, module_names=True)} does not " + f"{format_type(original_type, self.options, module_names=True)} does not " f'explicitly export attribute "{member}"', context, code=codes.ATTR_DEFINED, @@ -471,7 +494,7 @@ def has_no_attr( if matches: self.fail( '{} has no attribute "{}"; maybe {}?{}'.format( - format_type(original_type), + format_type(original_type, self.options), member, pretty_seq(matches, "or"), extra, @@ -483,15 +506,18 @@ def has_no_attr( if not failed: self.fail( '{} has no attribute "{}"{}'.format( - format_type(original_type), member, extra + format_type(original_type, self.options), member, extra ), context, code=codes.ATTR_DEFINED, ) + return codes.ATTR_DEFINED elif isinstance(original_type, UnionType): # The checker passes "object" in lieu of "None" for attribute # checks, so we manually convert it back. - typ_format, orig_type_format = format_type_distinctly(typ, original_type) + typ_format, orig_type_format = format_type_distinctly( + typ, original_type, options=self.options + ) if typ_format == '"object"' and any( type(item) == NoneType for item in original_type.items ): @@ -503,11 +529,12 @@ def has_no_attr( context, code=codes.UNION_ATTR, ) + return codes.UNION_ATTR elif isinstance(original_type, TypeVarType): bound = get_proper_type(original_type.upper_bound) if isinstance(bound, UnionType): - typ_fmt, bound_fmt = format_type_distinctly(typ, bound) - original_type_fmt = format_type(original_type) + typ_fmt, bound_fmt = format_type_distinctly(typ, bound, options=self.options) + original_type_fmt = format_type(original_type, self.options) self.fail( "Item {} of the upper bound {} of type variable {} has no " 'attribute "{}"{}'.format( @@ -516,7 +543,17 @@ def has_no_attr( context, code=codes.UNION_ATTR, ) - return AnyType(TypeOfAny.from_error) + return codes.UNION_ATTR + else: + self.fail( + '{} has no attribute "{}"{}'.format( + format_type(original_type, self.options), member, extra + ), + context, + code=codes.ATTR_DEFINED, + ) + return codes.ATTR_DEFINED + return None def unsupported_operand_types( self, @@ -535,13 +572,13 @@ def unsupported_operand_types( if isinstance(left_type, str): left_str = left_type else: - left_str = format_type(left_type) + left_str = format_type(left_type, self.options) right_str = "" if isinstance(right_type, str): right_str = right_type else: - right_str = format_type(right_type) + right_str = format_type(right_type, self.options) if self.are_type_names_disabled(): msg = f"Unsupported operand types for {op} (likely involving Union)" @@ -553,11 +590,11 @@ def unsupported_left_operand(self, op: str, typ: Type, context: Context) -> None if self.are_type_names_disabled(): msg = f"Unsupported left operand type for {op} (some union)" else: - msg = f"Unsupported left operand type for {op} ({format_type(typ)})" + msg = f"Unsupported left operand type for {op} ({format_type(typ, self.options)})" self.fail(msg, context, code=codes.OPERATOR) def not_callable(self, typ: Type, context: Context) -> Type: - self.fail(message_registry.NOT_CALLABLE.format(format_type(typ)), context) + self.fail(message_registry.NOT_CALLABLE.format(format_type(typ, self.options)), context) return AnyType(TypeOfAny.from_error) def untyped_function_call(self, callee: CallableType, context: Context) -> Type: @@ -597,7 +634,7 @@ def incompatible_argument( if callee_name is not None: name = callee_name if callee.bound_args and callee.bound_args[0] is not None: - base = format_type(callee.bound_args[0]) + base = format_type(callee.bound_args[0], self.options) else: base = extract_type(name) @@ -630,7 +667,7 @@ def incompatible_argument( return codes.INDEX else: arg_type_str, callee_type_str = format_type_distinctly( - arg_type, callee.arg_types[n - 1] + arg_type, callee.arg_types[n - 1], options=self.options ) info = ( f" (expression has type {arg_type_str}, " @@ -651,32 +688,34 @@ def incompatible_argument( name = callee_name[1:-1] n -= 1 actual_type_str, expected_type_str = format_type_distinctly( - arg_type, callee.arg_types[0] + arg_type, callee.arg_types[0], options=self.options ) msg = "{} item {} has incompatible type {}; expected {}".format( name.title(), n, actual_type_str, expected_type_str ) code = codes.LIST_ITEM - elif callee_name == "": + elif callee_name == "" and isinstance( + get_proper_type(callee.arg_types[n - 1]), TupleType + ): name = callee_name[1:-1] n -= 1 key_type, value_type = cast(TupleType, arg_type).items - expected_key_type, expected_value_type = cast(TupleType, callee.arg_types[0]).items + expected_key_type, expected_value_type = cast(TupleType, callee.arg_types[n]).items # don't increase verbosity unless there is need to do so if is_subtype(key_type, expected_key_type): - key_type_str = format_type(key_type) - expected_key_type_str = format_type(expected_key_type) + key_type_str = format_type(key_type, self.options) + expected_key_type_str = format_type(expected_key_type, self.options) else: key_type_str, expected_key_type_str = format_type_distinctly( - key_type, expected_key_type + key_type, expected_key_type, options=self.options ) if is_subtype(value_type, expected_value_type): - value_type_str = format_type(value_type) - expected_value_type_str = format_type(expected_value_type) + value_type_str = format_type(value_type, self.options) + expected_value_type_str = format_type(expected_value_type, self.options) else: value_type_str, expected_value_type_str = format_type_distinctly( - value_type, expected_value_type + value_type, expected_value_type, options=self.options ) msg = "{} entry {} has incompatible type {}: {}; expected {}: {}".format( @@ -688,23 +727,33 @@ def incompatible_argument( expected_value_type_str, ) code = codes.DICT_ITEM + elif callee_name == "": + value_type_str, expected_value_type_str = format_type_distinctly( + arg_type, callee.arg_types[n - 1], options=self.options + ) + msg = "Unpacked dict entry {} has incompatible type {}; expected {}".format( + n - 1, value_type_str, expected_value_type_str + ) + code = codes.DICT_ITEM elif callee_name == "": actual_type_str, expected_type_str = map( - strip_quotes, format_type_distinctly(arg_type, callee.arg_types[0]) + strip_quotes, + format_type_distinctly(arg_type, callee.arg_types[0], options=self.options), ) msg = "List comprehension has incompatible type List[{}]; expected List[{}]".format( actual_type_str, expected_type_str ) elif callee_name == "": actual_type_str, expected_type_str = map( - strip_quotes, format_type_distinctly(arg_type, callee.arg_types[0]) + strip_quotes, + format_type_distinctly(arg_type, callee.arg_types[0], options=self.options), ) msg = "Set comprehension has incompatible type Set[{}]; expected Set[{}]".format( actual_type_str, expected_type_str ) elif callee_name == "": actual_type_str, expected_type_str = format_type_distinctly( - arg_type, callee.arg_types[n - 1] + arg_type, callee.arg_types[n - 1], options=self.options ) msg = ( "{} expression in dictionary comprehension has incompatible type {}; " @@ -712,7 +761,7 @@ def incompatible_argument( ).format("Key" if n == 1 else "Value", actual_type_str, expected_type_str) elif callee_name == "": actual_type_str, expected_type_str = format_type_distinctly( - arg_type, callee.arg_types[0] + arg_type, callee.arg_types[0], options=self.options ) msg = "Generator has incompatible item type {}; expected {}".format( actual_type_str, expected_type_str @@ -726,7 +775,7 @@ def incompatible_argument( except IndexError: # Varargs callees expected_type = callee.arg_types[-1] arg_type_str, expected_type_str = format_type_distinctly( - arg_type, expected_type, bare=True + arg_type, expected_type, bare=True, options=self.options ) if arg_kind == ARG_STAR: arg_type_str = "*" + arg_type_str @@ -750,7 +799,7 @@ def incompatible_argument( arg_name = callee.arg_names[m - 1] assert arg_name is not None arg_type_str, expected_type_str = format_type_distinctly( - arg_type.items[arg_name], expected_type, bare=True + arg_type.items[arg_name], expected_type, bare=True, options=self.options ) arg_label = f'"{arg_name}"' if isinstance(outer_context, IndexExpr) and isinstance( @@ -776,6 +825,7 @@ def incompatible_argument( for type in get_proper_types(expected_types): if isinstance(arg_type, Instance) and isinstance(type, Instance): notes = append_invariance_notes(notes, arg_type, type) + notes = append_numbers_notes(notes, arg_type, type) object_type = get_proper_type(object_type) if isinstance(object_type, TypedDictType): code = codes.TYPEDDICT_ITEM @@ -860,7 +910,9 @@ def invalid_index_type( *, code: ErrorCode, ) -> None: - index_str, expected_str = format_type_distinctly(index_type, expected_type) + index_str, expected_str = format_type_distinctly( + index_type, expected_type, options=self.options + ) self.fail( "Invalid index type {} for {}; expected type {}".format( index_str, base_str, expected_str @@ -939,10 +991,17 @@ def maybe_note_about_special_args(self, callee: CallableType, context: Context) context, ) + def unexpected_keyword_argument_for_function( + self, for_func: str, name: str, context: Context, *, matches: list[str] | None = None + ) -> None: + msg = f'Unexpected keyword argument "{name}"' + for_func + if matches: + msg += f"; did you mean {pretty_seq(matches, 'or')}?" + self.fail(msg, context, code=codes.CALL_ARG) + def unexpected_keyword_argument( self, callee: CallableType, name: str, arg_type: Type, context: Context ) -> None: - msg = f'Unexpected keyword argument "{name}"' + for_function(callee) # Suggest intended keyword, look for type match else fallback on any match. matching_type_args = [] not_matching_type_args = [] @@ -956,9 +1015,9 @@ def unexpected_keyword_argument( matches = best_matches(name, matching_type_args, n=3) if not matches: matches = best_matches(name, not_matching_type_args, n=3) - if matches: - msg += f"; did you mean {pretty_seq(matches, 'or')}?" - self.fail(msg, context, code=codes.CALL_ARG) + self.unexpected_keyword_argument_for_function( + for_function(callee), name, context, matches=matches + ) module = find_defining_module(self.modules, callee) if module: assert callee.definition is not None @@ -983,18 +1042,11 @@ def duplicate_argument_value(self, callee: CallableType, index: int, context: Co def does_not_return_value(self, callee_type: Type | None, context: Context) -> None: """Report an error about use of an unusable type.""" - name: str | None = None callee_type = get_proper_type(callee_type) - if isinstance(callee_type, FunctionLike): - name = callable_name(callee_type) - if name is not None: - self.fail( - f"{capitalize(name)} does not return a value", - context, - code=codes.FUNC_RETURNS_VALUE, - ) - else: - self.fail("Function does not return a value", context, code=codes.FUNC_RETURNS_VALUE) + callee_name = callable_name(callee_type) if isinstance(callee_type, FunctionLike) else None + name = callee_name or "Function" + message = f"{name} does not return a value (it only ever returns None)" + self.fail(message, context, code=codes.FUNC_RETURNS_VALUE) def deleted_as_rvalue(self, typ: DeletedType, context: Context) -> None: """Report an error about using an deleted type as an rvalue.""" @@ -1030,7 +1082,7 @@ def no_variant_matches_arguments( name_str = f" of {name}" else: name_str = "" - arg_types_str = ", ".join(format_type(arg) for arg in arg_types) + arg_types_str = ", ".join(format_type(arg, self.options) for arg in arg_types) num_args = len(arg_types) if num_args == 0: self.fail( @@ -1053,7 +1105,7 @@ def no_variant_matches_arguments( self.note(f"Possible overload variant{plural_s(len(overload.items))}:", context, code=code) for item in overload.items: - self.note(pretty_callable(item), context, offset=4, code=code) + self.note(pretty_callable(item, self.options), context, offset=4, code=code) def wrong_number_values_to_unpack( self, provided: int, expected: int, context: Context @@ -1074,10 +1126,10 @@ def unpacking_strings_disallowed(self, context: Context) -> None: self.fail("Unpacking a string is disallowed", context) def type_not_iterable(self, type: Type, context: Context) -> None: - self.fail(f"{format_type(type)} object is not iterable", context) + self.fail(f"{format_type(type, self.options)} object is not iterable", context) - def possible_missing_await(self, context: Context) -> None: - self.note('Maybe you forgot to use "await"?', context) + def possible_missing_await(self, context: Context, code: ErrorCode | None) -> None: + self.note('Maybe you forgot to use "await"?', context, code=code) def incompatible_operator_assignment(self, op: str, context: Context) -> None: self.fail(f"Result type of {op} incompatible in assignment", context) @@ -1099,13 +1151,18 @@ def signature_incompatible_with_supertype( name_in_super: str, supertype: str, context: Context, - original: FunctionLike | None = None, - override: FunctionLike | None = None, + *, + original: ProperType, + override: ProperType, ) -> None: code = codes.OVERRIDE target = self.override_target(name, name_in_super, supertype) self.fail(f'Signature of "{name}" incompatible with {target}', context, code=code) + original_str, override_str = format_type_distinctly( + original, override, options=self.options, bare=True + ) + INCLUDE_DECORATOR = True # Include @classmethod and @staticmethod decorators, if any ALLOW_DUPS = True # Allow duplicate notes, needed when signatures are duplicates ALIGN_OFFSET = 1 # One space, to account for the difference between error and note @@ -1115,13 +1172,10 @@ def signature_incompatible_with_supertype( # note: def f(self) -> str # note: Subclass: # note: def f(self, x: str) -> None - if ( - original is not None - and isinstance(original, (CallableType, Overloaded)) - and override is not None - and isinstance(override, (CallableType, Overloaded)) - ): - self.note("Superclass:", context, offset=ALIGN_OFFSET + OFFSET, code=code) + self.note( + "Superclass:", context, offset=ALIGN_OFFSET + OFFSET, allow_dups=ALLOW_DUPS, code=code + ) + if isinstance(original, (CallableType, Overloaded)): self.pretty_callable_or_overload( original, context, @@ -1130,8 +1184,19 @@ def signature_incompatible_with_supertype( allow_dups=ALLOW_DUPS, code=code, ) + else: + self.note( + original_str, + context, + offset=ALIGN_OFFSET + 2 * OFFSET, + allow_dups=ALLOW_DUPS, + code=code, + ) - self.note("Subclass:", context, offset=ALIGN_OFFSET + OFFSET, code=code) + self.note( + "Subclass:", context, offset=ALIGN_OFFSET + OFFSET, allow_dups=ALLOW_DUPS, code=code + ) + if isinstance(override, (CallableType, Overloaded)): self.pretty_callable_or_overload( override, context, @@ -1140,6 +1205,14 @@ def signature_incompatible_with_supertype( allow_dups=ALLOW_DUPS, code=code, ) + else: + self.note( + override_str, + context, + offset=ALIGN_OFFSET + 2 * OFFSET, + allow_dups=ALLOW_DUPS, + code=code, + ) def pretty_callable_or_overload( self, @@ -1157,7 +1230,11 @@ def pretty_callable_or_overload( if decorator is not None: self.note(decorator, context, offset=offset, allow_dups=allow_dups, code=code) self.note( - pretty_callable(tp), context, offset=offset, allow_dups=allow_dups, code=code + pretty_callable(tp, self.options), + context, + offset=offset, + allow_dups=allow_dups, + code=code, ) elif isinstance(tp, Overloaded): self.pretty_overload( @@ -1181,7 +1258,7 @@ def argument_incompatible_with_supertype( secondary_context: Context, ) -> None: target = self.override_target(name, name_in_supertype, supertype) - arg_type_in_supertype_f = format_type_bare(arg_type_in_supertype) + arg_type_in_supertype_f = format_type_bare(arg_type_in_supertype, self.options) self.fail( 'Argument {} of "{}" is incompatible with {}; ' 'supertype defines the argument type as "{}"'.format( @@ -1191,18 +1268,21 @@ def argument_incompatible_with_supertype( code=codes.OVERRIDE, secondary_context=secondary_context, ) - self.note( - "This violates the Liskov substitution principle", - context, - code=codes.OVERRIDE, - secondary_context=secondary_context, - ) - self.note( - "See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides", - context, - code=codes.OVERRIDE, - secondary_context=secondary_context, - ) + if name != "__post_init__": + # `__post_init__` is special, it can be incompatible by design. + # So, this note is misleading. + self.note( + "This violates the Liskov substitution principle", + context, + code=codes.OVERRIDE, + secondary_context=secondary_context, + ) + self.note( + "See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides", + context, + code=codes.OVERRIDE, + secondary_context=secondary_context, + ) if name == "__eq__" and type_name: multiline_msg = self.comparison_method_example_msg(class_name=type_name) @@ -1233,7 +1313,9 @@ def return_type_incompatible_with_supertype( context: Context, ) -> None: target = self.override_target(name, name_in_supertype, supertype) - override_str, original_str = format_type_distinctly(override, original) + override_str, original_str = format_type_distinctly( + override, original, options=self.options + ) self.fail( 'Return type {} of "{}" incompatible with return type {} in {}'.format( override_str, name, original_str, target @@ -1242,6 +1324,22 @@ def return_type_incompatible_with_supertype( code=codes.OVERRIDE, ) + original = get_proper_type(original) + override = get_proper_type(override) + if ( + isinstance(original, Instance) + and isinstance(override, Instance) + and override.type.fullname == "typing.AsyncIterator" + and original.type.fullname == "typing.Coroutine" + and len(original.args) == 3 + and original.args[2] == override + ): + self.note(f'Consider declaring "{name}" in {target} without "async"', context) + self.note( + "See https://mypy.readthedocs.io/en/stable/more_types.html#asynchronous-iterators", + context, + ) + def override_target(self, name: str, name_in_super: str, supertype: str) -> str: target = f'supertype "{supertype}"' if name_in_super != name: @@ -1249,18 +1347,21 @@ def override_target(self, name: str, name_in_super: str, supertype: str) -> str: return target def incompatible_type_application( - self, expected_arg_count: int, actual_arg_count: int, context: Context + self, min_arg_count: int, max_arg_count: int, actual_arg_count: int, context: Context ) -> None: - if expected_arg_count == 0: + if max_arg_count == 0: self.fail("Type application targets a non-generic function or class", context) - elif actual_arg_count > expected_arg_count: - self.fail( - f"Type application has too many types ({expected_arg_count} expected)", context - ) + return + + if min_arg_count == max_arg_count: + s = f"{max_arg_count} expected" else: - self.fail( - f"Type application has too few types ({expected_arg_count} expected)", context - ) + s = f"expected between {min_arg_count} and {max_arg_count}" + + if actual_arg_count > max_arg_count: + self.fail(f"Type application has too many types ({s})", context) + else: + self.fail(f"Type application has too few types ({s})", context) def could_not_infer_type_arguments( self, callee_type: CallableType, n: int, context: Context @@ -1268,6 +1369,12 @@ def could_not_infer_type_arguments( callee_name = callable_name(callee_type) if callee_name is not None and n > 0: self.fail(f"Cannot infer type argument {n} of {callee_name}", context) + if callee_name == "": + # Invariance in key type causes more of these errors than we would want. + self.note( + "Try assigning the literal to a variable annotated as dict[, ]", + context, + ) else: self.fail("Cannot infer function type argument", context) @@ -1280,7 +1387,7 @@ def invalid_keyword_var_arg(self, typ: Type, is_mapping: bool, context: Context) self.fail("Keywords must be strings", context) else: self.fail( - f"Argument after ** must be a mapping, not {format_type(typ)}", + f"Argument after ** must be a mapping, not {format_type(typ, self.options)}", context, code=codes.ARG_TYPE, ) @@ -1301,7 +1408,7 @@ def first_argument_for_super_must_be_type(self, actual: Type, context: Context) # object. type_str = "a non-type instance" else: - type_str = format_type(actual) + type_str = format_type(actual, self.options) self.fail( f'Argument 1 for "super" must be a type object; got {type_str}', context, @@ -1371,20 +1478,19 @@ def cannot_determine_type_in_base(self, name: str, base: str, context: Context) self.fail(f'Cannot determine type of "{name}" in base class "{base}"', context) def no_formal_self(self, name: str, item: CallableType, context: Context) -> None: + type = format_type(item, self.options) self.fail( - 'Attribute function "%s" with type %s does not accept self argument' - % (name, format_type(item)), - context, + f'Attribute function "{name}" with type {type} does not accept self argument', context ) def incompatible_self_argument( self, name: str, arg: Type, sig: CallableType, is_classmethod: bool, context: Context ) -> None: kind = "class attribute function" if is_classmethod else "attribute function" + arg_type = format_type(arg, self.options) + sig_type = format_type(sig, self.options) self.fail( - 'Invalid self argument %s to %s "%s" with type %s' - % (format_type(arg), kind, name, format_type(sig)), - context, + f'Invalid self argument {arg_type} to {kind} "{name}" with type {sig_type}', context ) def incompatible_conditional_function_def( @@ -1404,8 +1510,8 @@ def cannot_instantiate_abstract_class( ) -> None: attrs = format_string_list([f'"{a}"' for a in abstract_attributes]) self.fail( - 'Cannot instantiate abstract class "%s" with abstract ' - "attribute%s %s" % (class_name, plural_s(abstract_attributes), attrs), + f'Cannot instantiate abstract class "{class_name}" with abstract ' + f"attribute{plural_s(abstract_attributes)} {attrs}", context, code=codes.ABSTRACT, ) @@ -1444,6 +1550,23 @@ def cant_assign_to_method(self, context: Context) -> None: def cant_assign_to_classvar(self, name: str, context: Context) -> None: self.fail(f'Cannot assign to class variable "{name}" via instance', context) + def no_overridable_method(self, name: str, context: Context) -> None: + self.fail( + f'Method "{name}" is marked as an override, ' + "but no base method was found with this name", + context, + ) + + def explicit_override_decorator_missing( + self, name: str, base_name: str, context: Context + ) -> None: + self.fail( + f'Method "{name}" is not using @override ' + f'but is overriding a method in class "{base_name}"', + context, + code=codes.EXPLICIT_OVERRIDE_REQUIRED, + ) + def final_cant_override_writable(self, name: str, ctx: Context) -> None: self.fail(f'Cannot override writable attribute "{name}" with a final one', ctx) @@ -1476,7 +1599,7 @@ def incompatible_typevar_value( ) -> None: self.fail( message_registry.INCOMPATIBLE_TYPEVAR_VALUE.format( - typevar_name, callable_name(callee) or "function", format_type(typ) + typevar_name, callable_name(callee) or "function", format_type(typ, self.options) ), context, code=codes.TYPE_VAR, @@ -1486,7 +1609,7 @@ def dangerous_comparison(self, left: Type, right: Type, kind: str, ctx: Context) left_str = "element" if kind == "container" else "left operand" right_str = "container item" if kind == "container" else "right operand" message = "Non-overlapping {} check ({} type: {}, {} type: {})" - left_typ, right_typ = format_type_distinctly(left, right) + left_typ, right_typ = format_type_distinctly(left, right, options=self.options) self.fail( message.format(kind, left_str, left_typ, right_str, right_typ), ctx, @@ -1505,6 +1628,7 @@ def overloaded_signatures_overlap(self, index1: int, index2: int, context: Conte "Overloaded function signatures {} and {} overlap with " "incompatible return types".format(index1, index2), context, + code=codes.OVERLOAD_OVERLAP, ) def overloaded_signature_will_never_match( @@ -1544,7 +1668,9 @@ def warn_both_operands_are_from_unions(self, context: Context) -> None: def warn_operand_was_from_union(self, side: str, original: Type, context: Context) -> None: self.note( - f"{side} operand is of type {format_type(original)}", context, code=codes.OPERATOR + f"{side} operand is of type {format_type(original, self.options)}", + context, + code=codes.OPERATOR, ) def operator_method_signatures_overlap( @@ -1558,7 +1684,10 @@ def operator_method_signatures_overlap( self.fail( 'Signatures of "{}" of "{}" and "{}" of {} ' "are unsafely overlapping".format( - reverse_method, reverse_class.name, forward_method, format_type(forward_class) + reverse_method, + reverse_class.name, + forward_method, + format_type(forward_class, self.options), ), context, ) @@ -1570,20 +1699,28 @@ def signatures_incompatible(self, method: str, other_method: str, context: Conte self.fail(f'Signatures of "{method}" and "{other_method}" are incompatible', context) def yield_from_invalid_operand_type(self, expr: Type, context: Context) -> Type: - text = format_type(expr) if format_type(expr) != "object" else expr + text = ( + format_type(expr, self.options) + if format_type(expr, self.options) != "object" + else expr + ) self.fail(f'"yield from" can\'t be applied to {text}', context) return AnyType(TypeOfAny.from_error) def invalid_signature(self, func_type: Type, context: Context) -> None: - self.fail(f"Invalid signature {format_type(func_type)}", context) + self.fail(f"Invalid signature {format_type(func_type, self.options)}", context) def invalid_signature_for_special_method( self, func_type: Type, context: Context, method_name: str ) -> None: - self.fail(f'Invalid signature {format_type(func_type)} for "{method_name}"', context) + self.fail( + f'Invalid signature {format_type(func_type, self.options)} for "{method_name}"', + context, + ) def reveal_type(self, typ: Type, context: Context) -> None: - self.note(f'Revealed type is "{typ}"', context) + visitor = TypeStrVisitor(options=self.options) + self.note(f'Revealed type is "{typ.accept(visitor)}"', context) def reveal_locals(self, type_map: dict[str, Type | None], context: Context) -> None: # To ensure that the output is predictable on Python < 3.6, @@ -1592,27 +1729,30 @@ def reveal_locals(self, type_map: dict[str, Type | None], context: Context) -> N if sorted_locals: self.note("Revealed local types are:", context) for k, v in sorted_locals.items(): - self.note(f" {k}: {v}", context) + visitor = TypeStrVisitor(options=self.options) + self.note(f" {k}: {v.accept(visitor) if v is not None else None}", context) else: self.note("There are no locals to reveal", context) def unsupported_type_type(self, item: Type, context: Context) -> None: - self.fail(f'Cannot instantiate type "Type[{format_type_bare(item)}]"', context) + self.fail( + f'Cannot instantiate type "Type[{format_type_bare(item, self.options)}]"', context + ) def redundant_cast(self, typ: Type, context: Context) -> None: - self.fail(f"Redundant cast to {format_type(typ)}", context, code=codes.REDUNDANT_CAST) - - def assert_type_fail(self, source_type: Type, target_type: Type, context: Context) -> None: self.fail( - f"Expression is of type {format_type(source_type)}, " - f"not {format_type(target_type)}", + f"Redundant cast to {format_type(typ, self.options)}", context, - code=codes.ASSERT_TYPE, + code=codes.REDUNDANT_CAST, ) + def assert_type_fail(self, source_type: Type, target_type: Type, context: Context) -> None: + (source, target) = format_type_distinctly(source_type, target_type, options=self.options) + self.fail(f"Expression is of type {source}, not {target}", context, code=codes.ASSERT_TYPE) + def unimported_type_becomes_any(self, prefix: str, typ: Type, ctx: Context) -> None: self.fail( - f"{prefix} becomes {format_type(typ)} due to an unfollowed import", + f"{prefix} becomes {format_type(typ, self.options)} due to an unfollowed import", ctx, code=codes.NO_ANY_UNIMPORTED, ) @@ -1621,7 +1761,6 @@ def need_annotation_for_var( self, node: SymbolNode, context: Context, python_version: tuple[int, int] | None = None ) -> None: hint = "" - has_variable_annotations = not python_version or python_version >= (3, 6) pep604_supported = not python_version or python_version >= (3, 10) # type to recommend the user adds recommended_type = None @@ -1640,20 +1779,14 @@ def need_annotation_for_var( alias = alias.split(".")[-1] if alias == "Dict": type_dec = f"{type_dec}, {type_dec}" + if self.options.use_lowercase_names(): + alias = alias.lower() recommended_type = f"{alias}[{type_dec}]" if recommended_type is not None: - if has_variable_annotations: - hint = f' (hint: "{node.name}: {recommended_type} = ...")' - else: - hint = f' (hint: "{node.name} = ... # type: {recommended_type}")' - - if has_variable_annotations: - needed = "annotation" - else: - needed = "comment" + hint = f' (hint: "{node.name}: {recommended_type} = ...")' self.fail( - f'Need type {needed} for "{unmangle(node.name)}"{hint}', + f'Need type annotation for "{unmangle(node.name)}"{hint}', context, code=codes.VAR_ANNOTATED, ) @@ -1661,6 +1794,24 @@ def need_annotation_for_var( def explicit_any(self, ctx: Context) -> None: self.fail('Explicit "Any" is not allowed', ctx) + def unsupported_target_for_star_typeddict(self, typ: Type, ctx: Context) -> None: + self.fail( + "Unsupported type {} for ** expansion in TypedDict".format( + format_type(typ, self.options) + ), + ctx, + code=codes.TYPEDDICT_ITEM, + ) + + def non_required_keys_absent_with_star(self, keys: list[str], ctx: Context) -> None: + self.fail( + "Non-required {} not explicitly found in any ** item".format( + format_key_list(keys, short=True) + ), + ctx, + code=codes.TYPEDDICT_ITEM, + ) + def unexpected_typeddict_keys( self, typ: TypedDictType, @@ -1677,7 +1828,7 @@ def unexpected_typeddict_keys( if missing: self.fail( "Missing {} for TypedDict {}".format( - format_key_list(missing, short=True), format_type(typ) + format_key_list(missing, short=True), format_type(typ, self.options) ), context, code=codes.TYPEDDICT_ITEM, @@ -1686,7 +1837,7 @@ def unexpected_typeddict_keys( if extra: self.fail( "Extra {} for TypedDict {}".format( - format_key_list(extra, short=True), format_type(typ) + format_key_list(extra, short=True), format_type(typ, self.options) ), context, code=codes.TYPEDDICT_UNKNOWN_KEY, @@ -1732,7 +1883,9 @@ def typeddict_key_not_found( else: err_code = codes.TYPEDDICT_UNKNOWN_KEY if setitem else codes.TYPEDDICT_ITEM self.fail( - f'TypedDict {format_type(typ)} has no key "{item_name}"', context, code=err_code + f'TypedDict {format_type(typ, self.options)} has no key "{item_name}"', + context, + code=err_code, ) matches = best_matches(item_name, typ.items.keys(), n=3) if matches: @@ -1741,7 +1894,7 @@ def typeddict_key_not_found( ) def typeddict_context_ambiguous(self, types: list[TypedDictType], context: Context) -> None: - formatted_types = ", ".join(list(format_type_distinctly(*types))) + formatted_types = ", ".join(list(format_type_distinctly(*types, options=self.options))) self.fail( f"Type of TypedDict is ambiguous, none of ({formatted_types}) matches cleanly", context ) @@ -1753,7 +1906,8 @@ def typeddict_key_cannot_be_deleted( self.fail(f'TypedDict key "{item_name}" cannot be deleted', context) else: self.fail( - f'Key "{item_name}" of TypedDict {format_type(typ)} cannot be deleted', context + f'Key "{item_name}" of TypedDict {format_type(typ, self.options)} cannot be deleted', + context, ) def typeddict_setdefault_arguments_inconsistent( @@ -1761,7 +1915,7 @@ def typeddict_setdefault_arguments_inconsistent( ) -> None: msg = 'Argument 2 to "setdefault" of "TypedDict" has incompatible type {}; expected {}' self.fail( - msg.format(format_type(default), format_type(expected)), + msg.format(format_type(default, self.options), format_type(expected, self.options)), context, code=codes.TYPEDDICT_ITEM, ) @@ -1774,11 +1928,13 @@ def disallowed_any_type(self, typ: Type, context: Context) -> None: if isinstance(typ, AnyType): message = 'Expression has type "Any"' else: - message = f'Expression type contains "Any" (has type {format_type(typ)})' + message = f'Expression type contains "Any" (has type {format_type(typ, self.options)})' self.fail(message, context) def incorrectly_returning_any(self, typ: Type, context: Context) -> None: - message = f"Returning Any from function declared to return {format_type(typ)}" + message = ( + f"Returning Any from function declared to return {format_type(typ, self.options)}" + ) self.fail(message, context, code=codes.NO_ANY_RETURN) def incorrect__exit__return(self, context: Context) -> None: @@ -1805,7 +1961,8 @@ def untyped_decorated_function(self, typ: Type, context: Context) -> None: self.fail("Function is untyped after decorator transformation", context) else: self.fail( - f'Type of decorated function contains type "Any" ({format_type(typ)})', context + f'Type of decorated function contains type "Any" ({format_type(typ, self.options)})', + context, ) def typed_function_untyped_decorator(self, func_name: str, context: Context) -> None: @@ -1824,14 +1981,14 @@ def bad_proto_variance( def concrete_only_assign(self, typ: Type, context: Context) -> None: self.fail( - f"Can only assign concrete classes to a variable of type {format_type(typ)}", + f"Can only assign concrete classes to a variable of type {format_type(typ, self.options)}", context, code=codes.TYPE_ABSTRACT, ) def concrete_only_call(self, typ: Type, context: Context) -> None: self.fail( - f"Only concrete class can be given where {format_type(typ)} is expected", + f"Only concrete class can be given where {format_type(typ, self.options)} is expected", context, code=codes.TYPE_ABSTRACT, ) @@ -1857,7 +2014,8 @@ def note_call( ) -> None: self.note( '"{}.__call__" has type {}'.format( - format_type_bare(subtype), format_type(call, verbosity=1) + format_type_bare(subtype, self.options), + format_type(call, self.options, verbosity=1), ), context, code=code, @@ -1898,11 +2056,20 @@ def redundant_expr(self, description: str, truthiness: bool, context: Context) - def impossible_intersection( self, formatted_base_class_list: str, reason: str, context: Context ) -> None: - template = "Subclass of {} cannot exist: would have {}" + template = "Subclass of {} cannot exist: {}" self.fail( template.format(formatted_base_class_list, reason), context, code=codes.UNREACHABLE ) + def tvar_without_default_type( + self, tvar_name: str, last_tvar_name_with_default: str, context: Context + ) -> None: + self.fail( + f'"{tvar_name}" cannot appear after "{last_tvar_name_with_default}" ' + "in type parameter list because it has no default type", + context, + ) + def report_protocol_problems( self, subtype: Instance | TupleType | TypedDictType | TypeType | CallableType, @@ -1929,7 +2096,7 @@ def report_protocol_problems( if supertype.type.fullname in exclusions.get(type(subtype), []): return if any(isinstance(tp, UninhabitedType) for tp in get_proper_types(supertype.args)): - # We don't want to add notes for failed inference (e.g. Iterable[]). + # We don't want to add notes for failed inference (e.g. Iterable[Never]). # This will be only confusing a user even more. return @@ -1993,13 +2160,18 @@ def report_protocol_problems( return # Report member type conflicts - conflict_types = get_conflict_protocol_types(subtype, supertype, class_obj=class_obj) + conflict_types = get_conflict_protocol_types( + subtype, supertype, class_obj=class_obj, options=self.options + ) if conflict_types and ( - not is_subtype(subtype, erase_type(supertype)) + not is_subtype(subtype, erase_type(supertype), options=self.options) or not subtype.type.defn.type_vars or not supertype.type.defn.type_vars + # Always show detailed message for ParamSpec + or subtype.type.has_param_spec_type + or supertype.type.has_param_spec_type ): - type_name = format_type(subtype, module_names=True) + type_name = format_type(subtype, self.options, module_names=True) self.note(f"Following member(s) of {type_name} have conflicts:", context, code=code) for name, got, exp in conflict_types[:MAX_ITEMS]: exp = get_proper_type(exp) @@ -2008,7 +2180,9 @@ def report_protocol_problems( got, (CallableType, Overloaded) ): self.note( - "{}: expected {}, got {}".format(name, *format_type_distinctly(exp, got)), + "{}: expected {}, got {}".format( + name, *format_type_distinctly(exp, got, options=self.options) + ), context, offset=OFFSET, code=code, @@ -2017,7 +2191,7 @@ def report_protocol_problems( self.note("Expected:", context, offset=OFFSET, code=code) if isinstance(exp, CallableType): self.note( - pretty_callable(exp, skip_self=class_obj or is_module), + pretty_callable(exp, self.options, skip_self=class_obj or is_module), context, offset=2 * OFFSET, code=code, @@ -2030,7 +2204,7 @@ def report_protocol_problems( self.note("Got:", context, offset=OFFSET, code=code) if isinstance(got, CallableType): self.note( - pretty_callable(got, skip_self=class_obj or is_module), + pretty_callable(got, self.options, skip_self=class_obj or is_module), context, offset=2 * OFFSET, code=code, @@ -2115,7 +2289,7 @@ def pretty_overload( self.note(decorator, context, offset=offset, allow_dups=allow_dups, code=code) self.note( - pretty_callable(item, skip_self=skip_self), + pretty_callable(item, self.options, skip_self=skip_self), context, offset=offset, allow_dups=allow_dups, @@ -2188,11 +2362,14 @@ def format_long_tuple_type(self, typ: TupleType) -> str: """Format very long tuple type using an ellipsis notation""" item_cnt = len(typ.items) if item_cnt > 10: - return "Tuple[{}, {}, ... <{} more items>]".format( - format_type_bare(typ.items[0]), format_type_bare(typ.items[1]), str(item_cnt - 2) + return "{}[{}, {}, ... <{} more items>]".format( + "tuple" if self.options.use_lowercase_names() else "Tuple", + format_type_bare(typ.items[0], self.options), + format_type_bare(typ.items[1], self.options), + str(item_cnt - 2), ) else: - return format_type_bare(typ) + return format_type_bare(typ, self.options) def generate_incompatible_tuple_error( self, @@ -2203,13 +2380,15 @@ def generate_incompatible_tuple_error( ) -> None: """Generate error message for individual incompatible tuple pairs""" error_cnt = 0 - notes = [] # List[str] + notes: list[str] = [] for i, (lhs_t, rhs_t) in enumerate(zip(lhs_types, rhs_types)): if not is_subtype(lhs_t, rhs_t): if error_cnt < 3: notes.append( "Expression tuple item {} has type {}; {} expected; ".format( - str(i), format_type(rhs_t), format_type(lhs_t) + str(i), + format_type(rhs_t, self.options), + format_type(lhs_t, self.options), ) ) error_cnt += 1 @@ -2247,7 +2426,7 @@ def quote_type_string(type_string: str) -> str: """Quotes a type representation for use in messages.""" no_quote_regex = r"^<(tuple|union): \d+ items>$" if ( - type_string in ["Module", "overloaded function", "", ""] + type_string in ["Module", "overloaded function", "Never", ""] or type_string.startswith("Module ") or re.match(no_quote_regex, type_string) is not None or type_string.endswith("?") @@ -2269,7 +2448,6 @@ def format_callable_args( arg_strings = [] for arg_name, arg_type, arg_kind in zip(arg_names, arg_types, arg_kinds): if arg_kind == ARG_POS and arg_name is None or verbosity == 0 and arg_kind.is_positional(): - arg_strings.append(format(arg_type)) else: constructor = ARG_CONSTRUCTOR_NAMES[arg_kind] @@ -2282,7 +2460,11 @@ def format_callable_args( def format_type_inner( - typ: Type, verbosity: int, fullnames: set[str] | None, module_names: bool = False + typ: Type, + verbosity: int, + options: Options, + fullnames: set[str] | None, + module_names: bool = False, ) -> str: """ Convert a type to a relatively short string suitable for error messages. @@ -2293,11 +2475,17 @@ def format_type_inner( """ def format(typ: Type) -> str: - return format_type_inner(typ, verbosity, fullnames) + return format_type_inner(typ, verbosity, options, fullnames) def format_list(types: Sequence[Type]) -> str: return ", ".join(format(typ) for typ in types) + def format_union(types: Sequence[Type]) -> str: + formatted = [format(typ) for typ in types if format(typ) != "None"] + if any(format(typ) == "None" for typ in types): + formatted.append("None") + return " | ".join(formatted) + def format_literal_value(typ: LiteralType) -> str: if typ.is_enum_literal(): underlying_type = format(typ.fallback) @@ -2321,7 +2509,7 @@ def format_literal_value(typ: LiteralType) -> str: if isinstance(typ, Instance): itype = typ # Get the short name of the type. - if itype.type.fullname in ("types.ModuleType", "_importlib_modulespec.ModuleType"): + if itype.type.fullname == "types.ModuleType": # Make some common error messages simpler and tidier. base_str = "Module" if itype.extra_attrs and itype.extra_attrs.mod_name and module_names: @@ -2330,24 +2518,27 @@ def format_literal_value(typ: LiteralType) -> str: if itype.type.fullname == "typing._SpecialForm": # This is not a real type but used for some typing-related constructs. return "" - if verbosity >= 2 or (fullnames and itype.type.fullname in fullnames): + if itype.type.fullname in reverse_builtin_aliases and not options.use_lowercase_names(): + alias = reverse_builtin_aliases[itype.type.fullname] + base_str = alias.split(".")[-1] + elif verbosity >= 2 or (fullnames and itype.type.fullname in fullnames): base_str = itype.type.fullname else: base_str = itype.type.name if not itype.args: + if itype.type.has_type_var_tuple_type and len(itype.type.type_vars) == 1: + return base_str + "[()]" # No type arguments, just return the type name return base_str elif itype.type.fullname == "builtins.tuple": item_type_str = format(itype.args[0]) - return f"Tuple[{item_type_str}, ...]" - elif itype.type.fullname in reverse_builtin_aliases: - alias = reverse_builtin_aliases[itype.type.fullname] - alias = alias.split(".")[-1] - return f"{alias}[{format_list(itype.args)}]" + return f"{'tuple' if options.use_lowercase_names() else 'Tuple'}[{item_type_str}, ...]" else: # There are type arguments. Convert the arguments to strings. return f"{base_str}[{format_list(itype.args)}]" elif isinstance(typ, UnpackType): + if options.use_star_unpack(): + return f"*{format(typ.type)}" return f"Unpack[{format(typ.type)}]" elif isinstance(typ, TypeVarType): # This is similar to non-generic instance types. @@ -2369,14 +2560,18 @@ def format_literal_value(typ: LiteralType) -> str: # Prefer the name of the fallback class (if not tuple), as it's more informative. if typ.partial_fallback.type.fullname != "builtins.tuple": return format(typ.partial_fallback) - s = f"Tuple[{format_list(typ.items)}]" + type_items = format_list(typ.items) or "()" + if options.use_lowercase_names(): + s = f"tuple[{type_items}]" + else: + s = f"Tuple[{type_items}]" return s elif isinstance(typ, TypedDictType): # If the TypedDictType is named, return the name if not typ.is_anonymous(): return format(typ.fallback) items = [] - for (item_name, item_type) in typ.items.items(): + for item_name, item_type in typ.items.items(): modifier = "" if item_name in typ.required_keys else "?" items.append(f"{item_name!r}{modifier}: {format(item_type)}") s = f"TypedDict({{{', '.join(items)}}})" @@ -2394,9 +2589,17 @@ def format_literal_value(typ: LiteralType) -> str: ) if len(union_items) == 1 and isinstance(get_proper_type(union_items[0]), NoneType): - return f"Optional[{literal_str}]" + return ( + f"{literal_str} | None" + if options.use_or_syntax() + else f"Optional[{literal_str}]" + ) elif union_items: - return f"Union[{format_list(union_items)}, {literal_str}]" + return ( + f"{literal_str} | {format_union(union_items)}" + if options.use_or_syntax() + else f"Union[{format_list(union_items)}, {literal_str}]" + ) else: return literal_str else: @@ -2407,10 +2610,17 @@ def format_literal_value(typ: LiteralType) -> str: ) if print_as_optional: rest = [t for t in typ.items if not isinstance(get_proper_type(t), NoneType)] - return f"Optional[{format(rest[0])}]" + return ( + f"{format(rest[0])} | None" + if options.use_or_syntax() + else f"Optional[{format(rest[0])}]" + ) else: - s = f"Union[{format_list(typ.items)}]" - + s = ( + format_union(typ.items) + if options.use_or_syntax() + else f"Union[{format_list(typ.items)}]" + ) return s elif isinstance(typ, NoneType): return "None" @@ -2422,9 +2632,10 @@ def format_literal_value(typ: LiteralType) -> str: if typ.is_noreturn: return "NoReturn" else: - return "" + return "Never" elif isinstance(typ, TypeType): - return f"Type[{format(typ.item)}]" + type_name = "type" if options.use_lowercase_names() else "Type" + return f"{type_name}[{format(typ.item)}]" elif isinstance(typ, FunctionLike): func = typ if func.is_type_obj(): @@ -2434,6 +2645,8 @@ def format_literal_value(typ: LiteralType) -> str: elif isinstance(func, CallableType): if func.type_guard is not None: return_type = f"TypeGuard[{format(func.type_guard)}]" + elif func.type_is is not None: + return_type = f"TypeIs[{format(func.type_is)}]" else: return_type = format(func.ret_type) if func.is_ellipsis_args: @@ -2451,7 +2664,7 @@ def format_literal_value(typ: LiteralType) -> str: # error messages. return "overloaded function" elif isinstance(typ, UnboundType): - return str(typ) + return typ.accept(TypeStrVisitor(options=options)) elif isinstance(typ, Parameters): args = format_callable_args(typ.arg_types, typ.arg_kinds, typ.arg_names, format, verbosity) return f"[{args}]" @@ -2508,7 +2721,9 @@ def find_type_overlaps(*types: Type) -> set[str]: return overlaps -def format_type(typ: Type, verbosity: int = 0, module_names: bool = False) -> str: +def format_type( + typ: Type, options: Options, verbosity: int = 0, module_names: bool = False +) -> str: """ Convert a type to a relatively short string suitable for error messages. @@ -2519,10 +2734,12 @@ def format_type(typ: Type, verbosity: int = 0, module_names: bool = False) -> st modification of the formatted string is required, callers should use format_type_bare. """ - return quote_type_string(format_type_bare(typ, verbosity, module_names)) + return quote_type_string(format_type_bare(typ, options, verbosity, module_names)) -def format_type_bare(typ: Type, verbosity: int = 0, module_names: bool = False) -> str: +def format_type_bare( + typ: Type, options: Options, verbosity: int = 0, module_names: bool = False +) -> str: """ Convert a type to a relatively short string suitable for error messages. @@ -2534,10 +2751,10 @@ def format_type_bare(typ: Type, verbosity: int = 0, module_names: bool = False) instead. (The caller may want to use quote_type_string after processing has happened, to maintain consistent quoting in messages.) """ - return format_type_inner(typ, verbosity, find_type_overlaps(typ), module_names) + return format_type_inner(typ, verbosity, options, find_type_overlaps(typ), module_names) -def format_type_distinctly(*types: Type, bare: bool = False) -> tuple[str, ...]: +def format_type_distinctly(*types: Type, options: Options, bare: bool = False) -> tuple[str, ...]: """Jointly format types to distinct strings. Increase the verbosity of the type strings until they become distinct @@ -2552,7 +2769,8 @@ def format_type_distinctly(*types: Type, bare: bool = False) -> tuple[str, ...]: overlapping = find_type_overlaps(*types) for verbosity in range(2): strs = [ - format_type_inner(type, verbosity=verbosity, fullnames=overlapping) for type in types + format_type_inner(type, verbosity=verbosity, options=options, fullnames=overlapping) + for type in types ] if len(set(strs)) == len(strs): break @@ -2572,7 +2790,7 @@ def pretty_class_or_static_decorator(tp: CallableType) -> str | None: return None -def pretty_callable(tp: CallableType, skip_self: bool = False) -> str: +def pretty_callable(tp: CallableType, options: Options, skip_self: bool = False) -> str: """Return a nice easily-readable representation of a callable type. For example: def [T <: int] f(self, x: int, y: T) -> None @@ -2598,7 +2816,7 @@ def [T <: int] f(self, x: int, y: T) -> None name = tp.arg_names[i] if name: s += name + ": " - type_str = format_type_bare(tp.arg_types[i]) + type_str = format_type_bare(tp.arg_types[i], options) if tp.arg_kinds[i] == ARG_STAR2 and tp.unpack_kwargs: type_str = f"Unpack[{type_str}]" s += type_str @@ -2617,7 +2835,11 @@ def [T <: int] f(self, x: int, y: T) -> None slash = True # If we got a "special arg" (i.e: self, cls, etc...), prepend it to the arg list - if isinstance(tp.definition, FuncDef) and hasattr(tp.definition, "arguments"): + if ( + isinstance(tp.definition, FuncDef) + and hasattr(tp.definition, "arguments") + and not tp.from_concatenate + ): definition_arg_names = [arg.variable.name for arg in tp.definition.arguments] if ( len(definition_arg_names) > len(tp.arg_names) @@ -2640,9 +2862,11 @@ def [T <: int] f(self, x: int, y: T) -> None s += " -> " if tp.type_guard is not None: - s += f"TypeGuard[{format_type_bare(tp.type_guard)}]" + s += f"TypeGuard[{format_type_bare(tp.type_guard, options)}]" + elif tp.type_is is not None: + s += f"TypeIs[{format_type_bare(tp.type_is, options)}]" else: - s += format_type_bare(tp.ret_type) + s += format_type_bare(tp.ret_type, options) if tp.variables: tvars = [] @@ -2653,11 +2877,12 @@ def [T <: int] f(self, x: int, y: T) -> None isinstance(upper_bound, Instance) and upper_bound.type.fullname != "builtins.object" ): - tvars.append(f"{tvar.name} <: {format_type_bare(upper_bound)}") + tvars.append(f"{tvar.name} <: {format_type_bare(upper_bound, options)}") elif tvar.values: tvars.append( "{} in ({})".format( - tvar.name, ", ".join([format_type_bare(tp) for tp in tvar.values]) + tvar.name, + ", ".join([format_type_bare(tp, options) for tp in tvar.values]), ) ) else: @@ -2693,7 +2918,7 @@ def get_missing_protocol_members(left: Instance, right: Instance, skip: list[str def get_conflict_protocol_types( - left: Instance, right: Instance, class_obj: bool = False + left: Instance, right: Instance, class_obj: bool = False, options: Options | None = None ) -> list[tuple[str, Type, Type]]: """Find members that are defined in 'left' but have incompatible types. Return them as a list of ('member', 'got', 'expected'). @@ -2705,12 +2930,12 @@ def get_conflict_protocol_types( continue supertype = find_member(member, right, left) assert supertype is not None - subtype = find_member(member, left, left, class_obj=class_obj) + subtype = mypy.typeops.get_protocol_member(left, member, class_obj) if not subtype: continue - is_compat = is_subtype(subtype, supertype, ignore_pos_arg_names=True) + is_compat = is_subtype(subtype, supertype, ignore_pos_arg_names=True, options=options) if IS_SETTABLE in get_member_flags(member, right): - is_compat = is_compat and is_subtype(supertype, subtype) + is_compat = is_compat and is_subtype(supertype, subtype, options=options) if not is_compat: conflicts.append((member, subtype, supertype)) return conflicts @@ -2775,7 +3000,7 @@ def strip_quotes(s: str) -> str: def format_string_list(lst: list[str]) -> str: - assert len(lst) > 0 + assert lst if len(lst) == 1: return lst[0] elif len(lst) <= 5: @@ -2810,12 +3035,15 @@ def for_function(callee: CallableType) -> str: return "" -def wrong_type_arg_count(n: int, act: str, name: str) -> str: - s = f"{n} type arguments" - if n == 0: - s = "no type arguments" - elif n == 1: - s = "1 type argument" +def wrong_type_arg_count(low: int, high: int, act: str, name: str) -> str: + if low == high: + s = f"{low} type arguments" + if low == 0: + s = "no type arguments" + elif low == 1: + s = "1 type argument" + else: + s = f"between {low} and {high} type arguments" if act == "0": act = "none" return f'"{name}" expects {s}, but {act} given' @@ -2849,8 +3077,9 @@ def _real_quick_ratio(a: str, b: str) -> float: def best_matches(current: str, options: Collection[str], n: int) -> list[str]: + if not current: + return [] # narrow down options cheaply - assert current options = [o for o in options if _real_quick_ratio(current, o) > 0.75] if len(options) >= 50: options = [o for o in options if abs(len(o) - len(current)) <= 1] @@ -2891,7 +3120,7 @@ def append_invariance_notes( ): invariant_type = "Dict" covariant_suggestion = ( - 'Consider using "Mapping" instead, ' "which is covariant in the value type" + 'Consider using "Mapping" instead, which is covariant in the value type' ) if invariant_type and covariant_suggestion: notes.append( @@ -2902,6 +3131,17 @@ def append_invariance_notes( return notes +def append_numbers_notes( + notes: list[str], arg_type: Instance, expected_type: Instance +) -> list[str]: + """Explain if an unsupported type from "numbers" is used in a subtype check.""" + if expected_type.type.fullname in UNSUPPORTED_NUMBERS_TYPES: + notes.append('Types from "numbers" aren\'t supported for static type checking') + notes.append("See https://peps.python.org/pep-0484/#the-numeric-tower") + notes.append("Consider using a protocol instead, such as typing.SupportsFloat") + return notes + + def make_inferred_type_note( context: Context, subtype: Type, supertype: Type, supertype_str: str ) -> str: diff --git a/mypy/metastore.py b/mypy/metastore.py index 16cbd5adc9c8..4caa7d7f0534 100644 --- a/mypy/metastore.py +++ b/mypy/metastore.py @@ -63,8 +63,7 @@ def commit(self) -> None: """ @abstractmethod - def list_all(self) -> Iterable[str]: - ... + def list_all(self) -> Iterable[str]: ... def random_string() -> str: @@ -112,7 +111,7 @@ def write(self, name: str, data: str, mtime: float | None = None) -> bool: if mtime is not None: os.utime(path, times=(mtime, mtime)) - except os.error: + except OSError: return False return True diff --git a/mypy/mixedtraverser.py b/mypy/mixedtraverser.py index 771f87fc6bd6..dfde41859c67 100644 --- a/mypy/mixedtraverser.py +++ b/mypy/mixedtraverser.py @@ -49,7 +49,7 @@ def visit_class_def(self, o: ClassDef) -> None: def visit_type_alias_expr(self, o: TypeAliasExpr) -> None: super().visit_type_alias_expr(o) self.in_type_alias_expr = True - o.type.accept(self) + o.node.target.accept(self) self.in_type_alias_expr = False def visit_type_var_expr(self, o: TypeVarExpr) -> None: diff --git a/mypy/modulefinder.py b/mypy/modulefinder.py index 265d76ed5bb6..452cfef20f4c 100644 --- a/mypy/modulefinder.py +++ b/mypy/modulefinder.py @@ -13,18 +13,11 @@ import subprocess import sys from enum import Enum, unique - -from mypy.errors import CompileError - -if sys.version_info >= (3, 11): - import tomllib -else: - import tomli as tomllib - -from typing import Dict, List, NamedTuple, Optional, Tuple, Union -from typing_extensions import Final, TypeAlias as _TypeAlias +from typing import Dict, Final, List, NamedTuple, Optional, Tuple, Union +from typing_extensions import TypeAlias as _TypeAlias from mypy import pyinfo +from mypy.errors import CompileError from mypy.fscache import FileSystemCache from mypy.nodes import MypyFile from mypy.options import Options @@ -337,14 +330,9 @@ def _find_module_non_stub_helper( # If this is not a directory then we can't traverse further into it if not self.fscache.isdir(dir_path): break - if approved_stub_package_exists(components[0]): - if len(components) == 1 or ( - self.find_module(components[0]) - is ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED - ): + for i in range(len(components), 0, -1): + if approved_stub_package_exists(".".join(components[:i])): return ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED - if approved_stub_package_exists(".".join(components[:2])): - return ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED if plausible_match: return ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS else: @@ -431,7 +419,7 @@ def _find_module(self, id: str, use_typeshed: bool) -> ModuleSearchResult: for pkg_dir in self.search_paths.package_path: stub_name = components[0] + "-stubs" stub_dir = os.path.join(pkg_dir, stub_name) - if fscache.isdir(stub_dir) and self._is_compatible_stub_package(stub_dir): + if fscache.isdir(stub_dir): stub_typed_file = os.path.join(stub_dir, "py.typed") stub_components = [stub_name] + components[1:] path = os.path.join(pkg_dir, *stub_components[:-1]) @@ -567,19 +555,6 @@ def _find_module(self, id: str, use_typeshed: bool) -> ModuleSearchResult: else: return ModuleNotFoundReason.NOT_FOUND - def _is_compatible_stub_package(self, stub_dir: str) -> bool: - """Does a stub package support the target Python version? - - Stub packages may contain a metadata file which specifies - whether the stubs are compatible with Python 2 and 3. - """ - metadata_fnam = os.path.join(stub_dir, "METADATA.toml") - if not os.path.isfile(metadata_fnam): - return True - with open(metadata_fnam, "rb") as f: - metadata = tomllib.load(f) - return bool(metadata.get("python3", True)) - def find_modules_recursive(self, module: str) -> list[BuildSource]: module_path = self.find_module(module) if isinstance(module_path, ModuleNotFoundReason): @@ -751,12 +726,19 @@ def get_search_dirs(python_executable: str | None) -> tuple[list[str], list[str] else: # Use subprocess to get the package directory of given Python # executable + env = {**dict(os.environ), "PYTHONSAFEPATH": "1"} try: sys_path, site_packages = ast.literal_eval( subprocess.check_output( - [python_executable, pyinfo.__file__, "getsearchdirs"], stderr=subprocess.PIPE + [python_executable, pyinfo.__file__, "getsearchdirs"], + env=env, + stderr=subprocess.PIPE, ).decode() ) + except subprocess.CalledProcessError as err: + print(err.stderr) + print(err.stdout) + raise except OSError as err: reason = os.strerror(err.errno) raise CompileError( @@ -888,6 +870,6 @@ def parse_version(version: str) -> tuple[int, int]: def typeshed_py_version(options: Options) -> tuple[int, int]: """Return Python version used for checking whether module supports typeshed.""" - # Typeshed no longer covers Python 3.x versions before 3.7, so 3.7 is + # Typeshed no longer covers Python 3.x versions before 3.8, so 3.8 is # the earliest we can support. - return max(options.python_version, (3, 7)) + return max(options.python_version, (3, 8)) diff --git a/mypy/moduleinspect.py b/mypy/moduleinspect.py index b383fc9dc145..35db2132f66c 100644 --- a/mypy/moduleinspect.py +++ b/mypy/moduleinspect.py @@ -8,7 +8,7 @@ import pkgutil import queue import sys -from multiprocessing import Process, Queue +from multiprocessing import Queue, get_context from types import ModuleType @@ -39,6 +39,10 @@ def is_c_module(module: ModuleType) -> bool: return os.path.splitext(module.__dict__["__file__"])[-1] in [".so", ".pyd", ".dll"] +def is_pyc_only(file: str | None) -> bool: + return bool(file and file.endswith(".pyc") and not os.path.exists(file[:-1])) + + class InspectError(Exception): pass @@ -119,9 +123,13 @@ def __init__(self) -> None: self._start() def _start(self) -> None: - self.tasks: Queue[str] = Queue() - self.results: Queue[ModuleProperties | str] = Queue() - self.proc = Process(target=worker, args=(self.tasks, self.results, sys.path)) + if sys.platform == "linux": + ctx = get_context("forkserver") + else: + ctx = get_context("spawn") + self.tasks: Queue[str] = ctx.Queue() + self.results: Queue[ModuleProperties | str] = ctx.Queue() + self.proc = ctx.Process(target=worker, args=(self.tasks, self.results, sys.path)) self.proc.start() self.counter = 0 # Number of successful roundtrips diff --git a/mypy/mro.py b/mypy/mro.py index cc9f88a9d045..f34f3fa0c46d 100644 --- a/mypy/mro.py +++ b/mypy/mro.py @@ -44,7 +44,7 @@ def linearize_hierarchy( def merge(seqs: list[list[TypeInfo]]) -> list[TypeInfo]: - seqs = [s[:] for s in seqs] + seqs = [s.copy() for s in seqs] result: list[TypeInfo] = [] while True: seqs = [s for s in seqs if s] diff --git a/mypy/nodes.py b/mypy/nodes.py index dc32a638ad4c..21051ffa4d0b 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -11,6 +11,7 @@ Any, Callable, Dict, + Final, Iterator, List, Optional, @@ -20,12 +21,13 @@ Union, cast, ) -from typing_extensions import Final, TypeAlias as _TypeAlias +from typing_extensions import TypeAlias as _TypeAlias, TypeGuard from mypy_extensions import trait import mypy.strconv -from mypy.util import short_type +from mypy.options import Options +from mypy.util import is_typeshed_file, short_type from mypy.visitor import ExpressionVisitor, NodeVisitor, StatementVisitor if TYPE_CHECKING: @@ -137,18 +139,7 @@ def set_line( # This keeps track of the oldest supported Python version where the corresponding # alias source is available. -type_aliases_source_versions: Final = { - "typing.List": (2, 7), - "typing.Dict": (2, 7), - "typing.Set": (2, 7), - "typing.FrozenSet": (2, 7), - "typing.ChainMap": (3, 3), - "typing.Counter": (2, 7), - "typing.DefaultDict": (2, 7), - "typing.Deque": (2, 7), - "typing.OrderedDict": (3, 7), - "typing.LiteralString": (3, 11), -} +type_aliases_source_versions: Final = {"typing.LiteralString": (3, 11)} # This keeps track of aliases in `typing_extensions`, which we treat specially. typing_extensions_aliases: Final = { @@ -174,7 +165,7 @@ def set_line( def get_nongen_builtins(python_version: tuple[int, int]) -> dict[str, str]: - # After 3.9 with pep585 generic builtins are allowed. + # After 3.9 with pep585 generic builtins are allowed return _nongen_builtins if python_version < (3, 9) else {} @@ -191,13 +182,18 @@ class Node(Context): __slots__ = () def __str__(self) -> str: - ans = self.accept(mypy.strconv.StrConv()) + ans = self.accept(mypy.strconv.StrConv(options=Options())) if ans is None: return repr(self) return ans + def str_with_options(self, options: Options) -> str: + ans = self.accept(mypy.strconv.StrConv(options=options)) + assert ans + return ans + def accept(self, visitor: NodeVisitor[T]) -> T: - raise RuntimeError("Not implemented") + raise RuntimeError("Not implemented", type(self)) @trait @@ -207,7 +203,7 @@ class Statement(Node): __slots__ = () def accept(self, visitor: StatementVisitor[T]) -> T: - raise RuntimeError("Not implemented") + raise RuntimeError("Not implemented", type(self)) @trait @@ -217,7 +213,7 @@ class Expression(Node): __slots__ = () def accept(self, visitor: ExpressionVisitor[T]) -> T: - raise RuntimeError("Not implemented") + raise RuntimeError("Not implemented", type(self)) class FakeExpression(Expression): @@ -282,11 +278,13 @@ class MypyFile(SymbolNode): "names", "imports", "ignored_lines", + "skipped_lines", "is_stub", "is_cache_skeleton", "is_partial_stub_package", "plugin_deps", "future_import_flags", + "_is_typeshed_file", ) __match_args__ = ("name", "path", "defs") @@ -308,6 +306,9 @@ class MypyFile(SymbolNode): # If the value is empty, ignore all errors; otherwise, the list contains all # error codes to ignore. ignored_lines: dict[int, list[str]] + # Lines that were skipped during semantic analysis e.g. due to ALWAYS_FALSE, MYPY_FALSE, + # or platform/version checks. Those lines would not be type-checked. + skipped_lines: set[int] # Is this file represented by a stub file (.pyi)? is_stub: bool # Is this loaded from the cache and thus missing the actual body of the file? @@ -320,6 +321,7 @@ class MypyFile(SymbolNode): plugin_deps: dict[str, set[str]] # Future imports defined in this file. Populated during semantic analysis. future_import_flags: set[str] + _is_typeshed_file: bool | None def __init__( self, @@ -340,12 +342,14 @@ def __init__( self.ignored_lines = ignored_lines else: self.ignored_lines = {} + self.skipped_lines = set() self.path = "" self.is_stub = False self.is_cache_skeleton = False self.is_partial_stub_package = False self.future_import_flags = set() + self._is_typeshed_file = None def local_definitions(self) -> Iterator[Definition]: """Return all definitions within the module (including nested). @@ -371,6 +375,12 @@ def is_package_init_file(self) -> bool: def is_future_flag_set(self, flag: str) -> bool: return flag in self.future_import_flags + def is_typeshed_file(self, options: Options) -> bool: + # Cache result since this is called a lot + if self._is_typeshed_file is None: + self._is_typeshed_file = is_typeshed_file(options.abs_custom_typeshed_dir, self.path) + return self._is_typeshed_file + def serialize(self) -> JsonDict: return { ".class": "MypyFile", @@ -501,8 +511,10 @@ class FuncBase(Node): "info", "is_property", "is_class", # Uses "@classmethod" (explicit or implicit) - "is_static", # Uses "@staticmethod" + "is_static", # Uses "@staticmethod" (explicit or implicit) "is_final", # Uses "@final" + "is_explicit_override", # Uses "@override" + "is_type_check_only", # Uses "@type_check_only" "_fullname", ) @@ -514,12 +526,13 @@ def __init__(self) -> None: # Original, not semantically analyzed type (used for reprocessing) self.unanalyzed_type: mypy.types.ProperType | None = None # If method, reference to TypeInfo - # TODO: Type should be Optional[TypeInfo] self.info = FUNC_NO_INFO self.is_property = False self.is_class = False self.is_static = False self.is_final = False + self.is_explicit_override = False + self.is_type_check_only = False # Name with module prefix self._fullname = "" @@ -557,10 +570,9 @@ def __init__(self, items: list[OverloadPart]) -> None: self.items = items self.unanalyzed_items = items.copy() self.impl = None - if len(items) > 0: + if items: # TODO: figure out how to reliably set end position (we don't know the impl here). self.set_line(items[0].line, items[0].column) - self.is_final = False @property def name(self) -> str: @@ -642,6 +654,28 @@ def set_line( self.variable.set_line(self.line, self.column, self.end_line, self.end_column) +# These specify the kind of a TypeParam +TYPE_VAR_KIND: Final = 0 +PARAM_SPEC_KIND: Final = 1 +TYPE_VAR_TUPLE_KIND: Final = 2 + + +class TypeParam: + __slots__ = ("name", "kind", "upper_bound", "values") + + def __init__( + self, + name: str, + kind: int, + upper_bound: mypy.types.Type | None, + values: list[mypy.types.Type], + ) -> None: + self.name = name + self.kind = kind + self.upper_bound = upper_bound + self.values = values + + FUNCITEM_FLAGS: Final = FUNCBASE_FLAGS + [ "is_overload", "is_generator", @@ -661,6 +695,7 @@ class FuncItem(FuncBase): "min_args", # Minimum number of arguments "max_pos", # Maximum number of positional arguments, -1 if no explicit # limit (*args not included) + "type_args", # New-style type parameters (PEP 695) "body", # Body of the function "is_overload", # Is this an overload variant of function with more than # one overload variant? @@ -678,12 +713,14 @@ def __init__( arguments: list[Argument] | None = None, body: Block | None = None, typ: mypy.types.FunctionLike | None = None, + type_args: list[TypeParam] | None = None, ) -> None: super().__init__() self.arguments = arguments or [] self.arg_names = [None if arg.pos_only else arg.variable.name for arg in self.arguments] self.arg_kinds: list[ArgKind] = [arg.kind for arg in self.arguments] self.max_pos: int = self.arg_kinds.count(ARG_POS) + self.arg_kinds.count(ARG_OPT) + self.type_args: list[TypeParam] | None = type_args self.body: Block = body or Block([]) self.type = typ self.unanalyzed_type = typ @@ -738,6 +775,7 @@ class FuncDef(FuncItem, SymbolNode, Statement): "is_mypy_only", # Present only when a function is decorated with @typing.datasclass_transform or similar "dataclass_transform_spec", + "docstring", ) __match_args__ = ("name", "arguments", "type", "body") @@ -749,8 +787,9 @@ def __init__( arguments: list[Argument] | None = None, body: Block | None = None, typ: mypy.types.FunctionLike | None = None, + type_args: list[TypeParam] | None = None, ) -> None: - super().__init__(arguments, body, typ) + super().__init__(arguments, body, typ, type_args) self._name = name self.is_decorated = False self.is_conditional = False # Defined conditionally (within block)? @@ -758,7 +797,6 @@ def __init__( # Is this an abstract method with trivial body? # Such methods can't be called via super(). self.is_trivial_body = False - self.is_final = False # Original conditional definition self.original_def: None | FuncDef | Var | Decorator = None # Used for error reporting (to keep backward compatibility with pre-3.8) @@ -766,6 +804,7 @@ def __init__( # Definitions that appear in if TYPE_CHECKING are marked with this flag. self.is_mypy_only = False self.dataclass_transform_spec: DataclassTransformSpec | None = None + self.docstring: str | None = None @property def name(self) -> str: @@ -902,6 +941,7 @@ def deserialize(cls, data: JsonDict) -> Decorator: VAR_FLAGS: Final = [ "is_self", + "is_cls", "is_initialized_in_class", "is_staticmethod", "is_classmethod", @@ -936,6 +976,7 @@ class Var(SymbolNode): "type", "final_value", "is_self", + "is_cls", "is_ready", "is_inferred", "is_initialized_in_class", @@ -968,6 +1009,8 @@ def __init__(self, name: str, type: mypy.types.Type | None = None) -> None: self.type: mypy.types.Type | None = type # Declared or inferred type, or None # Is this the first argument to an ordinary method (usually "self")? self.is_self = False + # Is this the first argument to a classmethod (typically "cls")? + self.is_cls = False self.is_ready = True # If inferred, is the inferred type available? self.is_inferred = self.type is None # Is this initialized explicitly to a non-None value in class body? @@ -986,7 +1029,7 @@ def __init__(self, name: str, type: mypy.types.Type | None = None) -> None: # If constant value is a simple literal, # store the literal value (unboxed) for the benefit of # tools like mypyc. - self.final_value: int | float | bool | str | None = None + self.final_value: int | float | complex | bool | str | None = None # Where the value was set (only for class attributes) self.final_unset_in_class = False self.final_set_in_init = False @@ -1054,6 +1097,7 @@ class ClassDef(Statement): "name", "_fullname", "defs", + "type_args", "type_vars", "base_type_exprs", "removed_base_type_exprs", @@ -1064,6 +1108,7 @@ class ClassDef(Statement): "analyzed", "has_incompatible_baseclass", "deco_line", + "docstring", "removed_statements", ) @@ -1072,6 +1117,9 @@ class ClassDef(Statement): name: str # Name of the class without module prefix _fullname: str # Fully qualified name of the class defs: Block + # New-style type parameters (PEP 695), unanalyzed + type_args: list[TypeParam] | None + # Semantically analyzed type parameters (all syntax variants) type_vars: list[mypy.types.TypeVarLikeType] # Base class expressions (not semantically analyzed -- can be arbitrary expressions) base_type_exprs: list[Expression] @@ -1094,12 +1142,14 @@ def __init__( base_type_exprs: list[Expression] | None = None, metaclass: Expression | None = None, keywords: list[tuple[str, Expression]] | None = None, + type_args: list[TypeParam] | None = None, ) -> None: super().__init__() self.name = name self._fullname = "" self.defs = defs self.type_vars = type_vars or [] + self.type_args = type_args self.base_type_exprs = base_type_exprs or [] self.removed_base_type_exprs = [] self.info = CLASSDEF_NO_INFO @@ -1110,6 +1160,7 @@ def __init__( self.has_incompatible_baseclass = False # Used for error reporting (to keep backwad compatibility with pre-3.8) self.deco_line: int | None = None + self.docstring: str | None = None self.removed_statements = [] @property @@ -1137,7 +1188,7 @@ def serialize(self) -> JsonDict: } @classmethod - def deserialize(self, data: JsonDict) -> ClassDef: + def deserialize(cls, data: JsonDict) -> ClassDef: assert data[".class"] == "ClassDef" res = ClassDef( data["name"], @@ -1589,6 +1640,25 @@ def accept(self, visitor: StatementVisitor[T]) -> T: return visitor.visit_match_stmt(self) +class TypeAliasStmt(Statement): + __slots__ = ("name", "type_args", "value") + + __match_args__ = ("name", "type_args", "value") + + name: NameExpr + type_args: list[TypeParam] + value: Expression # Will get translated into a type + + def __init__(self, name: NameExpr, type_args: list[TypeParam], value: Expression) -> None: + super().__init__() + self.name = name + self.type_args = type_args + self.value = value + + def accept(self, visitor: StatementVisitor[T]) -> T: + return visitor.visit_type_alias_stmt(self) + + # Expressions @@ -1632,6 +1702,10 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T: return visitor.visit_str_expr(self) +def is_StrExpr_list(seq: list[Expression]) -> TypeGuard[list[StrExpr]]: + return all(isinstance(item, StrExpr) for item in seq) + + class BytesExpr(Expression): """Bytes literal""" @@ -1733,6 +1807,7 @@ class RefExpr(Expression): "is_inferred_def", "is_alias_rvalue", "type_guard", + "type_is", ) def __init__(self) -> None: @@ -1754,6 +1829,8 @@ def __init__(self) -> None: self.is_alias_rvalue = False # Cache type guard from callable_type.type_guard self.type_guard: mypy.types.Type | None = None + # And same for TypeIs + self.type_is: mypy.types.Type | None = None @property def fullname(self) -> str: @@ -2124,21 +2201,26 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T: class RevealExpr(Expression): """Reveal type expression reveal_type(expr) or reveal_locals() expression.""" - __slots__ = ("expr", "kind", "local_nodes") + __slots__ = ("expr", "kind", "local_nodes", "is_imported") - __match_args__ = ("expr", "kind", "local_nodes") + __match_args__ = ("expr", "kind", "local_nodes", "is_imported") expr: Expression | None kind: int local_nodes: list[Var] | None def __init__( - self, kind: int, expr: Expression | None = None, local_nodes: list[Var] | None = None + self, + kind: int, + expr: Expression | None = None, + local_nodes: list[Var] | None = None, + is_imported: bool = False, ) -> None: super().__init__() self.expr = expr self.kind = kind self.local_nodes = local_nodes + self.is_imported = is_imported def accept(self, visitor: ExpressionVisitor[T]) -> T: return visitor.visit_reveal_expr(self) @@ -2176,7 +2258,8 @@ def name(self) -> str: def expr(self) -> Expression: """Return the expression (the body) of the lambda.""" - ret = cast(ReturnStmt, self.body.body[-1]) + ret = self.body.body[-1] + assert isinstance(ret, ReturnStmt) expr = ret.expr assert expr is not None # lambda can't have empty body return expr @@ -2411,6 +2494,7 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T: INVARIANT: Final = 0 COVARIANT: Final = 1 CONTRAVARIANT: Final = 2 +VARIANCE_NOT_READY: Final = 3 # Variance hasn't been inferred (using Python 3.12 syntax) class TypeVarLikeExpr(SymbolNode, Expression): @@ -2419,13 +2503,16 @@ class TypeVarLikeExpr(SymbolNode, Expression): Note that they are constructed by the semantic analyzer. """ - __slots__ = ("_name", "_fullname", "upper_bound", "variance") + __slots__ = ("_name", "_fullname", "upper_bound", "default", "variance", "is_new_style") _name: str _fullname: str # Upper bound: only subtypes of upper_bound are valid as values. By default # this is 'object', meaning no restriction. upper_bound: mypy.types.Type + # Default: used to resolve the TypeVar if the default is not explicitly given. + # By default this is 'AnyType(TypeOfAny.from_omitted_generics)'. See PEP 696. + default: mypy.types.Type # Variance of the type variable. Invariant is the default. # TypeVar(..., covariant=True) defines a covariant type variable. # TypeVar(..., contravariant=True) defines a contravariant type @@ -2433,13 +2520,21 @@ class TypeVarLikeExpr(SymbolNode, Expression): variance: int def __init__( - self, name: str, fullname: str, upper_bound: mypy.types.Type, variance: int = INVARIANT + self, + name: str, + fullname: str, + upper_bound: mypy.types.Type, + default: mypy.types.Type, + variance: int = INVARIANT, + is_new_style: bool = False, ) -> None: super().__init__() self._name = name self._fullname = fullname self.upper_bound = upper_bound + self.default = default self.variance = variance + self.is_new_style = is_new_style @property def name(self) -> str: @@ -2464,7 +2559,7 @@ class TypeVarExpr(TypeVarLikeExpr): __slots__ = ("values",) - __match_args__ = ("name", "values", "upper_bound") + __match_args__ = ("name", "values", "upper_bound", "default") # Value restriction: only types in the list are valid as values. If the # list is empty, there is no restriction. @@ -2476,9 +2571,11 @@ def __init__( fullname: str, values: list[mypy.types.Type], upper_bound: mypy.types.Type, + default: mypy.types.Type, variance: int = INVARIANT, + is_new_style: bool = False, ) -> None: - super().__init__(name, fullname, upper_bound, variance) + super().__init__(name, fullname, upper_bound, default, variance, is_new_style) self.values = values def accept(self, visitor: ExpressionVisitor[T]) -> T: @@ -2491,6 +2588,7 @@ def serialize(self) -> JsonDict: "fullname": self._fullname, "values": [t.serialize() for t in self.values], "upper_bound": self.upper_bound.serialize(), + "default": self.default.serialize(), "variance": self.variance, } @@ -2502,6 +2600,7 @@ def deserialize(cls, data: JsonDict) -> TypeVarExpr: data["fullname"], [mypy.types.deserialize_type(v) for v in data["values"]], mypy.types.deserialize_type(data["upper_bound"]), + mypy.types.deserialize_type(data["default"]), data["variance"], ) @@ -2509,7 +2608,7 @@ def deserialize(cls, data: JsonDict) -> TypeVarExpr: class ParamSpecExpr(TypeVarLikeExpr): __slots__ = () - __match_args__ = ("name", "upper_bound") + __match_args__ = ("name", "upper_bound", "default") def accept(self, visitor: ExpressionVisitor[T]) -> T: return visitor.visit_paramspec_expr(self) @@ -2520,6 +2619,7 @@ def serialize(self) -> JsonDict: "name": self._name, "fullname": self._fullname, "upper_bound": self.upper_bound.serialize(), + "default": self.default.serialize(), "variance": self.variance, } @@ -2530,6 +2630,7 @@ def deserialize(cls, data: JsonDict) -> ParamSpecExpr: data["name"], data["fullname"], mypy.types.deserialize_type(data["upper_bound"]), + mypy.types.deserialize_type(data["default"]), data["variance"], ) @@ -2541,7 +2642,7 @@ class TypeVarTupleExpr(TypeVarLikeExpr): tuple_fallback: mypy.types.Instance - __match_args__ = ("name", "upper_bound") + __match_args__ = ("name", "upper_bound", "default") def __init__( self, @@ -2549,9 +2650,11 @@ def __init__( fullname: str, upper_bound: mypy.types.Type, tuple_fallback: mypy.types.Instance, + default: mypy.types.Type, variance: int = INVARIANT, + is_new_style: bool = False, ) -> None: - super().__init__(name, fullname, upper_bound, variance) + super().__init__(name, fullname, upper_bound, default, variance, is_new_style) self.tuple_fallback = tuple_fallback def accept(self, visitor: ExpressionVisitor[T]) -> T: @@ -2564,6 +2667,7 @@ def serialize(self) -> JsonDict: "fullname": self._fullname, "upper_bound": self.upper_bound.serialize(), "tuple_fallback": self.tuple_fallback.serialize(), + "default": self.default.serialize(), "variance": self.variance, } @@ -2575,6 +2679,7 @@ def deserialize(cls, data: JsonDict) -> TypeVarTupleExpr: data["fullname"], mypy.types.deserialize_type(data["upper_bound"]), mypy.types.Instance.deserialize(data["tuple_fallback"]), + mypy.types.deserialize_type(data["default"]), data["variance"], ) @@ -2582,27 +2687,14 @@ def deserialize(cls, data: JsonDict) -> TypeVarTupleExpr: class TypeAliasExpr(Expression): """Type alias expression (rvalue).""" - __slots__ = ("type", "tvars", "no_args", "node") + __slots__ = ("node",) - __match_args__ = ("type", "tvars", "no_args", "node") + __match_args__ = ("node",) - # The target type. - type: mypy.types.Type - # Names of type variables used to define the alias - tvars: list[str] - # Whether this alias was defined in bare form. Used to distinguish - # between - # A = List - # and - # A = List[Any] - no_args: bool node: TypeAlias def __init__(self, node: TypeAlias) -> None: super().__init__() - self.type = node.target - self.tvars = [v.name for v in node.alias_tvars] - self.no_args = node.no_args self.node = node def accept(self, visitor: ExpressionVisitor[T]) -> T: @@ -2764,6 +2856,25 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T: return visitor.visit_temp_node(self) +# Special attributes not collected as protocol members by Python 3.12 +# See typing._SPECIAL_NAMES +EXCLUDED_PROTOCOL_ATTRIBUTES: Final = frozenset( + { + "__abstractmethods__", + "__annotations__", + "__dict__", + "__doc__", + "__init__", + "__module__", + "__new__", + "__slots__", + "__subclasshook__", + "__weakref__", + "__class_getitem__", # Since Python 3.9 + } +) + + class TypeInfo(SymbolNode): """The type structure of a single class. @@ -2817,6 +2928,7 @@ class is generic then it will be a type constructor of higher kind. "type_var_tuple_suffix", "self_type", "dataclass_transform_spec", + "is_type_check_only", ) _fullname: str # Fully qualified name @@ -2967,6 +3079,9 @@ class is generic then it will be a type constructor of higher kind. # Added if the corresponding class is directly decorated with `typing.dataclass_transform` dataclass_transform_spec: DataclassTransformSpec | None + # Is set to `True` when class is decorated with `@typing.type_check_only` + is_type_check_only: bool + FLAGS: Final = [ "is_abstract", "is_enum", @@ -3023,6 +3138,7 @@ def __init__(self, names: SymbolTable, defn: ClassDef, module_name: str) -> None self.metadata = {} self.self_type = None self.dataclass_transform_spec = None + self.is_type_check_only = False def add_type_vars(self) -> None: self.has_type_var_tuple_type = False @@ -3075,11 +3191,13 @@ def protocol_members(self) -> list[str]: for base in self.mro[:-1]: # we skip "object" since everyone implements it if base.is_protocol: for name, node in base.names.items(): - if isinstance(node.node, (TypeAlias, TypeVarExpr)): + if isinstance(node.node, (TypeAlias, TypeVarExpr, MypyFile)): # These are auxiliary definitions (and type aliases are prohibited). continue + if name in EXCLUDED_PROTOCOL_ATTRIBUTES: + continue members.add(name) - return sorted(list(members)) + return sorted(members) def __getitem__(self, name: str) -> SymbolTableNode: n = self.get(name) @@ -3175,22 +3293,21 @@ def __str__(self) -> str: This includes the most important information about the type. """ - return self.dump() + options = Options() + return self.dump( + str_conv=mypy.strconv.StrConv(options=options), + type_str_conv=mypy.types.TypeStrVisitor(options=options), + ) def dump( - self, - str_conv: mypy.strconv.StrConv | None = None, - type_str_conv: mypy.types.TypeStrVisitor | None = None, + self, str_conv: mypy.strconv.StrConv, type_str_conv: mypy.types.TypeStrVisitor ) -> str: """Return a string dump of the contents of the TypeInfo.""" - if not str_conv: - str_conv = mypy.strconv.StrConv() + base: str = "" def type_str(typ: mypy.types.Type) -> str: - if type_str_conv: - return typ.accept(type_str_conv) - return str(typ) + return typ.accept(type_str_conv) head = "TypeInfo" + str_conv.format_id(self) if self.bases: @@ -3230,16 +3347,16 @@ def serialize(self) -> JsonDict: "declared_metaclass": ( None if self.declared_metaclass is None else self.declared_metaclass.serialize() ), - "metaclass_type": None - if self.metaclass_type is None - else self.metaclass_type.serialize(), + "metaclass_type": ( + None if self.metaclass_type is None else self.metaclass_type.serialize() + ), "tuple_type": None if self.tuple_type is None else self.tuple_type.serialize(), - "typeddict_type": None - if self.typeddict_type is None - else self.typeddict_type.serialize(), + "typeddict_type": ( + None if self.typeddict_type is None else self.typeddict_type.serialize() + ), "flags": get_flags(self, TypeInfo.FLAGS), "metadata": self.metadata, - "slots": list(sorted(self.slots)) if self.slots is not None else None, + "slots": sorted(self.slots) if self.slots is not None else None, "deletable_attributes": self.deletable_attributes, "self_type": self.self_type.serialize() if self.self_type is not None else None, "dataclass_transform_spec": ( @@ -3318,7 +3435,6 @@ def deserialize(cls, data: JsonDict) -> TypeInfo: class FakeInfo(TypeInfo): - __slots__ = ("msg",) # types.py defines a single instance of this class, called types.NOT_READY. @@ -3453,6 +3569,7 @@ def f(x: B[T]) -> T: ... # without T, Any would be used here "normalized", "_is_recursive", "eager", + "tvar_tuple_index", ) __match_args__ = ("name", "target", "alias_tvars", "no_args") @@ -3480,6 +3597,10 @@ def __init__( # it is the cached value. self._is_recursive: bool | None = None self.eager = eager + self.tvar_tuple_index = None + for i, t in enumerate(alias_tvars): + if isinstance(t, mypy.types.TypeVarTupleType): + self.tvar_tuple_index = i super().__init__(line, column) @classmethod @@ -3492,7 +3613,12 @@ def from_tuple_type(cls, info: TypeInfo) -> TypeAlias: assert info.tuple_type # TODO: is it possible to refactor this to set the correct type vars here? return TypeAlias( - info.tuple_type.copy_modified(fallback=mypy.types.Instance(info, info.defn.type_vars)), + info.tuple_type.copy_modified( + # Create an Instance similar to fill_typevars(). + fallback=mypy.types.Instance( + info, mypy.types.type_vars_as_args(info.defn.type_vars) + ) + ), info.fullname, info.line, info.column, @@ -3509,7 +3635,10 @@ def from_typeddict_type(cls, info: TypeInfo) -> TypeAlias: # TODO: is it possible to refactor this to set the correct type vars here? return TypeAlias( info.typeddict_type.copy_modified( - fallback=mypy.types.Instance(info, info.defn.type_vars) + # Create an Instance similar to fill_typevars(). + fallback=mypy.types.Instance( + info, mypy.types.type_vars_as_args(info.defn.type_vars) + ) ), info.fullname, info.line, @@ -3762,6 +3891,8 @@ def __str__(self) -> str: # Include declared type of variables and functions. if self.type is not None: s += f" : {self.type}" + if self.cross_ref: + s += f" cross_ref:{self.cross_ref}" return s def serialize(self, prefix: str, name: str) -> JsonDict: @@ -3895,7 +4026,7 @@ def __init__( # frozen_default was added to CPythonin https://github.com/python/cpython/pull/99958 citing # positive discussion in typing-sig frozen_default: bool | None = None, - ): + ) -> None: self.eq_default = eq_default if eq_default is not None else True self.order_default = order_default if order_default is not None else False self.kw_only_default = kw_only_default if kw_only_default is not None else False @@ -3907,8 +4038,8 @@ def serialize(self) -> JsonDict: "eq_default": self.eq_default, "order_default": self.order_default, "kw_only_default": self.kw_only_default, - "frozen_only_default": self.frozen_default, - "field_specifiers": self.field_specifiers, + "frozen_default": self.frozen_default, + "field_specifiers": list(self.field_specifiers), } @classmethod @@ -3918,7 +4049,7 @@ def deserialize(cls, data: JsonDict) -> DataclassTransformSpec: order_default=data.get("order_default"), kw_only_default=data.get("kw_only_default"), frozen_default=data.get("frozen_default"), - field_specifiers=data.get("field_specifiers"), + field_specifiers=tuple(data.get("field_specifiers", [])), ) diff --git a/mypy/operators.py b/mypy/operators.py index 2b383ef199bb..d1f050b58fae 100644 --- a/mypy/operators.py +++ b/mypy/operators.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing_extensions import Final +from typing import Final # Map from binary operator id to related method name (in Python 3). op_methods: Final = { @@ -101,3 +101,26 @@ reverse_op_method_set: Final = set(reverse_op_methods.values()) unary_op_methods: Final = {"-": "__neg__", "+": "__pos__", "~": "__invert__"} + +int_op_to_method: Final = { + "==": int.__eq__, + "is": int.__eq__, + "<": int.__lt__, + "<=": int.__le__, + "!=": int.__ne__, + "is not": int.__ne__, + ">": int.__gt__, + ">=": int.__ge__, +} + +flip_ops: Final = {"<": ">", "<=": ">=", ">": "<", ">=": "<="} +neg_ops: Final = { + "==": "!=", + "!=": "==", + "is": "is not", + "is not": "is", + "<": ">=", + "<=": ">", + ">": "<=", + ">=": "<", +} diff --git a/mypy/options.py b/mypy/options.py index 92c96a92c531..5ef6bc2a35e7 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -3,8 +3,8 @@ import pprint import re import sys -from typing import Any, Callable, Dict, Mapping, Pattern -from typing_extensions import Final +import sysconfig +from typing import Any, Callable, Final, Mapping, Pattern from mypy import defaults from mypy.errorcodes import ErrorCode, error_codes @@ -39,6 +39,7 @@ class BuildType: "disallow_untyped_defs", "enable_error_code", "enabled_error_codes", + "extra_checks", "follow_imports_for_stubs", "follow_imports", "ignore_errors", @@ -61,16 +62,20 @@ class BuildType: | { "platform", "bazel", + "old_type_inference", "plugins", "disable_bytearray_promotion", "disable_memoryview_promotion", } ) - {"debug_cache"} -# Features that are currently incomplete/experimental +# Features that are currently (or were recently) incomplete/experimental TYPE_VAR_TUPLE: Final = "TypeVarTuple" UNPACK: Final = "Unpack" -INCOMPLETE_FEATURES: Final = frozenset((TYPE_VAR_TUPLE, UNPACK)) +PRECISE_TUPLE_TYPES: Final = "PreciseTupleTypes" +NEW_GENERIC_SYNTAX: Final = "NewGenericSyntax" +INCOMPLETE_FEATURES: Final = frozenset((PRECISE_TUPLE_TYPES, NEW_GENERIC_SYNTAX)) +COMPLETE_FEATURES: Final = frozenset((TYPE_VAR_TUPLE, UNPACK)) class Options: @@ -86,7 +91,15 @@ def __init__(self) -> None: # The executable used to search for PEP 561 packages. If this is None, # then mypy does not search for PEP 561 packages. self.python_executable: str | None = sys.executable - self.platform = sys.platform + + # When cross compiling to emscripten, we need to rely on MACHDEP because + # sys.platform is the host build platform, not emscripten. + MACHDEP = sysconfig.get_config_var("MACHDEP") + if MACHDEP == "emscripten": + self.platform = MACHDEP + else: + self.platform = sys.platform + self.custom_typing_module: str | None = None self.custom_typeshed_dir: str | None = None # The abspath() version of the above, we compute it once as an optimization. @@ -127,6 +140,10 @@ def __init__(self) -> None: # Disallow calling untyped functions from typed ones self.disallow_untyped_calls = False + # Always allow untyped calls for function coming from modules/packages + # in this list (each item effectively acts as a prefix match) + self.untyped_calls_exclude: list[str] = [] + # Disallow defining untyped (or incompletely typed) functions self.disallow_untyped_defs = False @@ -191,9 +208,12 @@ def __init__(self) -> None: # This makes 1 == '1', 1 in ['1'], and 1 is '1' errors. self.strict_equality = False - # Make arguments prepended via Concatenate be truly positional-only. + # Deprecated, use extra_checks instead. self.strict_concatenate = False + # Enable additional checks that are technically correct but impractical. + self.extra_checks = False + # Report an error for any branches inferred to be unreachable as a result of # type analysis. self.warn_unreachable = False @@ -236,6 +256,8 @@ def __init__(self) -> None: # Write junit.xml to given file self.junit_xml: str | None = None + self.junit_format: str = "global" # global|per_file + # Caching and incremental checking options self.incremental = True self.cache_dir = defaults.CACHE_DIR @@ -267,6 +289,12 @@ def __init__(self) -> None: # mypy. (Like mypyc.) self.preserve_asts = False + # If True, function and class docstrings will be extracted and retained. + # This isn't exposed as a command line option + # because it is intended for software integrating with + # mypy. (Like stubgen.) + self.include_docstrings = False + # Paths of user plugins self.plugins: list[str] = [] @@ -283,7 +311,6 @@ def __init__(self) -> None: self.dump_type_stats = False self.dump_inference_stats = False self.dump_build_stats = False - self.enable_incomplete_features = False # deprecated self.enable_incomplete_feature: list[str] = [] self.timing_stats: str | None = None self.line_checking_stats: str | None = None @@ -295,11 +322,16 @@ def __init__(self) -> None: # Use stub builtins fixtures to speed up tests self.use_builtins_fixtures = False + # This should only be set when running certain mypy tests. + # Use this sparingly to avoid tests diverging from non-test behavior. + self.test_env = False + # -- experimental options -- self.shadow_file: list[list[str]] | None = None self.show_column_numbers: bool = False self.show_error_end: bool = False self.hide_error_codes = False + self.show_error_code_links = False # Use soft word wrap and show trimmed source snippets with error location markers. self.pretty = False self.dump_graph = False @@ -335,20 +367,41 @@ def __init__(self) -> None: # skip most errors after this many messages have been reported. # -1 means unlimited. self.many_errors_threshold = defaults.MANY_ERRORS_THRESHOLD - # Disable recursive type aliases (currently experimental) - self.disable_recursive_aliases = False + # Disable new experimental type inference algorithm. + self.old_type_inference = False # Deprecated reverse version of the above, do not use. - self.enable_recursive_aliases = False + self.new_type_inference = False + # Export line-level, limited, fine-grained dependency information in cache data + # (undocumented feature). + self.export_ref_info = False self.disable_bytearray_promotion = False self.disable_memoryview_promotion = False + self.force_uppercase_builtins = False + self.force_union_syntax = False + + # Sets custom output format + self.output: str | None = None + + def use_lowercase_names(self) -> bool: + if self.python_version >= (3, 9): + return not self.force_uppercase_builtins + return False + + def use_or_syntax(self) -> bool: + if self.python_version >= (3, 10): + return not self.force_union_syntax + return False + + def use_star_unpack(self) -> bool: + return self.python_version >= (3, 11) # To avoid breaking plugin compatibility, keep providing new_semantic_analyzer @property def new_semantic_analyzer(self) -> bool: return True - def snapshot(self) -> object: + def snapshot(self) -> dict[str, object]: """Produce a comparable snapshot of this Option""" # Under mypyc, we don't have a __dict__, so we need to do worse things. d = dict(getattr(self, "__dict__", ())) @@ -363,6 +416,7 @@ def __repr__(self) -> str: return f"Options({pprint.pformat(self.snapshot())})" def apply_changes(self, changes: dict[str, object]) -> Options: + # Note: effects of this method *must* be idempotent. new_options = Options() # Under mypyc, we don't have a __dict__, so we need to do worse things. replace_object_state(new_options, self, copy_dict=True) @@ -388,6 +442,17 @@ def apply_changes(self, changes: dict[str, object]) -> Options: return new_options + def compare_stable(self, other_snapshot: dict[str, object]) -> bool: + """Compare options in a way that is stable for snapshot() -> apply_changes() roundtrip. + + This is needed because apply_changes() has non-trivial effects for some flags, so + Options().apply_changes(options.snapshot()) may result in a (slightly) different object. + """ + return ( + Options().apply_changes(self.snapshot()).snapshot() + == Options().apply_changes(other_snapshot).snapshot() + ) + def build_per_module_cache(self) -> None: self._per_module_cache = {} @@ -485,7 +550,7 @@ def compile_glob(self, s: str) -> Pattern[str]: return re.compile(expr + "\\Z") def select_options_affecting_cache(self) -> Mapping[str, object]: - result: Dict[str, object] = {} + result: dict[str, object] = {} for opt in OPTIONS_AFFECTING_CACHE: val = getattr(self, opt) if opt in ("disabled_error_codes", "enabled_error_codes"): diff --git a/mypy/parse.py b/mypy/parse.py index 8bf9983967ba..ee61760c0ac0 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -6,7 +6,12 @@ def parse( - source: str | bytes, fnam: str, module: str | None, errors: Errors | None, options: Options + source: str | bytes, + fnam: str, + module: str | None, + errors: Errors, + options: Options, + raise_on_error: bool = False, ) -> MypyFile: """Parse a source file, without doing any semantic analysis. @@ -19,4 +24,7 @@ def parse( source = options.transform_source(source) import mypy.fastparse - return mypy.fastparse.parse(source, fnam=fnam, module=module, errors=errors, options=options) + tree = mypy.fastparse.parse(source, fnam=fnam, module=module, errors=errors, options=options) + if raise_on_error and errors.is_errors(): + errors.raise_error() + return tree diff --git a/mypy/partially_defined.py b/mypy/partially_defined.py index 9b8238eff83f..da0bb517189a 100644 --- a/mypy/partially_defined.py +++ b/mypy/partially_defined.py @@ -36,6 +36,7 @@ SymbolTable, TryStmt, TupleExpr, + TypeAliasStmt, WhileStmt, WithStmt, implicit_module_attrs, @@ -79,7 +80,9 @@ def copy(self) -> BranchState: class BranchStatement: - def __init__(self, initial_state: BranchState) -> None: + def __init__(self, initial_state: BranchState | None = None) -> None: + if initial_state is None: + initial_state = BranchState() self.initial_state = initial_state self.branches: list[BranchState] = [ BranchState( @@ -151,7 +154,7 @@ def done(self) -> BranchState: all_vars.update(b.must_be_defined) # For the rest of the things, we only care about branches that weren't skipped. non_skipped_branches = [b for b in self.branches if not b.skipped] - if len(non_skipped_branches) > 0: + if non_skipped_branches: must_be_defined = non_skipped_branches[0].must_be_defined for b in non_skipped_branches[1:]: must_be_defined.intersection_update(b.must_be_defined) @@ -171,7 +174,7 @@ class ScopeType(Enum): Global = 1 Class = 2 Func = 3 - Generator = 3 + Generator = 4 class Scope: @@ -199,7 +202,7 @@ class DefinedVariableTracker: def __init__(self) -> None: # There's always at least one scope. Within each scope, there's at least one "global" BranchingStatement. - self.scopes: list[Scope] = [Scope([BranchStatement(BranchState())], ScopeType.Global)] + self.scopes: list[Scope] = [Scope([BranchStatement()], ScopeType.Global)] # disable_branch_skip is used to disable skipping a branch due to a return/raise/etc. This is useful # in things like try/except/finally statements. self.disable_branch_skip = False @@ -216,9 +219,11 @@ def _scope(self) -> Scope: def enter_scope(self, scope_type: ScopeType) -> None: assert len(self._scope().branch_stmts) > 0 - self.scopes.append( - Scope([BranchStatement(self._scope().branch_stmts[-1].branches[-1])], scope_type) - ) + initial_state = None + if scope_type == ScopeType.Generator: + # Generators are special because they inherit the outer scope. + initial_state = self._scope().branch_stmts[-1].branches[-1] + self.scopes.append(Scope([BranchStatement(initial_state)], scope_type)) def exit_scope(self) -> None: self.scopes.pop() @@ -342,13 +347,15 @@ def variable_may_be_undefined(self, name: str, context: Context) -> None: def process_definition(self, name: str) -> None: # Was this name previously used? If yes, it's a used-before-definition error. if not self.tracker.in_scope(ScopeType.Class): - # Errors in class scopes are caught by the semantic analyzer. refs = self.tracker.pop_undefined_ref(name) for ref in refs: if self.loops: self.variable_may_be_undefined(name, ref) else: self.var_used_before_def(name, ref) + else: + # Errors in class scopes are caught by the semantic analyzer. + pass self.tracker.record_definition(name) def visit_global_decl(self, o: GlobalDecl) -> None: @@ -415,17 +422,24 @@ def visit_match_stmt(self, o: MatchStmt) -> None: def visit_func_def(self, o: FuncDef) -> None: self.process_definition(o.name) - self.tracker.enter_scope(ScopeType.Func) super().visit_func_def(o) - self.tracker.exit_scope() def visit_func(self, o: FuncItem) -> None: if o.is_dynamic() and not self.options.check_untyped_defs: return - if o.arguments is not None: - for arg in o.arguments: - self.tracker.record_definition(arg.variable.name) - super().visit_func(o) + + args = o.arguments or [] + # Process initializers (defaults) outside the function scope. + for arg in args: + if arg.initializer is not None: + arg.initializer.accept(self) + + self.tracker.enter_scope(ScopeType.Func) + for arg in args: + self.process_definition(arg.variable.name) + super().visit_var(arg.variable) + o.body.accept(self) + self.tracker.exit_scope() def visit_generator_expr(self, o: GeneratorExpr) -> None: self.tracker.enter_scope(ScopeType.Generator) @@ -493,7 +507,7 @@ def visit_break_stmt(self, o: BreakStmt) -> None: self.tracker.skip_branch() def visit_expression_stmt(self, o: ExpressionStmt) -> None: - if isinstance(self.type_map.get(o.expr, None), UninhabitedType): + if isinstance(self.type_map.get(o.expr, None), (UninhabitedType, type(None))): self.tracker.skip_branch() super().visit_expression_stmt(o) @@ -603,7 +617,7 @@ def visit_starred_pattern(self, o: StarredPattern) -> None: super().visit_starred_pattern(o) def visit_name_expr(self, o: NameExpr) -> None: - if o.name in self.builtins: + if o.name in self.builtins and self.tracker.in_scope(ScopeType.Global): return if self.tracker.is_possibly_undefined(o.name): # A variable is only defined in some branches. @@ -647,7 +661,7 @@ def visit_import(self, o: Import) -> None: else: # When you do `import x.y`, only `x` becomes defined. names = mod.split(".") - if len(names) > 0: + if names: # `names` should always be nonempty, but we don't want mypy # to crash on invalid code. self.tracker.record_definition(names[0]) @@ -660,3 +674,7 @@ def visit_import_from(self, o: ImportFrom) -> None: name = mod self.tracker.record_definition(name) super().visit_import_from(o) + + def visit_type_alias_stmt(self, o: TypeAliasStmt) -> None: + # Type alias target may contain forward references + self.tracker.record_definition(o.name.name) diff --git a/mypy/patterns.py b/mypy/patterns.py index 32c27d2a5b3c..a01bf6acc876 100644 --- a/mypy/patterns.py +++ b/mypy/patterns.py @@ -19,7 +19,7 @@ class Pattern(Node): __slots__ = () def accept(self, visitor: PatternVisitor[T]) -> T: - raise RuntimeError("Not implemented") + raise RuntimeError("Not implemented", type(self)) class AsPattern(Pattern): @@ -60,7 +60,7 @@ class ValuePattern(Pattern): expr: Expression - def __init__(self, expr: Expression): + def __init__(self, expr: Expression) -> None: super().__init__() self.expr = expr @@ -72,7 +72,7 @@ class SingletonPattern(Pattern): # This can be exactly True, False or None value: bool | None - def __init__(self, value: bool | None): + def __init__(self, value: bool | None) -> None: super().__init__() self.value = value @@ -85,7 +85,7 @@ class SequencePattern(Pattern): patterns: list[Pattern] - def __init__(self, patterns: list[Pattern]): + def __init__(self, patterns: list[Pattern]) -> None: super().__init__() self.patterns = patterns @@ -98,7 +98,7 @@ class StarredPattern(Pattern): # a name. capture: NameExpr | None - def __init__(self, capture: NameExpr | None): + def __init__(self, capture: NameExpr | None) -> None: super().__init__() self.capture = capture @@ -111,7 +111,9 @@ class MappingPattern(Pattern): values: list[Pattern] rest: NameExpr | None - def __init__(self, keys: list[Expression], values: list[Pattern], rest: NameExpr | None): + def __init__( + self, keys: list[Expression], values: list[Pattern], rest: NameExpr | None + ) -> None: super().__init__() assert len(keys) == len(values) self.keys = keys @@ -136,7 +138,7 @@ def __init__( positionals: list[Pattern], keyword_keys: list[str], keyword_values: list[Pattern], - ): + ) -> None: super().__init__() assert len(keyword_keys) == len(keyword_values) self.class_ref = class_ref diff --git a/mypy/plugin.py b/mypy/plugin.py index cf124b45d04f..38016191de8f 100644 --- a/mypy/plugin.py +++ b/mypy/plugin.py @@ -247,7 +247,12 @@ def fail( @abstractmethod def named_generic_type(self, name: str, args: list[Type]) -> Instance: - """Construct an instance of a builtin type with given type arguments.""" + """Construct an instance of a generic type with given type arguments.""" + raise NotImplementedError + + @abstractmethod + def get_expression_type(self, node: Expression, type_context: Type | None = None) -> Type: + """Checks the type of the given expression.""" raise NotImplementedError diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 6fda965ade8b..83f685f57a16 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -2,12 +2,18 @@ from __future__ import annotations -from typing import Iterable, List, cast -from typing_extensions import Final, Literal +from collections import defaultdict +from functools import reduce +from typing import Final, Iterable, List, Mapping, cast +from typing_extensions import Literal import mypy.plugin # To avoid circular imports. +from mypy.applytype import apply_generic_arguments from mypy.errorcodes import LITERAL_REQ +from mypy.expandtype import expand_type, expand_type_by_instance from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type +from mypy.meet import meet_types +from mypy.messages import format_type_bare from mypy.nodes import ( ARG_NAMED, ARG_NAMED_OPT, @@ -21,6 +27,7 @@ Decorator, Expression, FuncDef, + IndexExpr, JsonDict, LambdaExpr, ListExpr, @@ -32,6 +39,7 @@ SymbolTableNode, TempNode, TupleExpr, + TypeApplication, TypeInfo, TypeVarExpr, Var, @@ -43,11 +51,11 @@ _get_bool_argument, _get_decorator_bool_argument, add_attribute_to_class, - add_method, + add_method_to_class, deserialize_and_fixup_type, ) from mypy.server.trigger import make_wildcard_trigger -from mypy.typeops import make_simplified_union, map_type_from_supertype +from mypy.typeops import get_type_vars, make_simplified_union, map_type_from_supertype from mypy.types import ( AnyType, CallableType, @@ -56,10 +64,13 @@ LiteralType, NoneType, Overloaded, + ProperType, TupleType, Type, TypeOfAny, + TypeType, TypeVarType, + UninhabitedType, UnionType, get_proper_type, ) @@ -77,13 +88,15 @@ SELF_TVAR_NAME: Final = "_AT" MAGIC_ATTR_NAME: Final = "__attrs_attrs__" MAGIC_ATTR_CLS_NAME_TEMPLATE: Final = "__{}_AttrsAttributes__" # The tuple subclass pattern. +ATTRS_INIT_NAME: Final = "__attrs_init__" class Converter: """Holds information about a `converter=` argument""" - def __init__(self, init_type: Type | None = None) -> None: + def __init__(self, init_type: Type | None = None, ret_type: Type | None = None) -> None: self.init_type = init_type + self.ret_type = ret_type class Attribute: @@ -92,6 +105,7 @@ class Attribute: def __init__( self, name: str, + alias: str | None, info: TypeInfo, has_default: bool, init: bool, @@ -101,6 +115,7 @@ def __init__( init_type: Type | None, ) -> None: self.name = name + self.alias = alias self.info = info self.has_default = has_default self.init = init @@ -112,11 +127,20 @@ def __init__( def argument(self, ctx: mypy.plugin.ClassDefContext) -> Argument: """Return this attribute as an argument to __init__.""" assert self.init - init_type: Type | None = None if self.converter: if self.converter.init_type: init_type = self.converter.init_type + if init_type and self.init_type and self.converter.ret_type: + # The converter return type should be the same type as the attribute type. + # Copy type vars from attr type to converter. + converter_vars = get_type_vars(self.converter.ret_type) + init_vars = get_type_vars(self.init_type) + if converter_vars and len(converter_vars) == len(init_vars): + variables = { + binder.id: arg for binder, arg in zip(converter_vars, init_vars) + } + init_type = expand_type(init_type, variables) else: ctx.api.fail("Cannot determine __init__ type from converter", self.context) init_type = AnyType(TypeOfAny.from_error) @@ -149,19 +173,23 @@ def argument(self, ctx: mypy.plugin.ClassDefContext) -> Argument: arg_kind = ARG_OPT if self.has_default else ARG_POS # Attrs removes leading underscores when creating the __init__ arguments. - return Argument(Var(self.name.lstrip("_"), init_type), init_type, None, arg_kind) + name = self.alias or self.name.lstrip("_") + return Argument(Var(name, init_type), init_type, None, arg_kind) def serialize(self) -> JsonDict: """Serialize this object so it can be saved and restored.""" return { "name": self.name, + "alias": self.alias, "has_default": self.has_default, "init": self.init, "kw_only": self.kw_only, "has_converter": self.converter is not None, - "converter_init_type": self.converter.init_type.serialize() - if self.converter and self.converter.init_type - else None, + "converter_init_type": ( + self.converter.init_type.serialize() + if self.converter and self.converter.init_type + else None + ), "context_line": self.context.line, "context_column": self.context.column, "init_type": self.init_type.serialize() if self.init_type else None, @@ -183,6 +211,7 @@ def deserialize( return Attribute( data["name"], + data["alias"], info, data["has_default"], data["init"], @@ -272,6 +301,7 @@ def attr_class_maker_callback( ctx: mypy.plugin.ClassDefContext, auto_attribs_default: bool | None = False, frozen_default: bool = False, + slots_default: bool = False, ) -> bool: """Add necessary dunder methods to classes decorated with attr.s. @@ -282,7 +312,9 @@ def attr_class_maker_callback( it will add an __init__ or all the compare methods. For frozen=True it will turn the attrs into properties. - See http://www.attrs.org/en/stable/how-does-it-work.html for information on how attrs works. + Hashability will be set according to https://www.attrs.org/en/stable/hashing.html. + + See https://www.attrs.org/en/stable/how-does-it-work.html for information on how attrs works. If this returns False, some required metadata was not ready yet and we need another pass. @@ -292,7 +324,7 @@ def attr_class_maker_callback( init = _get_decorator_bool_argument(ctx, "init", True) frozen = _get_frozen(ctx, frozen_default) order = _determine_eq_order(ctx) - slots = _get_decorator_bool_argument(ctx, "slots", False) + slots = _get_decorator_bool_argument(ctx, "slots", slots_default) auto_attribs = _get_decorator_optional_bool_argument(ctx, "auto_attribs", auto_attribs_default) kw_only = _get_decorator_bool_argument(ctx, "kw_only", False) @@ -330,11 +362,31 @@ def attr_class_maker_callback( adder = MethodAdder(ctx) # If __init__ is not being generated, attrs still generates it as __attrs_init__ instead. - _add_init(ctx, attributes, adder, "__init__" if init else "__attrs_init__") + _add_init(ctx, attributes, adder, "__init__" if init else ATTRS_INIT_NAME) + if order: _add_order(ctx, adder) if frozen: _make_frozen(ctx, attributes) + # Frozen classes are hashable by default, even if inheriting from non-frozen ones. + hashable: bool | None = _get_decorator_bool_argument( + ctx, "hash", True + ) and _get_decorator_bool_argument(ctx, "unsafe_hash", True) + else: + hashable = _get_decorator_optional_bool_argument(ctx, "unsafe_hash") + if hashable is None: # unspecified + hashable = _get_decorator_optional_bool_argument(ctx, "hash") + + eq = _get_decorator_optional_bool_argument(ctx, "eq") + has_own_hash = "__hash__" in ctx.cls.info.names + + if has_own_hash or (hashable is None and eq is False): + pass # Do nothing. + elif hashable: + # We copy the `__hash__` signature from `object` to make them hashable. + ctx.cls.info.names["__hash__"] = ctx.cls.info.mro[-1].names["__hash__"] + else: + _remove_hashability(ctx) return True @@ -475,6 +527,7 @@ def _attributes_from_assignment( or if auto_attribs is enabled also like this: x: type x: type = default_value + x: type = attr.ib(...) """ for lvalue in stmt.lvalues: lvalues, rvalues = _parse_assignments(lvalue, stmt) @@ -511,7 +564,6 @@ def _cleanup_decorator(stmt: Decorator, attr_map: dict[str, Attribute]) -> None: and isinstance(func_decorator.expr, NameExpr) and func_decorator.expr.name in attr_map ): - if func_decorator.name == "default": attr_map[func_decorator.expr.name].has_default = True @@ -542,7 +594,7 @@ def _attribute_from_auto_attrib( has_rhs = not isinstance(rvalue, TempNode) sym = ctx.cls.info.names.get(name) init_type = sym.type if sym else None - return Attribute(name, ctx.cls.info, has_rhs, True, kw_only, None, stmt, init_type) + return Attribute(name, None, ctx.cls.info, has_rhs, True, kw_only, None, stmt, init_type) def _attribute_from_attrib_maker( @@ -606,9 +658,20 @@ def _attribute_from_attrib_maker( converter = convert converter_info = _parse_converter(ctx, converter) + # Custom alias might be defined: + alias = None + alias_expr = _get_argument(rvalue, "alias") + if alias_expr: + alias = ctx.api.parse_str_literal(alias_expr) + if alias is None: + ctx.api.fail( + '"alias" argument to attrs field must be a string literal', + rvalue, + code=LITERAL_REQ, + ) name = unmangle(lhs.name) return Attribute( - name, ctx.cls.info, attr_has_default, init, kw_only, converter_info, stmt, init_type + name, alias, ctx.cls.info, attr_has_default, init, kw_only, converter_info, stmt, init_type ) @@ -650,6 +713,26 @@ def _parse_converter( from mypy.checkmember import type_object_type # To avoid import cycle. converter_type = type_object_type(converter_expr.node, ctx.api.named_type) + elif ( + isinstance(converter_expr, IndexExpr) + and isinstance(converter_expr.analyzed, TypeApplication) + and isinstance(converter_expr.base, RefExpr) + and isinstance(converter_expr.base.node, TypeInfo) + ): + # The converter is a generic type. + from mypy.checkmember import type_object_type # To avoid import cycle. + + converter_type = type_object_type(converter_expr.base.node, ctx.api.named_type) + if isinstance(converter_type, CallableType): + converter_type = apply_generic_arguments( + converter_type, + converter_expr.analyzed.types, + ctx.api.msg.incompatible_typevar_value, + converter_type, + ) + else: + converter_type = None + if isinstance(converter_expr, LambdaExpr): # TODO: should we send a fail if converter_expr.min_args > 1? converter_info.init_type = AnyType(TypeOfAny.unannotated) @@ -668,6 +751,8 @@ def _parse_converter( converter_type = get_proper_type(converter_type) if isinstance(converter_type, CallableType) and converter_type.arg_types: converter_info.init_type = converter_type.arg_types[0] + if not is_attr_converters_optional: + converter_info.ret_type = converter_type.ret_type elif isinstance(converter_type, Overloaded): types: list[Type] = [] for item in converter_type.items: @@ -723,10 +808,19 @@ def _add_order(ctx: mypy.plugin.ClassDefContext, adder: MethodAdder) -> None: # def __lt__(self: AT, other: AT) -> bool # This way comparisons with subclasses will work correctly. tvd = TypeVarType( - SELF_TVAR_NAME, ctx.cls.info.fullname + "." + SELF_TVAR_NAME, -1, [], object_type + SELF_TVAR_NAME, + ctx.cls.info.fullname + "." + SELF_TVAR_NAME, + id=-1, + values=[], + upper_bound=object_type, + default=AnyType(TypeOfAny.from_omitted_generics), ) self_tvar_expr = TypeVarExpr( - SELF_TVAR_NAME, ctx.cls.info.fullname + "." + SELF_TVAR_NAME, [], object_type + SELF_TVAR_NAME, + ctx.cls.info.fullname + "." + SELF_TVAR_NAME, + [], + object_type, + AnyType(TypeOfAny.from_omitted_generics), ) ctx.cls.info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr) @@ -750,7 +844,7 @@ def _make_frozen(ctx: mypy.plugin.ClassDefContext, attributes: list[Attribute]) else: # This variable belongs to a super class so create new Var so we # can modify it. - var = Var(attribute.name, ctx.cls.info[attribute.name].type) + var = Var(attribute.name, attribute.init_type) var.info = ctx.cls.info var._fullname = f"{ctx.cls.info.fullname}.{var.name}" ctx.cls.info.names[var.name] = SymbolTableNode(MDEF, var) @@ -840,9 +934,21 @@ def _add_attrs_magic_attribute( def _add_slots(ctx: mypy.plugin.ClassDefContext, attributes: list[Attribute]) -> None: + if any(p.slots is None for p in ctx.cls.info.mro[1:-1]): + # At least one type in mro (excluding `self` and `object`) + # does not have concrete `__slots__` defined. Ignoring. + return + # Unlike `@dataclasses.dataclass`, `__slots__` is rewritten here. ctx.cls.info.slots = {attr.name for attr in attributes} + # Also, inject `__slots__` attribute to class namespace: + slots_type = TupleType( + [ctx.api.named_type("builtins.str") for _ in attributes], + fallback=ctx.api.named_type("builtins.tuple"), + ) + add_attribute_to_class(api=ctx.api, cls=ctx.cls, name="__slots__", typ=slots_type) + def _add_match_args(ctx: mypy.plugin.ClassDefContext, attributes: list[Attribute]) -> None: if ( @@ -861,6 +967,13 @@ def _add_match_args(ctx: mypy.plugin.ClassDefContext, attributes: list[Attribute add_attribute_to_class(api=ctx.api, cls=ctx.cls, name="__match_args__", typ=match_args) +def _remove_hashability(ctx: mypy.plugin.ClassDefContext) -> None: + """Remove hashability from a class.""" + add_attribute_to_class( + ctx.api, ctx.cls, "__hash__", NoneType(), is_classvar=True, overwrite_existing=True + ) + + class MethodAdder: """Helper to add methods to a TypeInfo. @@ -887,4 +1000,167 @@ def add_method( tvd: If the method is generic these should be the type variables. """ self_type = self_type if self_type is not None else self.self_type - add_method(self.ctx, method_name, args, ret_type, self_type, tvd) + add_method_to_class( + self.ctx.api, self.ctx.cls, method_name, args, ret_type, self_type, tvd + ) + + +def _get_attrs_init_type(typ: Instance) -> CallableType | None: + """ + If `typ` refers to an attrs class, get the type of its initializer method. + """ + magic_attr = typ.type.get(MAGIC_ATTR_NAME) + if magic_attr is None or not magic_attr.plugin_generated: + return None + init_method = typ.type.get_method("__init__") or typ.type.get_method(ATTRS_INIT_NAME) + if not isinstance(init_method, FuncDef) or not isinstance(init_method.type, CallableType): + return None + return init_method.type + + +def _fail_not_attrs_class(ctx: mypy.plugin.FunctionSigContext, t: Type, parent_t: Type) -> None: + t_name = format_type_bare(t, ctx.api.options) + if parent_t is t: + msg = ( + f'Argument 1 to "evolve" has a variable type "{t_name}" not bound to an attrs class' + if isinstance(t, TypeVarType) + else f'Argument 1 to "evolve" has incompatible type "{t_name}"; expected an attrs class' + ) + else: + pt_name = format_type_bare(parent_t, ctx.api.options) + msg = ( + f'Argument 1 to "evolve" has type "{pt_name}" whose item "{t_name}" is not bound to an attrs class' + if isinstance(t, TypeVarType) + else f'Argument 1 to "evolve" has incompatible type "{pt_name}" whose item "{t_name}" is not an attrs class' + ) + + ctx.api.fail(msg, ctx.context) + + +def _get_expanded_attr_types( + ctx: mypy.plugin.FunctionSigContext, + typ: ProperType, + display_typ: ProperType, + parent_typ: ProperType, +) -> list[Mapping[str, Type]] | None: + """ + For a given type, determine what attrs classes it can be: for each class, return the field types. + For generic classes, the field types are expanded. + If the type contains Any or a non-attrs type, returns None; in the latter case, also reports an error. + """ + if isinstance(typ, AnyType): + return None + elif isinstance(typ, UnionType): + ret: list[Mapping[str, Type]] | None = [] + for item in typ.relevant_items(): + item = get_proper_type(item) + item_types = _get_expanded_attr_types(ctx, item, item, parent_typ) + if ret is not None and item_types is not None: + ret += item_types + else: + ret = None # but keep iterating to emit all errors + return ret + elif isinstance(typ, TypeVarType): + return _get_expanded_attr_types( + ctx, get_proper_type(typ.upper_bound), display_typ, parent_typ + ) + elif isinstance(typ, Instance): + init_func = _get_attrs_init_type(typ) + if init_func is None: + _fail_not_attrs_class(ctx, display_typ, parent_typ) + return None + init_func = expand_type_by_instance(init_func, typ) + # [1:] to skip the self argument of AttrClass.__init__ + field_names = cast(List[str], init_func.arg_names[1:]) + field_types = init_func.arg_types[1:] + return [dict(zip(field_names, field_types))] + else: + _fail_not_attrs_class(ctx, display_typ, parent_typ) + return None + + +def _meet_fields(types: list[Mapping[str, Type]]) -> Mapping[str, Type]: + """ + "Meet" the fields of a list of attrs classes, i.e. for each field, its new type will be the lower bound. + """ + field_to_types = defaultdict(list) + for fields in types: + for name, typ in fields.items(): + field_to_types[name].append(typ) + + return { + name: ( + get_proper_type(reduce(meet_types, f_types)) + if len(f_types) == len(types) + else UninhabitedType() + ) + for name, f_types in field_to_types.items() + } + + +def evolve_function_sig_callback(ctx: mypy.plugin.FunctionSigContext) -> CallableType: + """ + Generate a signature for the 'attr.evolve' function that's specific to the call site + and dependent on the type of the first argument. + """ + if len(ctx.args) != 2: + # Ideally the name and context should be callee's, but we don't have it in FunctionSigContext. + ctx.api.fail(f'"{ctx.default_signature.name}" has unexpected type annotation', ctx.context) + return ctx.default_signature + + if len(ctx.args[0]) != 1: + return ctx.default_signature # leave it to the type checker to complain + + inst_arg = ctx.args[0][0] + inst_type = get_proper_type(ctx.api.get_expression_type(inst_arg)) + inst_type_str = format_type_bare(inst_type, ctx.api.options) + + attr_types = _get_expanded_attr_types(ctx, inst_type, inst_type, inst_type) + if attr_types is None: + return ctx.default_signature + fields = _meet_fields(attr_types) + + return CallableType( + arg_names=["inst", *fields.keys()], + arg_kinds=[ARG_POS] + [ARG_NAMED_OPT] * len(fields), + arg_types=[inst_type, *fields.values()], + ret_type=inst_type, + fallback=ctx.default_signature.fallback, + name=f"{ctx.default_signature.name} of {inst_type_str}", + ) + + +def fields_function_sig_callback(ctx: mypy.plugin.FunctionSigContext) -> CallableType: + """Provide the signature for `attrs.fields`.""" + if len(ctx.args) != 1 or len(ctx.args[0]) != 1: + return ctx.default_signature + + proper_type = get_proper_type(ctx.api.get_expression_type(ctx.args[0][0])) + + # fields(Any) -> Any, fields(type[Any]) -> Any + if ( + isinstance(proper_type, AnyType) + or isinstance(proper_type, TypeType) + and isinstance(proper_type.item, AnyType) + ): + return ctx.default_signature + + cls = None + arg_types = ctx.default_signature.arg_types + + if isinstance(proper_type, TypeVarType): + inner = get_proper_type(proper_type.upper_bound) + if isinstance(inner, Instance): + # We need to work arg_types to compensate for the attrs stubs. + arg_types = [proper_type] + cls = inner.type + elif isinstance(proper_type, CallableType): + cls = proper_type.type_object() + + if cls is not None and MAGIC_ATTR_NAME in cls.names: + # This is a proper attrs class. + ret_type = cls.names[MAGIC_ATTR_NAME].type + assert ret_type is not None + return ctx.default_signature.copy_modified(arg_types=arg_types, ret_type=ret_type) + + return ctx.default_signature diff --git a/mypy/plugins/common.py b/mypy/plugins/common.py index 0acf3e3a6369..f0ff6f30a3b9 100644 --- a/mypy/plugins/common.py +++ b/mypy/plugins/common.py @@ -1,5 +1,8 @@ from __future__ import annotations +from typing import NamedTuple + +from mypy.argmap import map_actuals_to_formals from mypy.fixup import TypeFixer from mypy.nodes import ( ARG_POS, @@ -13,30 +16,38 @@ Expression, FuncDef, JsonDict, + NameExpr, Node, + OverloadedFuncDef, PassStmt, RefExpr, SymbolTableNode, + TypeInfo, Var, ) from mypy.plugin import CheckerPluginInterface, ClassDefContext, SemanticAnalyzerPluginInterface from mypy.semanal_shared import ( ALLOW_INCOMPATIBLE_OVERRIDE, + parse_bool, require_bool_literal_argument, set_callable_name, ) -from mypy.typeops import ( # noqa: F401 # Part of public API - try_getting_str_literals as try_getting_str_literals, -) +from mypy.typeops import try_getting_str_literals as try_getting_str_literals from mypy.types import ( + AnyType, CallableType, + Instance, + LiteralType, + NoneType, Overloaded, Type, + TypeOfAny, TypeType, TypeVarType, deserialize_type, get_proper_type, ) +from mypy.types_utils import is_overlapping_none from mypy.typevars import fill_typevars from mypy.util import get_unique_redefinition_name @@ -87,6 +98,71 @@ def _get_argument(call: CallExpr, name: str) -> Expression | None: return None +def find_shallow_matching_overload_item(overload: Overloaded, call: CallExpr) -> CallableType: + """Perform limited lookup of a matching overload item. + + Full overload resolution is only supported during type checking, but plugins + sometimes need to resolve overloads. This can be used in some such use cases. + + Resolve overloads based on these things only: + + * Match using argument kinds and names + * If formal argument has type None, only accept the "None" expression in the callee + * If formal argument has type Literal[True] or Literal[False], only accept the + relevant bool literal + + Return the first matching overload item, or the last one if nothing matches. + """ + for item in overload.items[:-1]: + ok = True + mapped = map_actuals_to_formals( + call.arg_kinds, + call.arg_names, + item.arg_kinds, + item.arg_names, + lambda i: AnyType(TypeOfAny.special_form), + ) + + # Look for extra actuals + matched_actuals = set() + for actuals in mapped: + matched_actuals.update(actuals) + if any(i not in matched_actuals for i in range(len(call.args))): + ok = False + + for arg_type, kind, actuals in zip(item.arg_types, item.arg_kinds, mapped): + if kind.is_required() and not actuals: + # Missing required argument + ok = False + break + elif actuals: + args = [call.args[i] for i in actuals] + arg_type = get_proper_type(arg_type) + arg_none = any(isinstance(arg, NameExpr) and arg.name == "None" for arg in args) + if isinstance(arg_type, NoneType): + if not arg_none: + ok = False + break + elif ( + arg_none + and not is_overlapping_none(arg_type) + and not ( + isinstance(arg_type, Instance) + and arg_type.type.fullname == "builtins.object" + ) + and not isinstance(arg_type, AnyType) + ): + ok = False + break + elif isinstance(arg_type, LiteralType) and isinstance(arg_type.value, bool): + if not any(parse_bool(arg) == arg_type.value for arg in args): + ok = False + break + if ok: + return item + return overload.items[-1] + + def _get_callee_type(call: CallExpr) -> CallableType | None: """Return the type of the callee, regardless of its syntatic form.""" @@ -103,8 +179,7 @@ def _get_callee_type(call: CallExpr) -> CallableType | None: if isinstance(callee_node, (Var, SYMBOL_FUNCBASE_TYPES)) and callee_node.type: callee_node_type = get_proper_type(callee_node.type) if isinstance(callee_node_type, Overloaded): - # We take the last overload. - return callee_node_type.items[-1] + return find_shallow_matching_overload_item(callee_node_type, call) elif isinstance(callee_node_type, CallableType): return callee_node_type @@ -138,24 +213,99 @@ def add_method( ) +class MethodSpec(NamedTuple): + """Represents a method signature to be added, except for `name`.""" + + args: list[Argument] + return_type: Type + self_type: Type | None = None + tvar_defs: list[TypeVarType] | None = None + + def add_method_to_class( api: SemanticAnalyzerPluginInterface | CheckerPluginInterface, cls: ClassDef, name: str, + # MethodSpec items kept for backward compatibility: args: list[Argument], return_type: Type, self_type: Type | None = None, - tvar_def: TypeVarType | None = None, + tvar_def: list[TypeVarType] | TypeVarType | None = None, is_classmethod: bool = False, is_staticmethod: bool = False, -) -> None: +) -> FuncDef | Decorator: """Adds a new method to a class definition.""" + _prepare_class_namespace(cls, name) - assert not ( - is_classmethod is True and is_staticmethod is True - ), "Can't add a new method that's both staticmethod and classmethod." + if tvar_def is not None and not isinstance(tvar_def, list): + tvar_def = [tvar_def] + + func, sym = _add_method_by_spec( + api, + cls.info, + name, + MethodSpec(args=args, return_type=return_type, self_type=self_type, tvar_defs=tvar_def), + is_classmethod=is_classmethod, + is_staticmethod=is_staticmethod, + ) + cls.info.names[name] = sym + cls.info.defn.defs.body.append(func) + return func + + +def add_overloaded_method_to_class( + api: SemanticAnalyzerPluginInterface | CheckerPluginInterface, + cls: ClassDef, + name: str, + items: list[MethodSpec], + is_classmethod: bool = False, + is_staticmethod: bool = False, +) -> OverloadedFuncDef: + """Adds a new overloaded method to a class definition.""" + assert len(items) >= 2, "Overloads must contain at least two cases" + + # Save old definition, if it exists. + _prepare_class_namespace(cls, name) + + # Create function bodies for each passed method spec. + funcs: list[Decorator | FuncDef] = [] + for item in items: + func, _sym = _add_method_by_spec( + api, + cls.info, + name=name, + spec=item, + is_classmethod=is_classmethod, + is_staticmethod=is_staticmethod, + ) + if isinstance(func, FuncDef): + var = Var(func.name, func.type) + var.set_line(func.line) + func.is_decorated = True + func.deco_line = func.line + + deco = Decorator(func, [], var) + else: + deco = func + deco.is_overload = True + funcs.append(deco) + + # Create the final OverloadedFuncDef node: + overload_def = OverloadedFuncDef(funcs) + overload_def.info = cls.info + overload_def.is_class = is_classmethod + overload_def.is_static = is_staticmethod + sym = SymbolTableNode(MDEF, overload_def) + sym.plugin_generated = True + cls.info.names[name] = sym + cls.info.defn.defs.body.append(overload_def) + return overload_def + + +def _prepare_class_namespace(cls: ClassDef, name: str) -> None: info = cls.info + assert info # First remove any previously generated methods with the same name # to avoid clashes and problems in the semantic analyzer. @@ -164,6 +314,29 @@ def add_method_to_class( if sym.plugin_generated and isinstance(sym.node, FuncDef): cls.defs.body.remove(sym.node) + # NOTE: we would like the plugin generated node to dominate, but we still + # need to keep any existing definitions so they get semantically analyzed. + if name in info.names: + # Get a nice unique name instead. + r_name = get_unique_redefinition_name(name, info.names) + info.names[r_name] = info.names[name] + + +def _add_method_by_spec( + api: SemanticAnalyzerPluginInterface | CheckerPluginInterface, + info: TypeInfo, + name: str, + spec: MethodSpec, + *, + is_classmethod: bool, + is_staticmethod: bool, +) -> tuple[FuncDef | Decorator, SymbolTableNode]: + args, return_type, self_type, tvar_defs = spec + + assert not ( + is_classmethod is True and is_staticmethod is True + ), "Can't add a new method that's both staticmethod and classmethod." + if isinstance(api, SemanticAnalyzerPluginInterface): function_type = api.named_type("builtins.function") else: @@ -187,8 +360,8 @@ def add_method_to_class( arg_kinds.append(arg.kind) signature = CallableType(arg_types, arg_kinds, arg_names, return_type, function_type) - if tvar_def: - signature.variables = [tvar_def] + if tvar_defs: + signature.variables = tvar_defs func = FuncDef(name, args, Block([PassStmt()])) func.info = info @@ -198,13 +371,6 @@ def add_method_to_class( func._fullname = info.fullname + "." + name func.line = info.line - # NOTE: we would like the plugin generated node to dominate, but we still - # need to keep any existing definitions so they get semantically analyzed. - if name in info.names: - # Get a nice unique name instead. - r_name = get_unique_redefinition_name(name, info.names) - info.names[r_name] = info.names[name] - # Add decorator for is_staticmethod. It's unnecessary for is_classmethod. if is_staticmethod: func.is_decorated = True @@ -215,12 +381,12 @@ def add_method_to_class( dec = Decorator(func, [], v) dec.line = info.line sym = SymbolTableNode(MDEF, dec) - else: - sym = SymbolTableNode(MDEF, func) - sym.plugin_generated = True - info.names[name] = sym + sym.plugin_generated = True + return dec, sym - info.defn.defs.body.append(func) + sym = SymbolTableNode(MDEF, func) + sym.plugin_generated = True + return func, sym def add_attribute_to_class( @@ -233,7 +399,8 @@ def add_attribute_to_class( override_allow_incompatible: bool = False, fullname: str | None = None, is_classvar: bool = False, -) -> None: + overwrite_existing: bool = False, +) -> Var: """ Adds a new attribute to a class definition. This currently only generates the symbol table entry and no corresponding AssignmentStatement @@ -242,7 +409,7 @@ def add_attribute_to_class( # NOTE: we would like the plugin generated node to dominate, but we still # need to keep any existing definitions so they get semantically analyzed. - if name in info.names: + if name in info.names and not overwrite_existing: # Get a nice unique name instead. r_name = get_unique_redefinition_name(name, info.names) info.names[r_name] = info.names[name] @@ -264,6 +431,7 @@ def add_attribute_to_class( info.names[name] = SymbolTableNode( MDEF, node, plugin_generated=True, no_serialize=no_serialize ) + return node def deserialize_and_fixup_type(data: str | JsonDict, api: SemanticAnalyzerPluginInterface) -> Type: diff --git a/mypy/plugins/ctypes.py b/mypy/plugins/ctypes.py index edfbe506fcca..b6dbec13ce90 100644 --- a/mypy/plugins/ctypes.py +++ b/mypy/plugins/ctypes.py @@ -30,10 +30,10 @@ def _find_simplecdata_base_arg( None is returned if _SimpleCData appears nowhere in tp's (direct or indirect) bases. """ - if tp.type.has_base("ctypes._SimpleCData"): + if tp.type.has_base("_ctypes._SimpleCData"): simplecdata_base = map_instance_to_supertype( tp, - api.named_generic_type("ctypes._SimpleCData", [AnyType(TypeOfAny.special_form)]).type, + api.named_generic_type("_ctypes._SimpleCData", [AnyType(TypeOfAny.special_form)]).type, ) assert len(simplecdata_base.args) == 1, "_SimpleCData takes exactly one type argument" return get_proper_type(simplecdata_base.args[0]) @@ -88,7 +88,7 @@ def _autounboxed_cdata(tp: Type) -> ProperType: return make_simplified_union([_autounboxed_cdata(t) for t in tp.items]) elif isinstance(tp, Instance): for base in tp.type.bases: - if base.type.fullname == "ctypes._SimpleCData": + if base.type.fullname == "_ctypes._SimpleCData": # If tp has _SimpleCData as a direct base class, # the auto-unboxed type is the single type argument of the _SimpleCData type. assert len(base.args) == 1 @@ -102,7 +102,7 @@ def _get_array_element_type(tp: Type) -> ProperType | None: """Get the element type of the Array type tp, or None if not specified.""" tp = get_proper_type(tp) if isinstance(tp, Instance): - assert tp.type.fullname == "ctypes.Array" + assert tp.type.fullname == "_ctypes.Array" if len(tp.args) == 1: return get_proper_type(tp.args[0]) return None @@ -123,7 +123,9 @@ def array_constructor_callback(ctx: mypy.plugin.FunctionContext) -> Type: ctx.api.msg.fail( "Array constructor argument {} of type {}" " is not convertible to the array element type {}".format( - arg_num, format_type(arg_type), format_type(et) + arg_num, + format_type(arg_type, ctx.api.options), + format_type(et, ctx.api.options), ), ctx.context, ) @@ -134,7 +136,9 @@ def array_constructor_callback(ctx: mypy.plugin.FunctionContext) -> Type: ctx.api.msg.fail( "Array constructor argument {} of type {}" " is not convertible to the array element type {}".format( - arg_num, format_type(arg_type), format_type(it) + arg_num, + format_type(arg_type, ctx.api.options), + format_type(it, ctx.api.options), ), ctx.context, ) @@ -209,7 +213,9 @@ def array_value_callback(ctx: mypy.plugin.AttributeContext) -> Type: else: ctx.api.msg.fail( 'Array attribute "value" is only available' - ' with element type "c_char" or "c_wchar", not {}'.format(format_type(et)), + ' with element type "c_char" or "c_wchar", not {}'.format( + format_type(et, ctx.api.options) + ), ctx.context, ) return make_simplified_union(types) @@ -232,7 +238,7 @@ def array_raw_callback(ctx: mypy.plugin.AttributeContext) -> Type: else: ctx.api.msg.fail( 'Array attribute "raw" is only available' - ' with element type "c_char", not {}'.format(format_type(et)), + ' with element type "c_char", not {}'.format(format_type(et, ctx.api.options)), ctx.context, ) return make_simplified_union(types) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 6b1062d6457f..dead512a2202 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -2,11 +2,12 @@ from __future__ import annotations -from typing import Optional -from typing_extensions import Final +from typing import TYPE_CHECKING, Final, Iterator, Literal from mypy import errorcodes, message_registry -from mypy.expandtype import expand_type +from mypy.expandtype import expand_type, expand_type_by_instance +from mypy.meet import meet_types +from mypy.messages import format_type_bare from mypy.nodes import ( ARG_NAMED, ARG_NAMED_OPT, @@ -17,11 +18,17 @@ MDEF, Argument, AssignmentStmt, + Block, CallExpr, ClassDef, Context, DataclassTransformSpec, + Decorator, + EllipsisExpr, Expression, + FuncDef, + FuncItem, + IfStmt, JsonDict, NameExpr, Node, @@ -35,8 +42,9 @@ TypeVarExpr, Var, ) -from mypy.plugin import ClassDefContext, SemanticAnalyzerPluginInterface +from mypy.plugin import ClassDefContext, FunctionSigContext, SemanticAnalyzerPluginInterface from mypy.plugins.common import ( + _get_callee_type, _get_decorator_bool_argument, add_attribute_to_class, add_method_to_class, @@ -45,33 +53,42 @@ from mypy.semanal_shared import find_dataclass_transform_spec, require_bool_literal_argument from mypy.server.trigger import make_wildcard_trigger from mypy.state import state -from mypy.typeops import map_type_from_supertype +from mypy.typeops import map_type_from_supertype, try_getting_literals_from_type from mypy.types import ( AnyType, CallableType, + FunctionLike, Instance, LiteralType, NoneType, + ProperType, TupleType, Type, TypeOfAny, TypeVarType, + UninhabitedType, + UnionType, get_proper_type, ) from mypy.typevars import fill_typevars +if TYPE_CHECKING: + from mypy.checker import TypeChecker + # The set of decorators that generate dataclasses. dataclass_makers: Final = {"dataclass", "dataclasses.dataclass"} SELF_TVAR_NAME: Final = "_DT" -_TRANSFORM_SPEC_FOR_DATACLASSES = DataclassTransformSpec( +_TRANSFORM_SPEC_FOR_DATACLASSES: Final = DataclassTransformSpec( eq_default=True, order_default=False, kw_only_default=False, frozen_default=False, field_specifiers=("dataclasses.Field", "dataclasses.field"), ) +_INTERNAL_REPLACE_SYM_NAME: Final = "__mypy-replace" +_INTERNAL_POST_INIT_SYM_NAME: Final = "__mypy-post_init" class DataclassAttribute: @@ -87,6 +104,8 @@ def __init__( type: Type | None, info: TypeInfo, kw_only: bool, + is_neither_frozen_nor_nonfrozen: bool, + api: SemanticAnalyzerPluginInterface, ) -> None: self.name = name self.alias = alias @@ -95,32 +114,56 @@ def __init__( self.has_default = has_default self.line = line self.column = column - self.type = type + self.type = type # Type as __init__ argument self.info = info self.kw_only = kw_only + self.is_neither_frozen_nor_nonfrozen = is_neither_frozen_nor_nonfrozen + self._api = api - def to_argument(self, current_info: TypeInfo) -> Argument: - arg_kind = ARG_POS - if self.kw_only and self.has_default: - arg_kind = ARG_NAMED_OPT - elif self.kw_only and not self.has_default: - arg_kind = ARG_NAMED - elif not self.kw_only and self.has_default: - arg_kind = ARG_OPT + def to_argument( + self, current_info: TypeInfo, *, of: Literal["__init__", "replace", "__post_init__"] + ) -> Argument: + if of == "__init__": + arg_kind = ARG_POS + if self.kw_only and self.has_default: + arg_kind = ARG_NAMED_OPT + elif self.kw_only and not self.has_default: + arg_kind = ARG_NAMED + elif not self.kw_only and self.has_default: + arg_kind = ARG_OPT + elif of == "replace": + arg_kind = ARG_NAMED if self.is_init_var and not self.has_default else ARG_NAMED_OPT + elif of == "__post_init__": + # We always use `ARG_POS` without a default value, because it is practical. + # Consider this case: + # + # @dataclass + # class My: + # y: dataclasses.InitVar[str] = 'a' + # def __post_init__(self, y: str) -> None: ... + # + # We would be *required* to specify `y: str = ...` if default is added here. + # But, most people won't care about adding default values to `__post_init__`, + # because it is not designed to be called directly, and duplicating default values + # for the sake of type-checking is unpleasant. + arg_kind = ARG_POS return Argument( variable=self.to_var(current_info), type_annotation=self.expand_type(current_info), - initializer=None, + initializer=EllipsisExpr() if self.has_default else None, # Only used by stubgen kind=arg_kind, ) - def expand_type(self, current_info: TypeInfo) -> Optional[Type]: + def expand_type(self, current_info: TypeInfo) -> Type | None: if self.type is not None and self.info.self_type is not None: - # In general, it is not safe to call `expand_type()` during semantic analyzis, + # In general, it is not safe to call `expand_type()` during semantic analysis, # however this plugin is called very late, so all types should be fully ready. # Also, it is tricky to avoid eager expansion of Self types here (e.g. because # we serialize attributes). - return expand_type(self.type, {self.info.self_type.id: fill_typevars(current_info)}) + with state.strict_optional_set(self._api.options.strict_optional): + return expand_type( + self.type, {self.info.self_type.id: fill_typevars(current_info)} + ) return self.type def to_var(self, current_info: TypeInfo) -> Var: @@ -138,6 +181,7 @@ def serialize(self) -> JsonDict: "column": self.column, "type": self.type.serialize(), "kw_only": self.kw_only, + "is_neither_frozen_nor_nonfrozen": self.is_neither_frozen_nor_nonfrozen, } @classmethod @@ -145,16 +189,15 @@ def deserialize( cls, info: TypeInfo, data: JsonDict, api: SemanticAnalyzerPluginInterface ) -> DataclassAttribute: data = data.copy() - if data.get("kw_only") is None: - data["kw_only"] = False typ = deserialize_and_fixup_type(data.pop("type"), api) - return cls(type=typ, info=info, **data) + return cls(type=typ, info=info, **data, api=api) def expand_typevar_from_subtype(self, sub_type: TypeInfo) -> None: """Expands type vars in the context of a subtype when an attribute is inherited from a generic super type.""" if self.type is not None: - self.type = map_type_from_supertype(self.type, sub_type, self.info) + with state.strict_optional_set(self._api.options.strict_optional): + self.type = map_type_from_supertype(self.type, sub_type, self.info) class DataclassTransformer: @@ -213,13 +256,11 @@ def transform(self) -> bool: and ("__init__" not in info.names or info.names["__init__"].plugin_generated) and attributes ): - - with state.strict_optional_set(self._api.options.strict_optional): - args = [ - attr.to_argument(info) - for attr in attributes - if attr.is_in_init and not self._is_kw_only_type(attr.type) - ] + args = [ + attr.to_argument(info, of="__init__") + for attr in attributes + if attr.is_in_init and not self._is_kw_only_type(attr.type) + ] if info.fallback_to_any: # Make positional args optional since we don't know their order. @@ -229,11 +270,17 @@ def transform(self) -> bool: if arg.kind == ARG_POS: arg.kind = ARG_OPT - nameless_var = Var("") + existing_args_names = {arg.variable.name for arg in args} + gen_args_name = "generated_args" + while gen_args_name in existing_args_names: + gen_args_name += "_" + gen_kwargs_name = "generated_kwargs" + while gen_kwargs_name in existing_args_names: + gen_kwargs_name += "_" args = [ - Argument(nameless_var, AnyType(TypeOfAny.explicit), None, ARG_STAR), + Argument(Var(gen_args_name), AnyType(TypeOfAny.explicit), None, ARG_STAR), *args, - Argument(nameless_var, AnyType(TypeOfAny.explicit), None, ARG_STAR2), + Argument(Var(gen_kwargs_name), AnyType(TypeOfAny.explicit), None, ARG_STAR2), ] add_method_to_class( @@ -248,7 +295,11 @@ def transform(self) -> bool: # Type variable for self types in generated methods. obj_type = self._api.named_type("builtins.object") self_tvar_expr = TypeVarExpr( - SELF_TVAR_NAME, info.fullname + "." + SELF_TVAR_NAME, [], obj_type + SELF_TVAR_NAME, + info.fullname + "." + SELF_TVAR_NAME, + [], + obj_type, + AnyType(TypeOfAny.from_omitted_generics), ) info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr) @@ -262,7 +313,12 @@ def transform(self) -> bool: # the self type. obj_type = self._api.named_type("builtins.object") order_tvar_def = TypeVarType( - SELF_TVAR_NAME, info.fullname + "." + SELF_TVAR_NAME, -1, [], obj_type + SELF_TVAR_NAME, + info.fullname + "." + SELF_TVAR_NAME, + id=-1, + values=[], + upper_bound=obj_type, + default=AnyType(TypeOfAny.from_omitted_generics), ) order_return_type = self._api.named_type("builtins.bool") order_args = [ @@ -290,7 +346,11 @@ def transform(self) -> bool: parent_decorator_arguments = [] for parent in info.mro[1:-1]: parent_args = parent.metadata.get("dataclass") - if parent_args: + + # Ignore parent classes that directly specify a dataclass transform-decorated metaclass + # when searching for usage of the frozen parameter. PEP 681 states that a class that + # directly specifies such a metaclass must be treated as neither frozen nor non-frozen. + if parent_args and not _has_direct_dataclass_transform_metaclass(parent): parent_decorator_arguments.append(parent_args) if decorator_arguments["frozen"]: @@ -313,7 +373,6 @@ def transform(self) -> bool: and ( "__match_args__" not in info.names or info.names["__match_args__"].plugin_generated ) - and attributes and py_version >= (3, 10) ): str_type = self._api.named_type("builtins.str") @@ -324,6 +383,9 @@ def transform(self) -> bool: add_attribute_to_class(self._api, self._cls, "__match_args__", match_args_type) self._add_dataclass_fields_magic_attribute() + self._add_internal_replace_method(attributes) + if "__post_init__" in info.names: + self._add_internal_post_init_method(attributes) info.metadata["dataclass"] = { "attributes": [attr.serialize() for attr in attributes], @@ -332,6 +394,33 @@ def transform(self) -> bool: return True + def _add_internal_replace_method(self, attributes: list[DataclassAttribute]) -> None: + """ + Stashes the signature of 'dataclasses.replace(...)' for this specific dataclass + to be used later whenever 'dataclasses.replace' is called for this dataclass. + """ + add_method_to_class( + self._api, + self._cls, + _INTERNAL_REPLACE_SYM_NAME, + args=[attr.to_argument(self._cls.info, of="replace") for attr in attributes], + return_type=NoneType(), + is_staticmethod=True, + ) + + def _add_internal_post_init_method(self, attributes: list[DataclassAttribute]) -> None: + add_method_to_class( + self._api, + self._cls, + _INTERNAL_POST_INIT_SYM_NAME, + args=[ + attr.to_argument(self._cls.info, of="__post_init__") + for attr in attributes + if attr.is_init_var + ], + return_type=NoneType(), + ) + def add_slots( self, info: TypeInfo, attributes: list[DataclassAttribute], *, correct_version: bool ) -> None: @@ -361,8 +450,20 @@ def add_slots( ) return + if any(p.slots is None for p in info.mro[1:-1]): + # At least one type in mro (excluding `self` and `object`) + # does not have concrete `__slots__` defined. Ignoring. + return + info.slots = generated_slots + # Now, insert `.__slots__` attribute to class namespace: + slots_type = TupleType( + [self._api.named_type("builtins.str") for _ in generated_slots], + self._api.named_type("builtins.tuple"), + ) + add_attribute_to_class(self._api, self._cls, "__slots__", slots_type) + def reset_init_only_vars(self, info: TypeInfo, attributes: list[DataclassAttribute]) -> None: """Remove init-only vars from the class and reset init var declarations.""" for attr in attributes: @@ -380,6 +481,22 @@ def reset_init_only_vars(self, info: TypeInfo, attributes: list[DataclassAttribu # recreate a symbol node for this attribute. lvalue.node = None + def _get_assignment_statements_from_if_statement( + self, stmt: IfStmt + ) -> Iterator[AssignmentStmt]: + for body in stmt.body: + if not body.is_unreachable: + yield from self._get_assignment_statements_from_block(body) + if stmt.else_body is not None and not stmt.else_body.is_unreachable: + yield from self._get_assignment_statements_from_block(stmt.else_body) + + def _get_assignment_statements_from_block(self, block: Block) -> Iterator[AssignmentStmt]: + for stmt in block.body: + if isinstance(stmt, AssignmentStmt): + yield stmt + elif isinstance(stmt, IfStmt): + yield from self._get_assignment_statements_from_if_statement(stmt) + def collect_attributes(self) -> list[DataclassAttribute] | None: """Collect all attributes declared in the dataclass and its parents. @@ -424,8 +541,7 @@ def collect_attributes(self) -> list[DataclassAttribute] | None: # TODO: We shouldn't be performing type operations during the main # semantic analysis pass, since some TypeInfo attributes might # still be in flux. This should be performed in a later phase. - with state.strict_optional_set(self._api.options.strict_optional): - attr.expand_typevar_from_subtype(cls.info) + attr.expand_typevar_from_subtype(cls.info) found_attrs[name] = attr sym_node = cls.info.names.get(name) @@ -438,10 +554,10 @@ def collect_attributes(self) -> list[DataclassAttribute] | None: # Second, collect attributes belonging to the current class. current_attr_names: set[str] = set() kw_only = self._get_bool_arg("kw_only", self._spec.kw_only_default) - for stmt in cls.defs.body: + for stmt in self._get_assignment_statements_from_block(cls.defs): # Any assignment that doesn't use the new type declaration # syntax can be ignored out of hand. - if not (isinstance(stmt, AssignmentStmt) and stmt.new_syntax): + if not stmt.new_syntax: continue # a: int, b: str = 1, 'foo' is not supported syntax so we @@ -467,6 +583,10 @@ def collect_attributes(self) -> list[DataclassAttribute] | None: # but the only alternative would be to modify the SymbolTable, # and it's a little hairy to do that in a plugin. continue + if isinstance(node, Decorator): + # This might be a property / field name clash. + # We will issue an error later. + continue assert isinstance(node, Var) @@ -491,7 +611,7 @@ def collect_attributes(self) -> list[DataclassAttribute] | None: is_in_init_param = field_args.get("init") if is_in_init_param is None: - is_in_init = True + is_in_init = self._get_default_init_value_for_field_specifier(stmt.rvalue) else: is_in_init = bool(self._api.parse_bool(is_in_init_param)) @@ -510,9 +630,12 @@ def collect_attributes(self) -> list[DataclassAttribute] | None: elif not isinstance(stmt.rvalue, TempNode): has_default = True - if not has_default: - # Make all non-default attributes implicit because they are de-facto set - # on self in the generated __init__(), not in the class body. + if not has_default and self._spec is _TRANSFORM_SPEC_FOR_DATACLASSES: + # Make all non-default dataclass attributes implicit because they are de-facto + # set on self in the generated __init__(), not in the class body. On the other + # hand, we don't know how custom dataclass transforms initialize attributes, + # so we don't treat them as implicit. This is required to support descriptors + # (https://github.com/python/mypy/issues/14868). sym.implicit = True is_kw_only = kw_only @@ -553,6 +676,8 @@ def collect_attributes(self) -> list[DataclassAttribute] | None: ) current_attr_names.add(lhs.name) + with state.strict_optional_set(self._api.options.strict_optional): + init_type = self._infer_dataclass_attr_init_type(sym, lhs.name, stmt) found_attrs[lhs.name] = DataclassAttribute( name=lhs.name, alias=alias, @@ -561,9 +686,13 @@ def collect_attributes(self) -> list[DataclassAttribute] | None: has_default=has_default, line=stmt.line, column=stmt.column, - type=sym.type, + type=init_type, info=cls.info, kw_only=is_kw_only, + is_neither_frozen_nor_nonfrozen=_has_direct_dataclass_transform_metaclass( + cls.info + ), + api=self._api, ) all_attrs = list(found_attrs.values()) @@ -606,6 +735,13 @@ def _freeze(self, attributes: list[DataclassAttribute]) -> None: """ info = self._cls.info for attr in attributes: + # Classes that directly specify a dataclass_transform metaclass must be neither frozen + # non non-frozen per PEP681. Though it is surprising, this means that attributes from + # such a class must be writable even if the rest of the class hierarchy is frozen. This + # matches the behavior of Pyright (the reference implementation). + if attr.is_neither_frozen_nor_nonfrozen: + continue + sym_node = info.names.get(attr.name) if sym_node is not None: var = sym_node.node @@ -648,17 +784,18 @@ def _is_kw_only_type(self, node: Type | None) -> bool: return node_type.type.fullname == "dataclasses.KW_ONLY" def _add_dataclass_fields_magic_attribute(self) -> None: - # Only add if the class is a dataclasses dataclass, and omit it for dataclass_transform - # classes. - # It would be nice if this condition were reified rather than using an `is` check. - # Only add if the class is a dataclasses dataclass, and omit it for dataclass_transform - # classes. - if self._spec is not _TRANSFORM_SPEC_FOR_DATACLASSES: - return - attr_name = "__dataclass_fields__" any_type = AnyType(TypeOfAny.explicit) - field_type = self._api.named_type_or_none("dataclasses.Field", [any_type]) or any_type + # For `dataclasses`, use the type `dict[str, Field[Any]]` for accuracy. For dataclass + # transforms, it's inaccurate to use `Field` since a given transform may use a completely + # different type (or none); fall back to `Any` there. + # + # In either case, we're aiming to match the Typeshed stub for `is_dataclass`, which expects + # the instance to have a `__dataclass_fields__` attribute of type `dict[str, Field[Any]]`. + if self._spec is _TRANSFORM_SPEC_FOR_DATACLASSES: + field_type = self._api.named_type_or_none("dataclasses.Field", [any_type]) or any_type + else: + field_type = any_type attr_type = self._api.named_type( "builtins.dict", [self._api.named_type("builtins.str"), field_type] ) @@ -719,6 +856,74 @@ def _get_bool_arg(self, name: str, default: bool) -> bool: return require_bool_literal_argument(self._api, expression, name, default) return default + def _get_default_init_value_for_field_specifier(self, call: Expression) -> bool: + """ + Find a default value for the `init` parameter of the specifier being called. If the + specifier's type signature includes an `init` parameter with a type of `Literal[True]` or + `Literal[False]`, return the appropriate boolean value from the literal. Otherwise, + fall back to the standard default of `True`. + """ + if not isinstance(call, CallExpr): + return True + + specifier_type = _get_callee_type(call) + if specifier_type is None: + return True + + parameter = specifier_type.argument_by_name("init") + if parameter is None: + return True + + literals = try_getting_literals_from_type(parameter.typ, bool, "builtins.bool") + if literals is None or len(literals) != 1: + return True + + return literals[0] + + def _infer_dataclass_attr_init_type( + self, sym: SymbolTableNode, name: str, context: Context + ) -> Type | None: + """Infer __init__ argument type for an attribute. + + In particular, possibly use the signature of __set__. + """ + default = sym.type + if sym.implicit: + return default + t = get_proper_type(sym.type) + + # Perform a simple-minded inference from the signature of __set__, if present. + # We can't use mypy.checkmember here, since this plugin runs before type checking. + # We only support some basic scanerios here, which is hopefully sufficient for + # the vast majority of use cases. + if not isinstance(t, Instance): + return default + setter = t.type.get("__set__") + if setter: + if isinstance(setter.node, FuncDef): + super_info = t.type.get_containing_type_info("__set__") + assert super_info + if setter.type: + setter_type = get_proper_type( + map_type_from_supertype(setter.type, t.type, super_info) + ) + else: + return AnyType(TypeOfAny.unannotated) + if isinstance(setter_type, CallableType) and setter_type.arg_kinds == [ + ARG_POS, + ARG_POS, + ARG_POS, + ]: + return expand_type_by_instance(setter_type.arg_types[2], t) + else: + self._api.fail( + f'Unsupported signature for "__set__" in "{t.type.name}"', context + ) + else: + self._api.fail(f'Unsupported "__set__" in "{t.type.name}"', context) + + return default + def add_dataclass_tag(info: TypeInfo) -> None: # The value is ignored, only the existence matters. @@ -768,3 +973,136 @@ def _is_dataclasses_decorator(node: Node) -> bool: if isinstance(node, RefExpr): return node.fullname in dataclass_makers return False + + +def _has_direct_dataclass_transform_metaclass(info: TypeInfo) -> bool: + return ( + info.declared_metaclass is not None + and info.declared_metaclass.type.dataclass_transform_spec is not None + ) + + +def _get_expanded_dataclasses_fields( + ctx: FunctionSigContext, typ: ProperType, display_typ: ProperType, parent_typ: ProperType +) -> list[CallableType] | None: + """ + For a given type, determine what dataclasses it can be: for each class, return the field types. + For generic classes, the field types are expanded. + If the type contains Any or a non-dataclass, returns None; in the latter case, also reports an error. + """ + if isinstance(typ, UnionType): + ret: list[CallableType] | None = [] + for item in typ.relevant_items(): + item = get_proper_type(item) + item_types = _get_expanded_dataclasses_fields(ctx, item, item, parent_typ) + if ret is not None and item_types is not None: + ret += item_types + else: + ret = None # but keep iterating to emit all errors + return ret + elif isinstance(typ, TypeVarType): + return _get_expanded_dataclasses_fields( + ctx, get_proper_type(typ.upper_bound), display_typ, parent_typ + ) + elif isinstance(typ, Instance): + replace_sym = typ.type.get_method(_INTERNAL_REPLACE_SYM_NAME) + if replace_sym is None: + return None + replace_sig = replace_sym.type + assert isinstance(replace_sig, ProperType) + assert isinstance(replace_sig, CallableType) + return [expand_type_by_instance(replace_sig, typ)] + else: + return None + + +# TODO: we can potentially get the function signature hook to allow returning a union +# and leave this to the regular machinery of resolving a union of callables +# (https://github.com/python/mypy/issues/15457) +def _meet_replace_sigs(sigs: list[CallableType]) -> CallableType: + """ + Produces the lowest bound of the 'replace' signatures of multiple dataclasses. + """ + args = { + name: (typ, kind) + for name, typ, kind in zip(sigs[0].arg_names, sigs[0].arg_types, sigs[0].arg_kinds) + } + + for sig in sigs[1:]: + sig_args = { + name: (typ, kind) + for name, typ, kind in zip(sig.arg_names, sig.arg_types, sig.arg_kinds) + } + for name in (*args.keys(), *sig_args.keys()): + sig_typ, sig_kind = args.get(name, (UninhabitedType(), ARG_NAMED_OPT)) + sig2_typ, sig2_kind = sig_args.get(name, (UninhabitedType(), ARG_NAMED_OPT)) + args[name] = ( + meet_types(sig_typ, sig2_typ), + ARG_NAMED_OPT if sig_kind == sig2_kind == ARG_NAMED_OPT else ARG_NAMED, + ) + + return sigs[0].copy_modified( + arg_names=list(args.keys()), + arg_types=[typ for typ, _ in args.values()], + arg_kinds=[kind for _, kind in args.values()], + ) + + +def replace_function_sig_callback(ctx: FunctionSigContext) -> CallableType: + """ + Returns a signature for the 'dataclasses.replace' function that's dependent on the type + of the first positional argument. + """ + if len(ctx.args) != 2: + # Ideally the name and context should be callee's, but we don't have it in FunctionSigContext. + ctx.api.fail(f'"{ctx.default_signature.name}" has unexpected type annotation', ctx.context) + return ctx.default_signature + + if len(ctx.args[0]) != 1: + return ctx.default_signature # leave it to the type checker to complain + + obj_arg = ctx.args[0][0] + obj_type = get_proper_type(ctx.api.get_expression_type(obj_arg)) + inst_type_str = format_type_bare(obj_type, ctx.api.options) + + replace_sigs = _get_expanded_dataclasses_fields(ctx, obj_type, obj_type, obj_type) + if replace_sigs is None: + return ctx.default_signature + replace_sig = _meet_replace_sigs(replace_sigs) + + return replace_sig.copy_modified( + arg_names=[None, *replace_sig.arg_names], + arg_kinds=[ARG_POS, *replace_sig.arg_kinds], + arg_types=[obj_type, *replace_sig.arg_types], + ret_type=obj_type, + fallback=ctx.default_signature.fallback, + name=f"{ctx.default_signature.name} of {inst_type_str}", + ) + + +def is_processed_dataclass(info: TypeInfo) -> bool: + return bool(info) and "dataclass" in info.metadata + + +def check_post_init(api: TypeChecker, defn: FuncItem, info: TypeInfo) -> None: + if defn.type is None: + return + assert isinstance(defn.type, FunctionLike) + + ideal_sig_method = info.get_method(_INTERNAL_POST_INIT_SYM_NAME) + assert ideal_sig_method is not None and ideal_sig_method.type is not None + ideal_sig = ideal_sig_method.type + assert isinstance(ideal_sig, ProperType) # we set it ourselves + assert isinstance(ideal_sig, CallableType) + ideal_sig = ideal_sig.copy_modified(name="__post_init__") + + api.check_override( + override=defn.type, + original=ideal_sig, + name="__post_init__", + name_in_super="__post_init__", + supertype="dataclass", + original_class_or_static=False, + override_class_or_static=False, + node=defn, + ) diff --git a/mypy/plugins/default.py b/mypy/plugins/default.py index 4d6f46860939..170d3c85b5f9 100644 --- a/mypy/plugins/default.py +++ b/mypy/plugins/default.py @@ -10,6 +10,7 @@ AttributeContext, ClassDefContext, FunctionContext, + FunctionSigContext, MethodContext, MethodSigContext, Plugin, @@ -30,7 +31,9 @@ TypedDictType, TypeOfAny, TypeVarType, + UnionType, get_proper_type, + get_proper_types, ) @@ -40,10 +43,24 @@ class DefaultPlugin(Plugin): def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None: from mypy.plugins import ctypes, singledispatch - if fullname == "ctypes.Array": + if fullname == "_ctypes.Array": return ctypes.array_constructor_callback elif fullname == "functools.singledispatch": return singledispatch.create_singledispatch_function_callback + + return None + + def get_function_signature_hook( + self, fullname: str + ) -> Callable[[FunctionSigContext], FunctionLike] | None: + from mypy.plugins import attrs, dataclasses + + if fullname in ("attr.evolve", "attrs.evolve", "attr.assoc", "attrs.assoc"): + return attrs.evolve_function_sig_callback + elif fullname in ("attr.fields", "attrs.fields"): + return attrs.fields_function_sig_callback + elif fullname == "dataclasses.replace": + return dataclasses.replace_function_sig_callback return None def get_method_signature_hook( @@ -57,12 +74,21 @@ def get_method_signature_hook( return typed_dict_setdefault_signature_callback elif fullname in {n + ".pop" for n in TPDICT_FB_NAMES}: return typed_dict_pop_signature_callback - elif fullname in {n + ".update" for n in TPDICT_FB_NAMES}: - return typed_dict_update_signature_callback - elif fullname == "ctypes.Array.__setitem__": + elif fullname == "_ctypes.Array.__setitem__": return ctypes.array_setitem_callback elif fullname == singledispatch.SINGLEDISPATCH_CALLABLE_CALL_METHOD: return singledispatch.call_singledispatch_function_callback + + typed_dict_updates = set() + for n in TPDICT_FB_NAMES: + typed_dict_updates.add(n + ".update") + typed_dict_updates.add(n + ".__or__") + typed_dict_updates.add(n + ".__ror__") + typed_dict_updates.add(n + ".__ior__") + + if fullname in typed_dict_updates: + return typed_dict_update_signature_callback + return None def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | None: @@ -74,6 +100,8 @@ def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | No return int_pow_callback elif fullname == "builtins.int.__neg__": return int_neg_callback + elif fullname == "builtins.int.__pos__": + return int_pos_callback elif fullname in ("builtins.tuple.__mul__", "builtins.tuple.__rmul__"): return tuple_mul_callback elif fullname in {n + ".setdefault" for n in TPDICT_FB_NAMES}: @@ -82,9 +110,9 @@ def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | No return typed_dict_pop_callback elif fullname in {n + ".__delitem__" for n in TPDICT_FB_NAMES}: return typed_dict_delitem_callback - elif fullname == "ctypes.Array.__getitem__": + elif fullname == "_ctypes.Array.__getitem__": return ctypes.array_getitem_callback - elif fullname == "ctypes.Array.__iter__": + elif fullname == "_ctypes.Array.__iter__": return ctypes.array_iter_callback elif fullname == singledispatch.SINGLEDISPATCH_REGISTER_METHOD: return singledispatch.singledispatch_register_callback @@ -95,9 +123,9 @@ def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | No def get_attribute_hook(self, fullname: str) -> Callable[[AttributeContext], Type] | None: from mypy.plugins import ctypes, enums - if fullname == "ctypes.Array.value": + if fullname == "_ctypes.Array.value": return ctypes.array_value_callback - elif fullname == "ctypes.Array.raw": + elif fullname == "_ctypes.Array.raw": return ctypes.array_raw_callback elif fullname in enums.ENUM_NAME_ACCESS: return enums.enum_name_callback @@ -142,7 +170,9 @@ def get_class_decorator_hook_2( attrs.attr_class_maker_callback, auto_attribs_default=None, frozen_default=True ) elif fullname in attrs.attr_define_makers: - return partial(attrs.attr_class_maker_callback, auto_attribs_default=None) + return partial( + attrs.attr_class_maker_callback, auto_attribs_default=None, slots_default=True + ) return None @@ -382,13 +412,43 @@ def typed_dict_delitem_callback(ctx: MethodContext) -> Type: def typed_dict_update_signature_callback(ctx: MethodSigContext) -> CallableType: - """Try to infer a better signature type for TypedDict.update.""" + """Try to infer a better signature type for methods that update `TypedDict`. + + This includes: `TypedDict.update`, `TypedDict.__or__`, `TypedDict.__ror__`, + and `TypedDict.__ior__`. + """ signature = ctx.default_signature if isinstance(ctx.type, TypedDictType) and len(signature.arg_types) == 1: arg_type = get_proper_type(signature.arg_types[0]) - assert isinstance(arg_type, TypedDictType) + if not isinstance(arg_type, TypedDictType): + return signature arg_type = arg_type.as_anonymous() arg_type = arg_type.copy_modified(required_keys=set()) + if ctx.args and ctx.args[0]: + with ctx.api.msg.filter_errors(): + inferred = get_proper_type( + ctx.api.get_expression_type(ctx.args[0][0], type_context=arg_type) + ) + possible_tds = [] + if isinstance(inferred, TypedDictType): + possible_tds = [inferred] + elif isinstance(inferred, UnionType): + possible_tds = [ + t + for t in get_proper_types(inferred.relevant_items()) + if isinstance(t, TypedDictType) + ] + items = [] + for td in possible_tds: + item = arg_type.copy_modified( + required_keys=(arg_type.required_keys | td.required_keys) + & arg_type.items.keys() + ) + if not ctx.api.options.extra_checks: + item = item.copy_modified(item_names=list(td.items)) + items.append(item) + if items: + arg_type = make_simplified_union(items) return signature.copy_modified(arg_types=[arg_type]) return signature @@ -413,32 +473,43 @@ def int_pow_callback(ctx: MethodContext) -> Type: return ctx.default_return_type -def int_neg_callback(ctx: MethodContext) -> Type: - """Infer a more precise return type for int.__neg__. +def int_neg_callback(ctx: MethodContext, multiplier: int = -1) -> Type: + """Infer a more precise return type for int.__neg__ and int.__pos__. This is mainly used to infer the return type as LiteralType - if the original underlying object is a LiteralType object + if the original underlying object is a LiteralType object. """ if isinstance(ctx.type, Instance) and ctx.type.last_known_value is not None: value = ctx.type.last_known_value.value fallback = ctx.type.last_known_value.fallback if isinstance(value, int): if is_literal_type_like(ctx.api.type_context[-1]): - return LiteralType(value=-value, fallback=fallback) + return LiteralType(value=multiplier * value, fallback=fallback) else: return ctx.type.copy_modified( last_known_value=LiteralType( - value=-value, fallback=ctx.type, line=ctx.type.line, column=ctx.type.column + value=multiplier * value, + fallback=fallback, + line=ctx.type.line, + column=ctx.type.column, ) ) elif isinstance(ctx.type, LiteralType): value = ctx.type.value fallback = ctx.type.fallback if isinstance(value, int): - return LiteralType(value=-value, fallback=fallback) + return LiteralType(value=multiplier * value, fallback=fallback) return ctx.default_return_type +def int_pos_callback(ctx: MethodContext) -> Type: + """Infer a more precise return type for int.__pos__. + + This is identical to __neg__, except the value is not inverted. + """ + return int_neg_callback(ctx, +1) + + def tuple_mul_callback(ctx: MethodContext) -> Type: """Infer a more precise return type for tuple.__mul__ and tuple.__rmul__. diff --git a/mypy/plugins/enums.py b/mypy/plugins/enums.py index 1acf42d11ee6..83350fe2fe11 100644 --- a/mypy/plugins/enums.py +++ b/mypy/plugins/enums.py @@ -10,10 +10,10 @@ we actually bake some of it directly in to the semantic analysis layer (see semanal_enum.py). """ + from __future__ import annotations -from typing import Iterable, Sequence, TypeVar, cast -from typing_extensions import Final +from typing import Final, Iterable, Sequence, TypeVar, cast import mypy.plugin # To avoid circular imports. from mypy.nodes import TypeInfo @@ -167,11 +167,11 @@ class SomeEnum: for n in stnodes if n is None or not n.implicit ) - proper_types = list( + proper_types = [ _infer_value_type_with_auto_fallback(ctx, t) for t in node_types if t is None or not isinstance(t, CallableType) - ) + ] underlying_type = _first(proper_types) if underlying_type is None: return ctx.default_attr_type diff --git a/mypy/plugins/functools.py b/mypy/plugins/functools.py index eba4d77f2343..792ed6669503 100644 --- a/mypy/plugins/functools.py +++ b/mypy/plugins/functools.py @@ -1,8 +1,8 @@ """Plugin for supporting the functools standard library module.""" + from __future__ import annotations -from typing import NamedTuple -from typing_extensions import Final +from typing import Final, NamedTuple import mypy.plugin from mypy.nodes import ARG_POS, ARG_STAR2, Argument, FuncItem, Var diff --git a/misc/proper_plugin.py b/mypy/plugins/proper_plugin.py similarity index 94% rename from misc/proper_plugin.py rename to mypy/plugins/proper_plugin.py index a8a8e80ef360..a1fd05272b65 100644 --- a/misc/proper_plugin.py +++ b/mypy/plugins/proper_plugin.py @@ -1,3 +1,12 @@ +""" +This plugin is helpful for mypy development itself. +By default, it is not enabled for mypy users. + +It also can be used by plugin developers as a part of their CI checks. + +It finds missing ``get_proper_type()`` call, which can lead to multiple errors. +""" + from __future__ import annotations from typing import Callable @@ -8,7 +17,6 @@ from mypy.subtypes import is_proper_subtype from mypy.types import ( AnyType, - CallableType, FunctionLike, Instance, NoneTyp, @@ -88,6 +96,7 @@ def is_special_target(right: ProperType) -> bool: "mypy.types.UnpackType", "mypy.types.TypeVarTupleType", "mypy.types.ParamSpecType", + "mypy.types.Parameters", "mypy.types.RawExpressionType", "mypy.types.EllipsisType", "mypy.types.StarType", @@ -121,7 +130,7 @@ def is_dangerous_target(typ: ProperType) -> bool: """Is this a dangerous target (right argument) for an isinstance() check?""" if isinstance(typ, TupleType): return any(is_dangerous_target(get_proper_type(t)) for t in typ.items) - if isinstance(typ, CallableType) and typ.is_type_obj(): + if isinstance(typ, FunctionLike) and typ.is_type_obj(): return typ.type_object().has_base("mypy.types.Type") return False diff --git a/mypy/plugins/singledispatch.py b/mypy/plugins/singledispatch.py index cd6a3a9fa1cc..c5ce20233a0a 100644 --- a/mypy/plugins/singledispatch.py +++ b/mypy/plugins/singledispatch.py @@ -1,10 +1,11 @@ from __future__ import annotations -from typing import NamedTuple, Sequence, TypeVar, Union -from typing_extensions import Final, TypeAlias as _TypeAlias +from typing import Final, NamedTuple, Sequence, TypeVar, Union +from typing_extensions import TypeAlias as _TypeAlias from mypy.messages import format_type from mypy.nodes import ARG_POS, Argument, Block, ClassDef, Context, SymbolTable, TypeInfo, Var +from mypy.options import Options from mypy.plugin import CheckerPluginInterface, FunctionContext, MethodContext, MethodSigContext from mypy.plugins.common import add_method_to_class from mypy.subtypes import is_subtype @@ -99,7 +100,6 @@ def create_singledispatch_function_callback(ctx: FunctionContext) -> Type: """Called for functools.singledispatch""" func_type = get_proper_type(get_first_arg(ctx.arg_types)) if isinstance(func_type, CallableType): - if len(func_type.arg_kinds) < 1: fail( ctx, "Singledispatch function requires at least one argument", func_type.definition @@ -143,7 +143,7 @@ def singledispatch_register_callback(ctx: MethodContext) -> Type: return register_callable elif isinstance(first_arg_type, CallableType): # TODO: do more checking for registered functions - register_function(ctx, ctx.type, first_arg_type) + register_function(ctx, ctx.type, first_arg_type, ctx.api.options) # The typeshed stubs for register say that the function returned is Callable[..., T], even # though the function returned is the same as the one passed in. We return the type of the # function so that mypy can properly type check cases where the registered function is used @@ -155,7 +155,11 @@ def singledispatch_register_callback(ctx: MethodContext) -> Type: def register_function( - ctx: PluginContext, singledispatch_obj: Instance, func: Type, register_arg: Type | None = None + ctx: PluginContext, + singledispatch_obj: Instance, + func: Type, + options: Options, + register_arg: Type | None = None, ) -> None: """Register a function""" @@ -176,11 +180,10 @@ def register_function( fallback_dispatch_type = fallback.arg_types[0] if not is_subtype(dispatch_type, fallback_dispatch_type): - fail( ctx, "Dispatch type {} must be subtype of fallback function first argument {}".format( - format_type(dispatch_type), format_type(fallback_dispatch_type) + format_type(dispatch_type, options), format_type(fallback_dispatch_type, options) ), func.definition, ) @@ -203,7 +206,9 @@ def call_singledispatch_function_after_register_argument(ctx: MethodContext) -> type_args = RegisterCallableInfo(*register_callable.args) # type: ignore[arg-type] func = get_first_arg(ctx.arg_types) if func is not None: - register_function(ctx, type_args.singledispatch_obj, func, type_args.register_type) + register_function( + ctx, type_args.singledispatch_obj, func, ctx.api.options, type_args.register_type + ) # see call to register_function in the callback for register return func return ctx.default_return_type diff --git a/mypy/pyinfo.py b/mypy/pyinfo.py index 5929bfb696b5..f262ac8b2132 100644 --- a/mypy/pyinfo.py +++ b/mypy/pyinfo.py @@ -2,25 +2,27 @@ """Utilities to find the site and prefix information of a Python executable. -This file MUST remain compatible with all Python 3.7+ versions. Since we cannot make any assumptions about the -Python being executed, this module should not use *any* dependencies outside of the standard -library found in Python 3.7. This file is run each mypy run, so it should be kept as fast as -possible. +This file MUST remain compatible with all Python 3.8+ versions. Since we cannot make any +assumptions about the Python being executed, this module should not use *any* dependencies outside +of the standard library found in Python 3.8. This file is run each mypy run, so it should be kept +as fast as possible. """ -import os -import site import sys -import sysconfig if __name__ == "__main__": # HACK: We don't want to pick up mypy.types as the top-level types # module. This could happen if this file is run as a script. - # This workaround fixes it. - old_sys_path = sys.path - sys.path = sys.path[1:] - import types # noqa: F401 + # This workaround fixes this for Python versions before 3.11. + if sys.version_info < (3, 11): + old_sys_path = sys.path + sys.path = sys.path[1:] + import types # noqa: F401 + + sys.path = old_sys_path - sys.path = old_sys_path +import os +import site +import sysconfig def getsitepackages() -> list[str]: @@ -31,9 +33,7 @@ def getsitepackages() -> list[str]: if hasattr(site, "getusersitepackages") and site.ENABLE_USER_SITE: res.insert(0, site.getusersitepackages()) else: - from distutils.sysconfig import get_python_lib - - res = [get_python_lib()] + res = [sysconfig.get_paths()["purelib"]] return res diff --git a/mypy/reachability.py b/mypy/reachability.py index 8602fc645e2b..a25b9dff4581 100644 --- a/mypy/reachability.py +++ b/mypy/reachability.py @@ -2,8 +2,7 @@ from __future__ import annotations -from typing import Tuple, TypeVar -from typing_extensions import Final +from typing import Final, Tuple, TypeVar from mypy.literals import literal from mypy.nodes import ( diff --git a/mypy/refinfo.py b/mypy/refinfo.py new file mode 100644 index 000000000000..a5b92832bb7e --- /dev/null +++ b/mypy/refinfo.py @@ -0,0 +1,92 @@ +"""Find line-level reference information from a mypy AST (undocumented feature)""" + +from __future__ import annotations + +from mypy.nodes import ( + LDEF, + Expression, + FuncDef, + MemberExpr, + MypyFile, + NameExpr, + RefExpr, + SymbolNode, + TypeInfo, +) +from mypy.traverser import TraverserVisitor +from mypy.typeops import tuple_fallback +from mypy.types import ( + FunctionLike, + Instance, + TupleType, + Type, + TypeType, + TypeVarLikeType, + get_proper_type, +) + + +class RefInfoVisitor(TraverserVisitor): + def __init__(self, type_map: dict[Expression, Type]) -> None: + super().__init__() + self.type_map = type_map + self.data: list[dict[str, object]] = [] + + def visit_name_expr(self, expr: NameExpr) -> None: + super().visit_name_expr(expr) + self.record_ref_expr(expr) + + def visit_member_expr(self, expr: MemberExpr) -> None: + super().visit_member_expr(expr) + self.record_ref_expr(expr) + + def visit_func_def(self, func: FuncDef) -> None: + if func.expanded: + for item in func.expanded: + if isinstance(item, FuncDef): + super().visit_func_def(item) + else: + super().visit_func_def(func) + + def record_ref_expr(self, expr: RefExpr) -> None: + fullname = None + if expr.kind != LDEF and "." in expr.fullname: + fullname = expr.fullname + elif isinstance(expr, MemberExpr): + typ = self.type_map.get(expr.expr) + sym = None + if isinstance(expr.expr, RefExpr): + sym = expr.expr.node + if typ: + tfn = type_fullname(typ, sym) + if tfn: + fullname = f"{tfn}.{expr.name}" + if not fullname: + fullname = f"*.{expr.name}" + if fullname is not None: + self.data.append({"line": expr.line, "column": expr.column, "target": fullname}) + + +def type_fullname(typ: Type, node: SymbolNode | None = None) -> str | None: + typ = get_proper_type(typ) + if isinstance(typ, Instance): + return typ.type.fullname + elif isinstance(typ, TypeType): + return type_fullname(typ.item) + elif isinstance(typ, FunctionLike) and typ.is_type_obj(): + if isinstance(node, TypeInfo): + return node.fullname + return type_fullname(typ.fallback) + elif isinstance(typ, TupleType): + return type_fullname(tuple_fallback(typ)) + elif isinstance(typ, TypeVarLikeType): + return type_fullname(typ.upper_bound) + return None + + +def get_undocumented_ref_info_json( + tree: MypyFile, type_map: dict[Expression, Type] +) -> list[dict[str, object]]: + visitor = RefInfoVisitor(type_map) + tree.accept(visitor) + return visitor.data diff --git a/mypy/renaming.py b/mypy/renaming.py index 05b67f41ab85..8db336205960 100644 --- a/mypy/renaming.py +++ b/mypy/renaming.py @@ -1,8 +1,7 @@ from __future__ import annotations from contextlib import contextmanager -from typing import Iterator -from typing_extensions import Final +from typing import Final, Iterator from mypy.nodes import ( AssignmentStmt, @@ -182,6 +181,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: self.analyze_lvalue(lvalue) def visit_match_stmt(self, s: MatchStmt) -> None: + s.subject.accept(self) for i in range(len(s.patterns)): with self.enter_block(): s.patterns[i].accept(self) @@ -270,7 +270,7 @@ def flush_refs(self) -> None: This will be called at the end of a scope. """ is_func = self.scope_kinds[-1] == FUNCTION - for name, refs in self.refs[-1].items(): + for refs in self.refs[-1].values(): if len(refs) == 1: # Only one definition -- no renaming needed. continue diff --git a/mypy/report.py b/mypy/report.py index 75c372200ca3..764cfec7799a 100644 --- a/mypy/report.py +++ b/mypy/report.py @@ -12,8 +12,8 @@ import tokenize from abc import ABCMeta, abstractmethod from operator import attrgetter -from typing import Any, Callable, Dict, Iterator, Tuple, cast -from typing_extensions import Final, TypeAlias as _TypeAlias +from typing import Any, Callable, Dict, Final, Iterator, Tuple +from typing_extensions import TypeAlias as _TypeAlias from urllib.request import pathname2url from mypy import stats @@ -25,7 +25,7 @@ from mypy.version import __version__ try: - from lxml import etree # type: ignore[import] + from lxml import etree # type: ignore[import-untyped] LXML_INSTALLED = True except ImportError: @@ -44,7 +44,7 @@ ) ReporterClasses: _TypeAlias = Dict[ - str, Tuple[Callable[["Reports", str], "AbstractReporter"], bool], + str, Tuple[Callable[["Reports", str], "AbstractReporter"], bool] ] reporter_classes: Final[ReporterClasses] = {} @@ -99,7 +99,7 @@ class AbstractReporter(metaclass=ABCMeta): def __init__(self, reports: Reports, output_dir: str) -> None: self.output_dir = output_dir if output_dir != "": - stats.ensure_dir_exists(output_dir) + os.makedirs(output_dir, exist_ok=True) @abstractmethod def on_file( @@ -171,8 +171,12 @@ def on_file( ) -> None: # Count physical lines. This assumes the file's encoding is a # superset of ASCII (or at least uses \n in its line endings). - with open(tree.path, "rb") as f: - physical_lines = len(f.readlines()) + try: + with open(tree.path, "rb") as f: + physical_lines = len(f.readlines()) + except IsADirectoryError: + # can happen with namespace packages + physical_lines = 0 func_counter = FuncCounterVisitor() tree.accept(func_counter) @@ -704,8 +708,9 @@ def __init__(self, reports: Reports, output_dir: str) -> None: super().__init__(reports, output_dir) memory_reporter = reports.add_report("memory-xml", "") + assert isinstance(memory_reporter, MemoryXmlReporter) # The dependency will be called first. - self.memory_xml = cast(MemoryXmlReporter, memory_reporter) + self.memory_xml = memory_reporter class XmlReporter(AbstractXmlReporter): @@ -732,7 +737,7 @@ def on_file( if path.startswith(".."): return out_path = os.path.join(self.output_dir, "xml", path + ".xml") - stats.ensure_dir_exists(os.path.dirname(out_path)) + os.makedirs(os.path.dirname(out_path), exist_ok=True) last_xml.write(out_path, encoding="utf-8") def on_finish(self) -> None: @@ -777,7 +782,7 @@ def on_file( if path.startswith(".."): return out_path = os.path.join(self.output_dir, "html", path + ".html") - stats.ensure_dir_exists(os.path.dirname(out_path)) + os.makedirs(os.path.dirname(out_path), exist_ok=True) transformed_html = bytes(self.xslt_html(last_xml, ext=self.param_html)) with open(out_path, "wb") as out_file: out_file.write(transformed_html) @@ -859,7 +864,6 @@ def on_file( type_map: dict[Expression, Type], options: Options, ) -> None: - try: path = os.path.relpath(tree.path) except ValueError: diff --git a/mypy/scope.py b/mypy/scope.py index 19a690df8220..021dd9a7d8a5 100644 --- a/mypy/scope.py +++ b/mypy/scope.py @@ -21,6 +21,7 @@ def __init__(self) -> None: self.module: str | None = None self.classes: list[TypeInfo] = [] self.function: FuncBase | None = None + self.functions: list[FuncBase] = [] # Number of nested scopes ignored (that don't get their own separate targets) self.ignored = 0 @@ -65,12 +66,14 @@ def module_scope(self, prefix: str) -> Iterator[None]: @contextmanager def function_scope(self, fdef: FuncBase) -> Iterator[None]: + self.functions.append(fdef) if not self.function: self.function = fdef else: # Nested functions are part of the topmost function target. self.ignored += 1 yield + self.functions.pop() if self.ignored: # Leave a scope that's included in the enclosing target. self.ignored -= 1 @@ -78,6 +81,9 @@ def function_scope(self, fdef: FuncBase) -> Iterator[None]: assert self.function self.function = None + def outer_functions(self) -> list[FuncBase]: + return self.functions[:-1] + def enter_class(self, info: TypeInfo) -> None: """Enter a class target scope.""" if not self.function: diff --git a/mypy/semanal.py b/mypy/semanal.py index a57fa92a25cb..7dcd3e7b11db 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -51,12 +51,12 @@ from __future__ import annotations from contextlib import contextmanager -from typing import Any, Callable, Collection, Iterable, Iterator, List, TypeVar, cast -from typing_extensions import Final, TypeAlias as _TypeAlias +from typing import Any, Callable, Collection, Final, Iterable, Iterator, List, TypeVar, cast +from typing_extensions import TypeAlias as _TypeAlias, TypeGuard from mypy import errorcodes as codes, message_registry from mypy.constant_fold import constant_fold_expr -from mypy.errorcodes import ErrorCode +from mypy.errorcodes import PROPERTY_DECORATOR, ErrorCode from mypy.errors import Errors, report_internal_error from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type from mypy.messages import ( @@ -81,9 +81,13 @@ LDEF, MDEF, NOT_ABSTRACT, + PARAM_SPEC_KIND, REVEAL_LOCALS, REVEAL_TYPE, RUNTIME_PROTOCOL_DECOS, + TYPE_VAR_KIND, + TYPE_VAR_TUPLE_KIND, + VARIANCE_NOT_READY, ArgKind, AssertStmt, AssertTypeExpr, @@ -159,9 +163,11 @@ TupleExpr, TypeAlias, TypeAliasExpr, + TypeAliasStmt, TypeApplication, TypedDictExpr, TypeInfo, + TypeParam, TypeVarExpr, TypeVarLikeExpr, TypeVarTupleExpr, @@ -179,7 +185,7 @@ type_aliases_source_versions, typing_extensions_aliases, ) -from mypy.options import TYPE_VAR_TUPLE, Options +from mypy.options import Options from mypy.patterns import ( AsPattern, ClassPattern, @@ -216,6 +222,7 @@ calculate_tuple_fallback, find_dataclass_transform_spec, has_placeholder, + parse_bool, require_bool_literal_argument, set_callable_name as set_callable_name, ) @@ -223,18 +230,19 @@ from mypy.tvar_scope import TypeVarLikeScope from mypy.typeanal import ( SELF_TYPE_NAMES, + FindTypeVarVisitor, TypeAnalyser, + TypeVarDefaultTranslator, TypeVarLikeList, - TypeVarLikeQuery, analyze_type_alias, check_for_explicit_any, detect_diverging_alias, find_self_type, - fix_instance_types, + fix_instance, has_any_from_unimported_type, no_subscript_builtin_alias, - remove_dups, type_constructors, + validate_instance, ) from mypy.typeops import function_type, get_type_vars, try_getting_str_literals_from_type from mypy.types import ( @@ -242,12 +250,16 @@ DATACLASS_TRANSFORM_NAMES, FINAL_DECORATOR_NAMES, FINAL_TYPE_NAMES, + IMPORTED_REVEAL_TYPE_NAMES, NEVER_NAMES, OVERLOAD_NAMES, + OVERRIDE_DECORATOR_NAMES, PROTOCOL_NAMES, REVEAL_TYPE_NAMES, TPDICT_NAMES, TYPE_ALIAS_NAMES, + TYPE_CHECK_ONLY_NAMES, + TYPE_VAR_LIKE_NAMES, TYPED_NAMEDTUPLE_NAMES, AnyType, CallableType, @@ -268,25 +280,20 @@ TypeOfAny, TypeType, TypeVarLikeType, + TypeVarTupleType, TypeVarType, UnboundType, UnionType, UnpackType, get_proper_type, get_proper_types, - invalid_recursive_alias, is_named_instance, - store_argument_type, + remove_dups, + type_vars_as_args, ) +from mypy.types_utils import is_invalid_recursive_alias, store_argument_type from mypy.typevars import fill_typevars -from mypy.util import ( - correct_relative_import, - is_dunder, - is_typeshed_file, - module_prefix, - unmangle, - unnamed_function, -) +from mypy.util import correct_relative_import, is_dunder, module_prefix, unmangle, unnamed_function from mypy.visitor import NodeVisitor T = TypeVar("T") @@ -311,6 +318,14 @@ CORE_BUILTIN_CLASSES: Final = ["object", "bool", "function"] +# Python has several different scope/namespace kinds with subtly different semantics. +SCOPE_GLOBAL: Final = 0 # Module top level +SCOPE_CLASS: Final = 1 # Class body +SCOPE_FUNC: Final = 2 # Function or lambda +SCOPE_COMPREHENSION: Final = 3 # Comprehension or generator expression +SCOPE_ANNOTATION: Final = 4 # Annotation scopes for type parameters and aliases (PEP 695) + + # Used for tracking incomplete references Tag: _TypeAlias = int @@ -336,8 +351,8 @@ class SemanticAnalyzer( nonlocal_decls: list[set[str]] # Local names of function scopes; None for non-function scopes. locals: list[SymbolTable | None] - # Whether each scope is a comprehension scope. - is_comprehension_stack: list[bool] + # Type of each scope (SCOPE_*, indexes match locals) + scope_stack: list[int] # Nested block depths of scopes block_depth: list[int] # TypeInfo of directly enclosing class (or None) @@ -374,7 +389,7 @@ class SemanticAnalyzer( missing_names: list[set[str]] # Callbacks that will be called after semantic analysis to tweak things. patches: list[tuple[int, Callable[[], None]]] - loop_depth = 0 # Depth of breakable loops + loop_depth: list[int] # Depth of breakable loops cur_mod_id = "" # Current module id (or None) (phase 2) _is_stub_file = False # Are we analyzing a stub file? _is_typeshed_stub_file = False # Are we analyzing a typeshed stub file? @@ -411,7 +426,7 @@ def __init__( errors: Report analysis errors using this instance """ self.locals = [None] - self.is_comprehension_stack = [False] + self.scope_stack = [SCOPE_GLOBAL] # Saved namespaces from previous iteration. Every top-level function/method body is # analyzed in several iterations until all names are resolved. We need to save # the local namespaces for the top level function and all nested functions between @@ -427,7 +442,7 @@ def __init__( self.tvar_scope = TypeVarLikeScope() self.function_stack = [] self.block_depth = [0] - self.loop_depth = 0 + self.loop_depth = [0] self.errors = errors self.modules = modules self.msg = MessageBuilder(errors, modules) @@ -510,6 +525,7 @@ def prepare_typing_namespace(self, file_node: MypyFile, aliases: dict[str, str]) They will be replaced with real aliases when corresponding targets are ready. """ + # This is all pretty unfortunate. typeshed now has a # sys.version_info check for OrderedDict, and we shouldn't # take it out, because it is correct and a typechecker should @@ -693,7 +709,10 @@ def add_builtin_aliases(self, tree: MypyFile) -> None: """ assert tree.fullname == "typing" for alias, target_name in type_aliases.items(): - if type_aliases_source_versions[alias] > self.options.python_version: + if ( + alias in type_aliases_source_versions + and type_aliases_source_versions[alias] > self.options.python_version + ): # This alias is not available on this Python version. continue name = alias.split(".")[-1] @@ -732,7 +751,9 @@ def create_alias(self, tree: MypyFile, target_name: str, alias: str, name: str) target = self.named_type_or_none(target_name, []) assert target is not None # Transform List to List[Any], etc. - fix_instance_types(target, self.fail, self.note, self.options.python_version) + fix_instance( + target, self.fail, self.note, disallow_any=False, options=self.options + ) alias_node = TypeAlias( target, alias, @@ -783,13 +804,11 @@ def file_context( self.cur_mod_id = file_node.fullname with scope.module_scope(self.cur_mod_id): self._is_stub_file = file_node.path.lower().endswith(".pyi") - self._is_typeshed_stub_file = is_typeshed_file( - options.abs_custom_typeshed_dir, file_node.path - ) + self._is_typeshed_stub_file = file_node.is_typeshed_file(options) self.globals = file_node.names self.tvar_scope = TypeVarLikeScope() - self.named_tuple_analyzer = NamedTupleAnalyzer(options, self) + self.named_tuple_analyzer = NamedTupleAnalyzer(options, self, self.msg) self.typed_dict_analyzer = TypedDictAnalyzer(options, self, self.msg) self.enum_call_analyzer = EnumCallAnalyzer(options, self) self.newtype_analyzer = NewTypeAnalyzer(options, self, self.msg) @@ -799,6 +818,7 @@ def file_context( self.num_incomplete_refs = 0 if active_type: + self.push_type_args(active_type.defn.type_args, active_type.defn) self.incomplete_type_stack.append(False) scope.enter_class(active_type) self.enter_class(active_type.defn.info) @@ -812,6 +832,7 @@ def file_context( self.leave_class() self._type = None self.incomplete_type_stack.pop() + self.pop_type_args(active_type.defn.type_args) del self.options # @@ -847,6 +868,10 @@ def visit_func_def(self, defn: FuncDef) -> None: self.analyze_func_def(defn) def analyze_func_def(self, defn: FuncDef) -> None: + if self.push_type_args(defn.type_args, defn) is None: + self.defer(defn) + return + self.function_stack.append(defn) if defn.type: @@ -880,15 +905,13 @@ def analyze_func_def(self, defn: FuncDef) -> None: # Don't store not ready types (including placeholders). if self.found_incomplete_ref(tag) or has_placeholder(result): self.defer(defn) + # TODO: pop type args return assert isinstance(result, ProperType) if isinstance(result, CallableType): # type guards need to have a positional argument, to spec - if ( - result.type_guard - and ARG_POS not in result.arg_kinds[self.is_class_scope() :] - and not defn.is_static - ): + skip_self = self.is_class_scope() and not defn.is_static + if result.type_guard and ARG_POS not in result.arg_kinds[skip_self:]: self.fail( "TypeGuard functions must have a positional argument", result, @@ -896,6 +919,13 @@ def analyze_func_def(self, defn: FuncDef) -> None: ) # in this case, we just kind of just ... remove the type guard. result = result.copy_modified(type_guard=None) + if result.type_is and ARG_POS not in result.arg_kinds[skip_self:]: + self.fail( + '"TypeIs" functions must have a positional argument', + result, + code=codes.VALID_TYPE, + ) + result = result.copy_modified(type_is=None) result = self.remove_unpack_kwargs(defn, result) if has_self_type and self.type is not None: @@ -951,15 +981,17 @@ def analyze_func_def(self, defn: FuncDef) -> None: defn.type = defn.type.copy_modified(ret_type=ret_type) self.wrapped_coro_return_types[defn] = defn.type + self.pop_type_args(defn.type_args) + def remove_unpack_kwargs(self, defn: FuncDef, typ: CallableType) -> CallableType: if not typ.arg_kinds or typ.arg_kinds[-1] is not ArgKind.ARG_STAR2: return typ - last_type = get_proper_type(typ.arg_types[-1]) + last_type = typ.arg_types[-1] if not isinstance(last_type, UnpackType): return typ last_type = get_proper_type(last_type.type) if not isinstance(last_type, TypedDictType): - self.fail("Unpack item in ** argument must be a TypedDict", defn) + self.fail("Unpack item in ** argument must be a TypedDict", last_type) new_arg_types = typ.arg_types[:-1] + [AnyType(TypeOfAny.from_error)] return typ.copy_modified(arg_types=new_arg_types) overlap = set(typ.arg_names) & set(last_type.items) @@ -976,9 +1008,11 @@ def remove_unpack_kwargs(self, defn: FuncDef, typ: CallableType) -> CallableType def prepare_method_signature(self, func: FuncDef, info: TypeInfo, has_self_type: bool) -> None: """Check basic signature validity and tweak annotation of self/cls argument.""" - # Only non-static methods are special. + # Only non-static methods are special, as well as __new__. functype = func.type - if not func.is_static: + if func.name == "__new__": + func.is_static = True + if not func.is_static or func.name == "__new__": if func.name in ["__init_subclass__", "__class_getitem__"]: func.is_class = True if not func.arguments: @@ -1025,7 +1059,21 @@ def is_expected_self_type(self, typ: Type, is_classmethod: bool) -> bool: return self.is_expected_self_type(typ.item, is_classmethod=False) if isinstance(typ, UnboundType): sym = self.lookup_qualified(typ.name, typ, suppress_errors=True) - if sym is not None and sym.fullname == "typing.Type" and typ.args: + if ( + sym is not None + and ( + sym.fullname == "typing.Type" + or ( + sym.fullname == "builtins.type" + and ( + self.is_stub_file + or self.is_future_flag_set("annotations") + or self.options.python_version >= (3, 9) + ) + ) + ) + and typ.args + ): return self.is_expected_self_type(typ.args[0], is_classmethod=False) return False if isinstance(typ, TypeVarType): @@ -1092,7 +1140,14 @@ def setup_self_type(self) -> None: ) else: return - info.self_type = TypeVarType("Self", f"{info.fullname}.Self", 0, [], fill_typevars(info)) + info.self_type = TypeVarType( + "Self", + f"{info.fullname}.Self", + id=0, + values=[], + upper_bound=fill_typevars(info), + default=AnyType(TypeOfAny.from_omitted_generics), + ) def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: self.statement = defn @@ -1147,7 +1202,16 @@ def analyze_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: elif not non_overload_indexes: self.handle_missing_overload_implementation(defn) - if types: + if types and not any( + # If some overload items are decorated with other decorators, then + # the overload type will be determined during type checking. + isinstance(it, Decorator) and len(it.decorators) > 1 + for it in defn.items + ): + # TODO: should we enforce decorated overloads consistency somehow? + # Some existing code uses both styles: + # * Put decorator only on implementation, use "effective" types in overloads + # * Put decorator everywhere, use "bare" types in overloads. defn.type = Overloaded(types) defn.type.line = defn.line @@ -1215,6 +1279,9 @@ def analyze_overload_sigs_and_impl( types.append(callable) if item.var.is_property: self.fail("An overload can not be a property", item) + # If any item was decorated with `@override`, the whole overload + # becomes an explicit override. + defn.is_explicit_override |= item.func.is_explicit_override elif isinstance(item, FuncDef): if i == len(defn.items) - 1 and not self.is_stub_file: impl = item @@ -1331,7 +1398,8 @@ def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) - """ defn.is_property = True items = defn.items - first_item = cast(Decorator, defn.items[0]) + first_item = defn.items[0] + assert isinstance(first_item, Decorator) deleted_items = [] for i, item in enumerate(items[1:]): if isinstance(item, Decorator): @@ -1343,6 +1411,8 @@ def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) - first_item.var.is_settable_property = True # Get abstractness from the original definition. item.func.abstract_status = first_item.func.abstract_status + if node.name == "deleter": + item.func.abstract_status = first_item.func.abstract_status else: self.fail( f"Only supported top decorator is @{first_item.func.name}.setter", item @@ -1374,7 +1444,8 @@ def analyze_function_body(self, defn: FuncItem) -> None: # Bind the type variables again to visit the body. if defn.type: a = self.type_analyzer() - typ = cast(CallableType, defn.type) + typ = defn.type + assert isinstance(typ, CallableType) a.bind_function_type_variables(typ, defn) for i in range(len(typ.arg_types)): store_argument_type(defn, i, typ, self.named_type) @@ -1386,8 +1457,11 @@ def analyze_function_body(self, defn: FuncItem) -> None: # The first argument of a non-static, non-class method is like 'self' # (though the name could be different), having the enclosing class's # instance type. - if is_method and not defn.is_static and not defn.is_class and defn.arguments: - defn.arguments[0].variable.is_self = True + if is_method and (not defn.is_static or defn.name == "__new__") and defn.arguments: + if not defn.is_class: + defn.arguments[0].variable.is_self = True + else: + defn.arguments[0].variable.is_cls = True defn.body.accept(self) self.function_stack.pop() @@ -1509,6 +1583,10 @@ def visit_decorator(self, dec: Decorator) -> None: dec.func.is_class = True dec.var.is_classmethod = True self.check_decorated_function_is_method("classmethod", dec) + elif refers_to_fullname(d, OVERRIDE_DECORATOR_NAMES): + removed.append(i) + dec.func.is_explicit_override = True + self.check_decorated_function_is_method("override", dec) elif refers_to_fullname( d, ( @@ -1540,6 +1618,9 @@ def visit_decorator(self, dec: Decorator) -> None: removed.append(i) else: self.fail("@final cannot be used with non-method functions", d) + elif refers_to_fullname(d, TYPE_CHECK_ONLY_NAMES): + # TODO: support `@overload` funcs. + dec.func.is_type_check_only = True elif isinstance(d, CallExpr) and refers_to_fullname( d.callee, DATACLASS_TRANSFORM_NAMES ): @@ -1556,11 +1637,15 @@ def visit_decorator(self, dec: Decorator) -> None: if not no_type_check and self.recurse_into_functions: dec.func.accept(self) if could_be_decorated_property and dec.decorators and dec.var.is_property: - self.fail("Decorators on top of @property are not supported", dec) + self.fail( + "Decorators on top of @property are not supported", dec, code=PROPERTY_DECORATOR + ) if (dec.func.is_static or dec.func.is_class) and dec.var.is_property: self.fail("Only instance methods can be decorated with @property", dec) if dec.func.abstract_status == IS_ABSTRACT and dec.func.is_final: self.fail(f"Method {dec.func.name} is both abstract and final", dec) + if dec.func.is_static and dec.func.is_class: + self.fail(message_registry.CLASS_PATTERN_CLASS_OR_STATIC_METHOD, dec) def check_decorated_function_is_method(self, decorator: str, context: Context) -> None: if not self.type or self.is_func_scope(): @@ -1575,9 +1660,99 @@ def visit_class_def(self, defn: ClassDef) -> None: self.incomplete_type_stack.append(not defn.info) namespace = self.qualified_name(defn.name) with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)): + if self.push_type_args(defn.type_args, defn) is None: + self.mark_incomplete(defn.name, defn) + return + self.analyze_class(defn) + self.pop_type_args(defn.type_args) self.incomplete_type_stack.pop() + def push_type_args( + self, type_args: list[TypeParam] | None, context: Context + ) -> list[tuple[str, TypeVarLikeExpr]] | None: + if not type_args: + return [] + self.locals.append(SymbolTable()) + self.scope_stack.append(SCOPE_ANNOTATION) + tvs: list[tuple[str, TypeVarLikeExpr]] = [] + for p in type_args: + tv = self.analyze_type_param(p) + if tv is None: + return None + tvs.append((p.name, tv)) + + for name, tv in tvs: + if self.is_defined_type_param(name): + self.fail(f'"{name}" already defined as a type parameter', context) + else: + self.add_symbol(name, tv, context, no_progress=True, type_param=True) + + return tvs + + def is_defined_type_param(self, name: str) -> bool: + for names in self.locals: + if names is None: + continue + if name in names: + node = names[name].node + if isinstance(node, TypeVarLikeExpr): + return True + return False + + def analyze_type_param(self, type_param: TypeParam) -> TypeVarLikeExpr | None: + fullname = self.qualified_name(type_param.name) + if type_param.upper_bound: + upper_bound = self.anal_type(type_param.upper_bound) + if upper_bound is None: + return None + else: + upper_bound = self.named_type("builtins.object") + default = AnyType(TypeOfAny.from_omitted_generics) + if type_param.kind == TYPE_VAR_KIND: + values = [] + if type_param.values: + for value in type_param.values: + analyzed = self.anal_type(value) + if analyzed is None: + return None + values.append(analyzed) + return TypeVarExpr( + name=type_param.name, + fullname=fullname, + values=values, + upper_bound=upper_bound, + default=default, + variance=VARIANCE_NOT_READY, + is_new_style=True, + ) + elif type_param.kind == PARAM_SPEC_KIND: + return ParamSpecExpr( + name=type_param.name, + fullname=fullname, + upper_bound=upper_bound, + default=default, + is_new_style=True, + ) + else: + assert type_param.kind == TYPE_VAR_TUPLE_KIND + tuple_fallback = self.named_type("builtins.tuple", [self.object_type()]) + return TypeVarTupleExpr( + name=type_param.name, + fullname=fullname, + # Upper bound for *Ts is *tuple[object, ...], it can never be object. + upper_bound=tuple_fallback.copy_modified(), + tuple_fallback=tuple_fallback, + default=default, + is_new_style=True, + ) + + def pop_type_args(self, type_args: list[TypeParam] | None) -> None: + if not type_args: + return + self.locals.pop() + self.scope_stack.pop() + def analyze_class(self, defn: ClassDef) -> None: fullname = self.qualified_name(defn.name) if not defn.info and not self.is_core_builtin_class(defn): @@ -1608,6 +1783,11 @@ def analyze_class(self, defn: ClassDef) -> None: # Some type variable bounds or values are not ready, we need # to re-analyze this class. self.defer() + if has_placeholder(tvd.default): + # Placeholder values in TypeVarLikeTypes may get substituted in. + # Defer current target until they are ready. + self.mark_incomplete(defn.name, defn) + return self.analyze_class_keywords(defn) bases_result = self.analyze_base_classes(bases) @@ -1653,8 +1833,18 @@ def analyze_class(self, defn: ClassDef) -> None: defn.info.is_protocol = is_protocol self.recalculate_metaclass(defn, declared_metaclass) defn.info.runtime_protocol = False + + if defn.type_args: + # PEP 695 type parameters are not in scope in class decorators, so + # temporarily disable type parameter namespace. + type_params_names = self.locals.pop() + self.scope_stack.pop() for decorator in defn.decorators: self.analyze_class_decorator(defn, decorator) + if defn.type_args: + self.locals.append(type_params_names) + self.scope_stack.append(SCOPE_ANNOTATION) + self.analyze_class_body_common(defn) def setup_type_vars(self, defn: ClassDef, tvar_defs: list[TypeVarLikeType]) -> None: @@ -1666,12 +1856,17 @@ def setup_type_vars(self, defn: ClassDef, tvar_defs: list[TypeVarLikeType]) -> N def setup_alias_type_vars(self, defn: ClassDef) -> None: assert defn.info.special_alias is not None defn.info.special_alias.alias_tvars = list(defn.type_vars) + # It is a bit unfortunate that we need to inline some logic from TypeAlias constructor, + # but it is required, since type variables may change during semantic analyzer passes. + for i, t in enumerate(defn.type_vars): + if isinstance(t, TypeVarTupleType): + defn.info.special_alias.tvar_tuple_index = i target = defn.info.special_alias.target assert isinstance(target, ProperType) if isinstance(target, TypedDictType): - target.fallback.args = tuple(defn.type_vars) + target.fallback.args = type_vars_as_args(defn.type_vars) elif isinstance(target, TupleType): - target.partial_fallback.args = tuple(defn.type_vars) + target.partial_fallback.args = type_vars_as_args(defn.type_vars) else: assert False, f"Unexpected special alias type: {type(target)}" @@ -1681,6 +1876,8 @@ def is_core_builtin_class(self, defn: ClassDef) -> bool: def analyze_class_body_common(self, defn: ClassDef) -> None: """Parts of class body analysis that are common to all kinds of class defs.""" self.enter_class(defn.info) + if any(b.self_type is not None for b in defn.info.mro): + self.setup_self_type() defn.defs.accept(self) self.apply_class_plugin_hooks(defn) self.leave_class() @@ -1697,13 +1894,12 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> bool: if is_typeddict: for decorator in defn.decorators: decorator.accept(self) - if isinstance(decorator, RefExpr): - if decorator.fullname in FINAL_DECORATOR_NAMES and info is not None: - info.is_final = True + if info is not None: + self.analyze_class_decorator_common(defn, info, decorator) if info is None: self.mark_incomplete(defn.name, defn) else: - self.prepare_class_def(defn, info) + self.prepare_class_def(defn, info, custom_names=True) return True return False @@ -1733,6 +1929,9 @@ def analyze_namedtuple_classdef( self.setup_type_vars(defn, tvar_defs) self.setup_alias_type_vars(defn) with self.scope.class_scope(defn.info): + for deco in defn.decorators: + deco.accept(self) + self.analyze_class_decorator_common(defn, defn.info, deco) with self.named_tuple_analyzer.save_namedtuple_body(info): self.analyze_class_body_common(defn) return True @@ -1797,34 +1996,47 @@ def enter_class(self, info: TypeInfo) -> None: # Remember previous active class self.type_stack.append(self.type) self.locals.append(None) # Add class scope - self.is_comprehension_stack.append(False) + self.scope_stack.append(SCOPE_CLASS) self.block_depth.append(-1) # The class body increments this to 0 + self.loop_depth.append(0) self._type = info self.missing_names.append(set()) def leave_class(self) -> None: """Restore analyzer state.""" self.block_depth.pop() + self.loop_depth.pop() self.locals.pop() - self.is_comprehension_stack.pop() + self.scope_stack.pop() self._type = self.type_stack.pop() self.missing_names.pop() def analyze_class_decorator(self, defn: ClassDef, decorator: Expression) -> None: decorator.accept(self) + self.analyze_class_decorator_common(defn, defn.info, decorator) if isinstance(decorator, RefExpr): if decorator.fullname in RUNTIME_PROTOCOL_DECOS: if defn.info.is_protocol: defn.info.runtime_protocol = True else: self.fail("@runtime_checkable can only be used with protocol classes", defn) - elif decorator.fullname in FINAL_DECORATOR_NAMES: - defn.info.is_final = True elif isinstance(decorator, CallExpr) and refers_to_fullname( decorator.callee, DATACLASS_TRANSFORM_NAMES ): defn.info.dataclass_transform_spec = self.parse_dataclass_transform_spec(decorator) + def analyze_class_decorator_common( + self, defn: ClassDef, info: TypeInfo, decorator: Expression + ) -> None: + """Common method for applying class decorators. + + Called on regular classes, typeddicts, and namedtuples. + """ + if refers_to_fullname(decorator, FINAL_DECORATOR_NAMES): + info.is_final = True + elif refers_to_fullname(decorator, TYPE_CHECK_ONLY_NAMES): + info.is_type_check_only = True + def clean_up_bases_and_infer_type_variables( self, defn: ClassDef, base_type_exprs: list[Expression], context: Context ) -> tuple[list[Expression], list[TypeVarLikeType], bool]: @@ -1844,6 +2056,13 @@ class Foo(Bar, Generic[T]): ... removed: list[int] = [] declared_tvars: TypeVarLikeList = [] is_protocol = False + if defn.type_args is not None: + for p in defn.type_args: + node = self.lookup(p.name, context) + assert node is not None + assert isinstance(node.node, TypeVarLikeExpr) + declared_tvars.append((p.name, node.node)) + for i, base_expr in enumerate(base_type_exprs): if isinstance(base_expr, StarExpr): base_expr.valid = True @@ -1892,8 +2111,19 @@ class Foo(Bar, Generic[T]): ... defn.removed_base_type_exprs.append(defn.base_type_exprs[i]) del base_type_exprs[i] tvar_defs: list[TypeVarLikeType] = [] + last_tvar_name_with_default: str | None = None for name, tvar_expr in declared_tvars: + tvar_expr.default = tvar_expr.default.accept( + TypeVarDefaultTranslator(self, tvar_expr.name, context) + ) tvar_def = self.tvar_scope.bind_new(name, tvar_expr) + if last_tvar_name_with_default is not None and not tvar_def.has_default(): + self.msg.tvar_without_default_type( + tvar_def.name, last_tvar_name_with_default, context + ) + tvar_def.default = AnyType(TypeOfAny.from_error) + elif tvar_def.has_default(): + last_tvar_name_with_default = tvar_def.name tvar_defs.append(tvar_def) return base_type_exprs, tvar_defs, is_protocol @@ -1936,38 +2166,48 @@ def analyze_class_typevar_declaration(self, base: Type) -> tuple[TypeVarLikeList return None def analyze_unbound_tvar(self, t: Type) -> tuple[str, TypeVarLikeExpr] | None: - if not isinstance(t, UnboundType): - return None - unbound = t - sym = self.lookup_qualified(unbound.name, unbound) + if isinstance(t, UnpackType) and isinstance(t.type, UnboundType): + return self.analyze_unbound_tvar_impl(t.type, is_unpacked=True) + if isinstance(t, UnboundType): + sym = self.lookup_qualified(t.name, t) + if sym and sym.fullname in ("typing.Unpack", "typing_extensions.Unpack"): + inner_t = t.args[0] + if isinstance(inner_t, UnboundType): + return self.analyze_unbound_tvar_impl(inner_t, is_unpacked=True) + return None + return self.analyze_unbound_tvar_impl(t) + return None + + def analyze_unbound_tvar_impl( + self, t: UnboundType, is_unpacked: bool = False, is_typealias_param: bool = False + ) -> tuple[str, TypeVarLikeExpr] | None: + assert not is_unpacked or not is_typealias_param, "Mutually exclusive conditions" + sym = self.lookup_qualified(t.name, t) if sym and isinstance(sym.node, PlaceholderNode): self.record_incomplete_ref() - if sym and isinstance(sym.node, ParamSpecExpr): + if not is_unpacked and sym and isinstance(sym.node, ParamSpecExpr): if sym.fullname and not self.tvar_scope.allow_binding(sym.fullname): # It's bound by our type variable scope return None - return unbound.name, sym.node - if sym and sym.fullname in ("typing.Unpack", "typing_extensions.Unpack"): - inner_t = unbound.args[0] - if not isinstance(inner_t, UnboundType): + return t.name, sym.node + if (is_unpacked or is_typealias_param) and sym and isinstance(sym.node, TypeVarTupleExpr): + if sym.fullname and not self.tvar_scope.allow_binding(sym.fullname): + # It's bound by our type variable scope return None - inner_unbound = inner_t - inner_sym = self.lookup_qualified(inner_unbound.name, inner_unbound) - if inner_sym and isinstance(inner_sym.node, PlaceholderNode): - self.record_incomplete_ref() - if inner_sym and isinstance(inner_sym.node, TypeVarTupleExpr): - if inner_sym.fullname and not self.tvar_scope.allow_binding(inner_sym.fullname): - # It's bound by our type variable scope - return None - return inner_unbound.name, inner_sym.node - if sym is None or not isinstance(sym.node, TypeVarExpr): + return t.name, sym.node + if sym is None or not isinstance(sym.node, TypeVarExpr) or is_unpacked: return None elif sym.fullname and not self.tvar_scope.allow_binding(sym.fullname): # It's bound by our type variable scope return None else: assert isinstance(sym.node, TypeVarExpr) - return unbound.name, sym.node + return t.name, sym.node + + def find_type_var_likes(self, t: Type) -> TypeVarLikeList: + visitor = FindTypeVarVisitor(self, self.tvar_scope) + t.accept(visitor) + return visitor.type_var_likes def get_all_bases_tvars( self, base_type_exprs: list[Expression], removed: list[int] @@ -1981,7 +2221,7 @@ def get_all_bases_tvars( except TypeTranslationError: # This error will be caught later. continue - base_tvars = base.accept(TypeVarLikeQuery(self, self.tvar_scope)) + base_tvars = self.find_type_var_likes(base) tvars.extend(base_tvars) return remove_dups(tvars) @@ -1999,7 +2239,7 @@ def get_and_bind_all_tvars(self, type_exprs: list[Expression]) -> list[TypeVarLi except TypeTranslationError: # This error will be caught later. continue - base_tvars = base.accept(TypeVarLikeQuery(self, self.tvar_scope)) + base_tvars = self.find_type_var_likes(base) tvars.extend(base_tvars) tvars = remove_dups(tvars) # Variables are defined in order of textual appearance. tvar_defs = [] @@ -2047,8 +2287,9 @@ def prepare_class_def( # Preserve name from previous fine-grained incremental run. global_name = defn.info.name defn.fullname = defn.info._fullname - if defn.info.is_named_tuple: - # Named tuple nested within a class is stored in the class symbol table. + if defn.info.is_named_tuple or defn.info.typeddict_type: + # Named tuples and Typed dicts nested within a class are stored + # in the class symbol table. self.add_symbol_skip_local(global_name, defn.info) else: self.globals[global_name] = SymbolTableNode(GDEF, defn.info) @@ -2098,8 +2339,16 @@ def analyze_base_classes( if ( isinstance(base_expr, RefExpr) and base_expr.fullname in TYPED_NAMEDTUPLE_NAMES + TPDICT_NAMES + ) or ( + isinstance(base_expr, CallExpr) + and isinstance(base_expr.callee, RefExpr) + and base_expr.callee.fullname in TPDICT_NAMES ): # Ignore magic bases for now. + # For example: + # class Foo(TypedDict): ... # RefExpr + # class Foo(NamedTuple): ... # RefExpr + # class Foo(TypedDict("Foo", {"a": int})): ... # CallExpr continue try: @@ -2180,6 +2429,10 @@ def configure_base_classes( if not self.verify_base_classes(defn): self.set_dummy_mro(defn.info) return + if not self.verify_duplicate_base_classes(defn): + # We don't want to block the typechecking process, + # so, we just insert `Any` as the base class and show an error. + self.set_any_mro(defn.info) self.calculate_class_mro(defn, self.object_type) def configure_tuple_base_class(self, defn: ClassDef, base: TupleType) -> Instance: @@ -2209,6 +2462,11 @@ def set_dummy_mro(self, info: TypeInfo) -> None: info.mro = [info, self.object_type().type] info.bad_mro = True + def set_any_mro(self, info: TypeInfo) -> None: + # Give it an MRO consisting direct `Any` subclass. + info.fallback_to_any = True + info.mro = [info, self.object_type().type] + def calculate_class_mro( self, defn: ClassDef, obj_type: Callable[[], Instance] | None = None ) -> None: @@ -2289,12 +2547,14 @@ def verify_base_classes(self, defn: ClassDef) -> bool: if self.is_base_class(info, baseinfo): self.fail("Cycle in inheritance hierarchy", defn) cycle = True - dup = find_duplicate(info.direct_base_classes()) - if dup: - self.fail(f'Duplicate base class "{dup.name}"', defn, blocker=True) - return False return not cycle + def verify_duplicate_base_classes(self, defn: ClassDef) -> bool: + dup = find_duplicate(defn.info.direct_base_classes()) + if dup: + self.fail(f'Duplicate base class "{dup.name}"', defn) + return not dup + def is_base_class(self, t: TypeInfo, s: TypeInfo) -> bool: """Determine if t is a base class of s (but do not use mro).""" # Search the base class graph for t, starting from s. @@ -2402,7 +2662,7 @@ def visit_import(self, i: Import) -> None: if as_id is not None: base_id = id imported_id = as_id - module_public = use_implicit_reexport or id.split(".")[-1] == as_id + module_public = use_implicit_reexport or id == as_id else: base_id = id.split(".")[0] imported_id = base_id @@ -2475,12 +2735,7 @@ def visit_import_from(self, imp: ImportFrom) -> None: elif fullname in self.missing_modules: missing_submodule = True # If it is still not resolved, check for a module level __getattr__ - if ( - module - and not node - and (module.is_stub or self.options.python_version >= (3, 7)) - and "__getattr__" in module.names - ): + if module and not node and "__getattr__" in module.names: # We store the fullname of the original definition so that we can # detect whether two imported names refer to the same thing. fullname = module_id + "." + id @@ -2630,11 +2885,14 @@ def report_missing_module_attribute( typing_extensions = self.modules.get("typing_extensions") if typing_extensions and source_id in typing_extensions.names: self.msg.note( - f"Use `from typing_extensions import {source_id}` instead", context + f"Use `from typing_extensions import {source_id}` instead", + context, + code=codes.ATTR_DEFINED, ) self.msg.note( "See https://mypy.readthedocs.io/en/stable/runtime_troubles.html#using-new-additions-to-the-typing-module", context, + code=codes.ATTR_DEFINED, ) def process_import_over_existing_name( @@ -2723,8 +2981,8 @@ class C: [(j := i) for i in [1, 2, 3]] is a syntax error that is not enforced by Python parser, but at later steps. """ - for i, is_comprehension in enumerate(reversed(self.is_comprehension_stack)): - if not is_comprehension and i < len(self.locals) - 1: + for i, scope_type in enumerate(reversed(self.scope_stack)): + if scope_type != SCOPE_COMPREHENSION and i < len(self.locals) - 1: if self.locals[-1 - i] is None: self.fail( "Assignment expression within a comprehension" @@ -2758,6 +3016,10 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: with self.allow_unbound_tvars_set(): s.rvalue.accept(self) self.basic_type_applications = old_basic_type_applications + elif self.can_possibly_be_typevarlike_declaration(s): + # Allow unbound tvars inside TypeVarLike defaults to be evaluated later + with self.allow_unbound_tvars_set(): + s.rvalue.accept(self) else: s.rvalue.accept(self) @@ -2780,22 +3042,23 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: if self.check_and_set_up_type_alias(s): s.is_alias_def = True special_form = True - # * type variable definition - elif self.process_typevar_declaration(s): - special_form = True - elif self.process_paramspec_declaration(s): - special_form = True - elif self.process_typevartuple_declaration(s): - special_form = True - # * type constructors - elif self.analyze_namedtuple_assign(s): - special_form = True - elif self.analyze_typeddict_assign(s): - special_form = True - elif self.newtype_analyzer.process_newtype_declaration(s): - special_form = True - elif self.analyze_enum_assign(s): - special_form = True + elif isinstance(s.rvalue, CallExpr): + # * type variable definition + if self.process_typevar_declaration(s): + special_form = True + elif self.process_paramspec_declaration(s): + special_form = True + elif self.process_typevartuple_declaration(s): + special_form = True + # * type constructors + elif self.analyze_namedtuple_assign(s): + special_form = True + elif self.analyze_typeddict_assign(s): + special_form = True + elif self.newtype_analyzer.process_newtype_declaration(s): + special_form = True + elif self.analyze_enum_assign(s): + special_form = True if special_form: self.record_special_form_lvalue(s) @@ -2933,6 +3196,16 @@ def can_possibly_be_type_form(self, s: AssignmentStmt) -> bool: # Something that looks like Foo = Bar[Baz, ...] return True + def can_possibly_be_typevarlike_declaration(self, s: AssignmentStmt) -> bool: + """Check if r.h.s. can be a TypeVarLike declaration.""" + if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], NameExpr): + return False + if not isinstance(s.rvalue, CallExpr) or not isinstance(s.rvalue.callee, NameExpr): + return False + ref = s.rvalue.callee + ref.accept(self) + return ref.fullname in TYPE_VAR_LIKE_NAMES + def is_type_ref(self, rv: Expression, bare: bool = False) -> bool: """Does this expression refer to a type? @@ -3042,6 +3315,12 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool: if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], (NameExpr, MemberExpr)): return False lvalue = s.lvalues[0] + if isinstance(lvalue, MemberExpr): + if isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.callee, RefExpr): + fullname = s.rvalue.callee.fullname + if fullname == "collections.namedtuple" or fullname in TYPED_NAMEDTUPLE_NAMES: + self.fail("NamedTuple type as an attribute is not supported", lvalue) + return False name = lvalue.name namespace = self.qualified_name(name) with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)): @@ -3050,9 +3329,6 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool: ) if internal_name is None: return False - if isinstance(lvalue, MemberExpr): - self.fail("NamedTuple type as an attribute is not supported", lvalue) - return False if internal_name != name: self.fail( 'First argument to namedtuple() should be "{}", not "{}"'.format( @@ -3104,10 +3380,10 @@ def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool: def analyze_lvalues(self, s: AssignmentStmt) -> None: # We cannot use s.type, because analyze_simple_literal_type() will set it. explicit = s.unanalyzed_type is not None - if self.is_final_type(s.unanalyzed_type): + final_type = self.unwrap_final_type(s.unanalyzed_type) + if final_type is not None: # We need to exclude bare Final. - assert isinstance(s.unanalyzed_type, UnboundType) - if not s.unanalyzed_type.args: + if not final_type.args: explicit = False if s.rvalue: @@ -3140,6 +3416,13 @@ def apply_dynamic_class_hook(self, s: AssignmentStmt) -> None: if isinstance(callee_expr, RefExpr) and callee_expr.fullname: method_name = call.callee.name fname = callee_expr.fullname + "." + method_name + elif ( + isinstance(callee_expr, IndexExpr) + and isinstance(callee_expr.base, RefExpr) + and isinstance(callee_expr.analyzed, TypeApplication) + ): + method_name = call.callee.name + fname = callee_expr.base.fullname + "." + method_name elif isinstance(callee_expr, CallExpr): # check if chain call call = callee_expr @@ -3166,19 +3449,19 @@ def unwrap_final(self, s: AssignmentStmt) -> bool: Returns True if Final[...] was present. """ - if not s.unanalyzed_type or not self.is_final_type(s.unanalyzed_type): + final_type = self.unwrap_final_type(s.unanalyzed_type) + if final_type is None: return False - assert isinstance(s.unanalyzed_type, UnboundType) - if len(s.unanalyzed_type.args) > 1: - self.fail("Final[...] takes at most one type argument", s.unanalyzed_type) + if len(final_type.args) > 1: + self.fail("Final[...] takes at most one type argument", final_type) invalid_bare_final = False - if not s.unanalyzed_type.args: + if not final_type.args: s.type = None if isinstance(s.rvalue, TempNode) and s.rvalue.no_rhs: invalid_bare_final = True self.fail("Type in Final[...] can only be omitted if there is an initializer", s) else: - s.type = s.unanalyzed_type.args[0] + s.type = final_type.args[0] if s.type is not None and self.is_classvar(s.type): self.fail("Variable should not be annotated with both ClassVar and Final", s) @@ -3196,7 +3479,7 @@ def unwrap_final(self, s: AssignmentStmt) -> bool: if lval.is_new_def: lval.is_inferred_def = s.type is None - if self.loop_depth > 0: + if self.loop_depth[-1] > 0: self.fail("Cannot use Final inside a loop", s) if self.type and self.type.is_protocol: self.msg.protocol_members_cant_be_final(s) @@ -3364,7 +3647,7 @@ def analyze_simple_literal_type(self, rvalue: Expression, is_final: bool) -> Typ return None value = constant_fold_expr(rvalue, self.cur_mod_id) - if value is None: + if value is None or isinstance(value, complex): return None if isinstance(value, bool): @@ -3382,8 +3665,13 @@ def analyze_simple_literal_type(self, rvalue: Expression, is_final: bool) -> Typ return typ def analyze_alias( - self, name: str, rvalue: Expression, allow_placeholder: bool = False - ) -> tuple[Type | None, list[TypeVarLikeType], set[str], list[str]]: + self, + name: str, + rvalue: Expression, + allow_placeholder: bool = False, + declared_type_vars: TypeVarLikeList | None = None, + all_declared_type_params_names: list[str] | None = None, + ) -> tuple[Type | None, list[TypeVarLikeType], set[str], list[str], bool]: """Check if 'rvalue' is a valid type allowed for aliasing (e.g. not a type variable). If yes, return the corresponding type, a list of @@ -3402,14 +3690,26 @@ def analyze_alias( self.fail( "Invalid type alias: expression is not a valid type", rvalue, code=codes.VALID_TYPE ) - return None, [], set(), [] + return None, [], set(), [], False - found_type_vars = typ.accept(TypeVarLikeQuery(self, self.tvar_scope)) + found_type_vars = self.find_type_var_likes(typ) tvar_defs: list[TypeVarLikeType] = [] namespace = self.qualified_name(name) + alias_type_vars = found_type_vars if declared_type_vars is None else declared_type_vars + last_tvar_name_with_default: str | None = None with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)): - for name, tvar_expr in found_type_vars: + for name, tvar_expr in alias_type_vars: + tvar_expr.default = tvar_expr.default.accept( + TypeVarDefaultTranslator(self, tvar_expr.name, typ) + ) tvar_def = self.tvar_scope.bind_new(name, tvar_expr) + if last_tvar_name_with_default is not None and not tvar_def.has_default(): + self.msg.tvar_without_default_type( + tvar_def.name, last_tvar_name_with_default, typ + ) + tvar_def.default = AnyType(TypeOfAny.from_error) + elif tvar_def.has_default(): + last_tvar_name_with_default = tvar_def.name tvar_defs.append(tvar_def) analyzed, depends_on = analyze_type_alias( @@ -3423,10 +3723,22 @@ def analyze_alias( in_dynamic_func=dynamic, global_scope=global_scope, allowed_alias_tvars=tvar_defs, + alias_type_params_names=all_declared_type_params_names, ) - qualified_tvars = [node.fullname for _name, node in found_type_vars] - return analyzed, tvar_defs, depends_on, qualified_tvars + # There can be only one variadic variable at most, the error is reported elsewhere. + new_tvar_defs = [] + variadic = False + for td in tvar_defs: + if isinstance(td, TypeVarTupleType): + if variadic: + continue + variadic = True + new_tvar_defs.append(td) + + qualified_tvars = [node.fullname for _name, node in alias_type_vars] + empty_tuple_index = typ.empty_tuple_index if isinstance(typ, UnboundType) else False + return analyzed, new_tvar_defs, depends_on, qualified_tvars, empty_tuple_index def is_pep_613(self, s: AssignmentStmt) -> bool: if s.unanalyzed_type is not None and isinstance(s.unanalyzed_type, UnboundType): @@ -3457,7 +3769,21 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: # unless using PEP 613 `cls: TypeAlias = A` return False - if isinstance(s.rvalue, CallExpr) and s.rvalue.analyzed: + # It can be `A = TypeAliasType('A', ...)` call, in this case, + # we just take the second argument and analyze it: + type_params: TypeVarLikeList | None + all_type_params_names: list[str] | None + if self.check_type_alias_type_call(s.rvalue, name=lvalue.name): + rvalue = s.rvalue.args[1] + pep_695 = True + type_params, all_type_params_names = self.analyze_type_alias_type_params(s.rvalue) + else: + rvalue = s.rvalue + pep_695 = False + type_params = None + all_type_params_names = None + + if isinstance(rvalue, CallExpr) and rvalue.analyzed: return False existing = self.current_symbol_table().get(lvalue.name) @@ -3483,7 +3809,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: return False non_global_scope = self.type or self.is_func_scope() - if not pep_613 and isinstance(s.rvalue, RefExpr) and non_global_scope: + if not pep_613 and not pep_695 and isinstance(rvalue, RefExpr) and non_global_scope: # Fourth rule (special case): Non-subscripted right hand side creates a variable # at class and function scopes. For example: # @@ -3495,8 +3821,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: # without this rule, this typical use case will require a lot of explicit # annotations (see the second rule). return False - rvalue = s.rvalue - if not pep_613 and not self.can_be_type_alias(rvalue): + if not pep_613 and not pep_695 and not self.can_be_type_alias(rvalue): return False if existing and not isinstance(existing.node, (PlaceholderNode, TypeAlias)): @@ -3509,14 +3834,19 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: alias_tvars: list[TypeVarLikeType] = [] depends_on: set[str] = set() qualified_tvars: list[str] = [] + empty_tuple_index = False else: tag = self.track_incomplete_refs() - res, alias_tvars, depends_on, qualified_tvars = self.analyze_alias( - lvalue.name, rvalue, allow_placeholder=True + res, alias_tvars, depends_on, qualified_tvars, empty_tuple_index = self.analyze_alias( + lvalue.name, + rvalue, + allow_placeholder=True, + declared_type_vars=type_params, + all_declared_type_params_names=all_type_params_names, ) if not res: return False - if not self.options.disable_recursive_aliases and not self.is_func_scope(): + if not self.is_func_scope(): # Only marking incomplete for top-level placeholders makes recursive aliases like # `A = Sequence[str | A]` valid here, similar to how we treat base classes in class # definitions, allowing `class str(Sequence[str]): ...` @@ -3542,10 +3872,19 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: # so we need to replace it with non-explicit Anys. res = make_any_non_explicit(res) # Note: with the new (lazy) type alias representation we only need to set no_args to True - # if the expected number of arguments is non-zero, so that aliases like A = List work. + # if the expected number of arguments is non-zero, so that aliases like `A = List` work + # but not aliases like `A = TypeAliasType("A", List)` as these need explicit type params. # However, eagerly expanding aliases like Text = str is a nice performance optimization. - no_args = isinstance(res, Instance) and not res.args # type: ignore[misc] - fix_instance_types(res, self.fail, self.note, self.options.python_version) + no_args = ( + isinstance(res, ProperType) + and isinstance(res, Instance) + and not res.args + and not empty_tuple_index + and not pep_695 + ) + if isinstance(res, ProperType) and isinstance(res, Instance): + if not validate_instance(res, self.fail, empty_tuple_index): + fix_instance(res, self.fail, self.note, disallow_any=False, options=self.options) # Aliases defined within functions can't be accessed outside # the function, since the symbol table will no longer # exist. Work around by expanding them eagerly when used. @@ -3608,13 +3947,102 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: self.note("Use variable annotation syntax to define protocol members", s) return True + def check_type_alias_type_call(self, rvalue: Expression, *, name: str) -> TypeGuard[CallExpr]: + if not isinstance(rvalue, CallExpr): + return False + + names = ["typing_extensions.TypeAliasType"] + if self.options.python_version >= (3, 12): + names.append("typing.TypeAliasType") + if not refers_to_fullname(rvalue.callee, tuple(names)): + return False + + return self.check_typevarlike_name(rvalue, name, rvalue) + + def analyze_type_alias_type_params( + self, rvalue: CallExpr + ) -> tuple[TypeVarLikeList, list[str]]: + """Analyze type_params of TypeAliasType. + + Returns declared unbound type variable expressions and a list of all decalred type + variable names for error reporting. + """ + if "type_params" in rvalue.arg_names: + type_params_arg = rvalue.args[rvalue.arg_names.index("type_params")] + if not isinstance(type_params_arg, TupleExpr): + self.fail( + "Tuple literal expected as the type_params argument to TypeAliasType", + type_params_arg, + ) + return [], [] + type_params = type_params_arg.items + else: + return [], [] + + declared_tvars: TypeVarLikeList = [] + all_declared_tvar_names: list[str] = [] # includes bound type variables + have_type_var_tuple = False + for tp_expr in type_params: + if isinstance(tp_expr, StarExpr): + tp_expr.valid = False + self.analyze_type_expr(tp_expr) + try: + base = self.expr_to_unanalyzed_type(tp_expr) + except TypeTranslationError: + continue + if not isinstance(base, UnboundType): + continue + + tag = self.track_incomplete_refs() + tvar = self.analyze_unbound_tvar_impl(base, is_typealias_param=True) + if tvar: + if isinstance(tvar[1], TypeVarTupleExpr): + if have_type_var_tuple: + self.fail( + "Can only use one TypeVarTuple in type_params argument to TypeAliasType", + base, + code=codes.TYPE_VAR, + ) + have_type_var_tuple = True + continue + have_type_var_tuple = True + elif not self.found_incomplete_ref(tag): + sym = self.lookup_qualified(base.name, base) + if sym and isinstance(sym.node, TypeVarLikeExpr): + all_declared_tvar_names.append(sym.node.name) # Error will be reported later + else: + self.fail( + "Free type variable expected in type_params argument to TypeAliasType", + base, + code=codes.TYPE_VAR, + ) + if sym and sym.fullname in ("typing.Unpack", "typing_extensions.Unpack"): + self.note( + "Don't Unpack type variables in type_params", base, code=codes.TYPE_VAR + ) + continue + if tvar in declared_tvars: + self.fail( + f'Duplicate type variable "{tvar[0]}" in type_params argument to TypeAliasType', + base, + code=codes.TYPE_VAR, + ) + continue + if tvar: + all_declared_tvar_names.append(tvar[0]) + declared_tvars.append(tvar) + return declared_tvars, all_declared_tvar_names + def disable_invalid_recursive_aliases( self, s: AssignmentStmt, current_node: TypeAlias ) -> None: """Prohibit and fix recursive type aliases that are invalid/unsupported.""" messages = [] - if invalid_recursive_alias({current_node}, current_node.target): - messages.append("Invalid recursive alias: a union item of itself") + if is_invalid_recursive_alias({current_node}, current_node.target): + target = ( + "tuple" if isinstance(get_proper_type(current_node.target), TupleType) else "union" + ) + messages.append(f"Invalid recursive alias: a {target} item of itself") if detect_diverging_alias( current_node, current_node.target, self.lookup_qualified, self.tvar_scope ): @@ -3655,7 +4083,7 @@ def analyze_lvalue( has_explicit_value=has_explicit_value, ) elif isinstance(lval, MemberExpr): - self.analyze_member_lvalue(lval, explicit_type, is_final) + self.analyze_member_lvalue(lval, explicit_type, is_final, has_explicit_value) if explicit_type and not self.is_self_member_ref(lval): self.fail("Type cannot be declared in assignment to non-self attribute", lval) elif isinstance(lval, IndexExpr): @@ -3700,7 +4128,12 @@ def analyze_name_lvalue( existing = names.get(name) outer = self.is_global_or_nonlocal(name) - if kind == MDEF and isinstance(self.type, TypeInfo) and self.type.is_enum: + if ( + kind == MDEF + and isinstance(self.type, TypeInfo) + and self.type.is_enum + and not name.startswith("__") + ): # Special case: we need to be sure that `Enum` keys are unique. if existing is not None and not isinstance(existing.node, PlaceholderNode): self.fail( @@ -3832,7 +4265,9 @@ def analyze_tuple_or_list_lvalue(self, lval: TupleExpr, explicit_type: bool = Fa has_explicit_value=True, ) - def analyze_member_lvalue(self, lval: MemberExpr, explicit_type: bool, is_final: bool) -> None: + def analyze_member_lvalue( + self, lval: MemberExpr, explicit_type: bool, is_final: bool, has_explicit_value: bool + ) -> None: """Analyze lvalue that is a member expression. Arguments: @@ -3861,12 +4296,18 @@ def analyze_member_lvalue(self, lval: MemberExpr, explicit_type: bool, is_final: and explicit_type ): self.attribute_already_defined(lval.name, lval, cur_node) - # If the attribute of self is not defined in superclasses, create a new Var, ... + if self.type.is_protocol and has_explicit_value and cur_node is not None: + # Make this variable non-abstract, it would be safer to do this only if we + # are inside __init__, but we do this always to preserve historical behaviour. + if isinstance(cur_node.node, Var): + cur_node.node.is_abstract_var = False if ( + # If the attribute of self is not defined, create a new Var, ... node is None - or (isinstance(node.node, Var) and node.node.is_abstract_var) + # ... or if it is defined as abstract in a *superclass*. + or (cur_node is None and isinstance(node.node, Var) and node.node.is_abstract_var) # ... also an explicit declaration on self also creates a new Var. - # Note that `explicit_type` might has been erased for bare `Final`, + # Note that `explicit_type` might have been erased for bare `Final`, # so we also check if `is_final` is passed. or (cur_node is None and (explicit_type or is_final)) ): @@ -3961,7 +4402,7 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: ) if res is None: return False - variance, upper_bound = res + variance, upper_bound, default = res existing = self.current_symbol_table().get(name) if existing and not ( @@ -3983,7 +4424,7 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: prefix = "Upper bound of type variable" self.msg.unimported_type_becomes_any(prefix, upper_bound, s) - for t in values + [upper_bound]: + for t in values + [upper_bound, default]: check_for_explicit_any( t, self.options, self.is_typeshed_stub_file, self.msg, context=s ) @@ -3996,19 +4437,28 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: # Yes, it's a valid type variable definition! Add it to the symbol table. if not call.analyzed: - type_var = TypeVarExpr(name, self.qualified_name(name), values, upper_bound, variance) + type_var = TypeVarExpr( + name, self.qualified_name(name), values, upper_bound, default, variance + ) type_var.line = call.line call.analyzed = type_var updated = True else: assert isinstance(call.analyzed, TypeVarExpr) - updated = values != call.analyzed.values or upper_bound != call.analyzed.upper_bound + updated = ( + values != call.analyzed.values + or upper_bound != call.analyzed.upper_bound + or default != call.analyzed.default + ) call.analyzed.upper_bound = upper_bound call.analyzed.values = values - if any(has_placeholder(v) for v in values) or has_placeholder(upper_bound): - self.process_placeholder( - None, f"TypeVar {'values' if values else 'upper bound'}", s, force_progress=updated - ) + call.analyzed.default = default + if any(has_placeholder(v) for v in values): + self.process_placeholder(None, "TypeVar values", s, force_progress=updated) + elif has_placeholder(upper_bound): + self.process_placeholder(None, "TypeVar upper bound", s, force_progress=updated) + elif has_placeholder(default): + self.process_placeholder(None, "TypeVar default", s, force_progress=updated) self.add_symbol(name, call.analyzed, s) return True @@ -4023,7 +4473,7 @@ def check_typevarlike_name(self, call: CallExpr, name: str, context: Context) -> if len(call.args) < 1: self.fail(f"Too few arguments for {typevarlike_type}()", context) return False - if not isinstance(call.args[0], StrExpr) or not call.arg_kinds[0] == ARG_POS: + if not isinstance(call.args[0], StrExpr) or call.arg_kinds[0] != ARG_POS: self.fail(f"{typevarlike_type}() expects a string literal as first argument", context) return False elif call.args[0].value != name: @@ -4057,11 +4507,12 @@ def process_typevar_parameters( kinds: list[ArgKind], num_values: int, context: Context, - ) -> tuple[int, Type] | None: + ) -> tuple[int, Type, Type] | None: has_values = num_values > 0 covariant = False contravariant = False upper_bound: Type = self.object_type() + default: Type = AnyType(TypeOfAny.from_omitted_generics) for param_value, param_name, param_kind in zip(args, names, kinds): if not param_kind.is_named(): self.fail(message_registry.TYPEVAR_UNEXPECTED_ARGUMENT, context) @@ -4084,28 +4535,15 @@ def process_typevar_parameters( if has_values: self.fail("TypeVar cannot have both values and an upper bound", context) return None - try: - # We want to use our custom error message below, so we suppress - # the default error message for invalid types here. - analyzed = self.expr_to_analyzed_type( - param_value, allow_placeholder=True, report_invalid_types=False - ) - if analyzed is None: - # Type variables are special: we need to place them in the symbol table - # soon, even if upper bound is not ready yet. Otherwise avoiding - # a "deadlock" in this common pattern would be tricky: - # T = TypeVar('T', bound=Custom[Any]) - # class Custom(Generic[T]): - # ... - analyzed = PlaceholderType(None, [], context.line) - upper_bound = get_proper_type(analyzed) - if isinstance(upper_bound, AnyType) and upper_bound.is_from_error: - self.fail(message_registry.TYPEVAR_BOUND_MUST_BE_TYPE, param_value) - # Note: we do not return 'None' here -- we want to continue - # using the AnyType as the upper bound. - except TypeTranslationError: - self.fail(message_registry.TYPEVAR_BOUND_MUST_BE_TYPE, param_value) + tv_arg = self.get_typevarlike_argument("TypeVar", param_name, param_value, context) + if tv_arg is None: return None + upper_bound = tv_arg + elif param_name == "default": + tv_arg = self.get_typevarlike_argument( + "TypeVar", param_name, param_value, context, allow_unbound_tvars=True + ) + default = tv_arg or AnyType(TypeOfAny.from_error) elif param_name == "values": # Probably using obsolete syntax with values=(...). Explain the current syntax. self.fail('TypeVar "values" argument not supported', context) @@ -4131,7 +4569,55 @@ def process_typevar_parameters( variance = CONTRAVARIANT else: variance = INVARIANT - return variance, upper_bound + return variance, upper_bound, default + + def get_typevarlike_argument( + self, + typevarlike_name: str, + param_name: str, + param_value: Expression, + context: Context, + *, + allow_unbound_tvars: bool = False, + allow_param_spec_literals: bool = False, + allow_unpack: bool = False, + report_invalid_typevar_arg: bool = True, + ) -> ProperType | None: + try: + # We want to use our custom error message below, so we suppress + # the default error message for invalid types here. + analyzed = self.expr_to_analyzed_type( + param_value, + allow_placeholder=True, + report_invalid_types=False, + allow_unbound_tvars=allow_unbound_tvars, + allow_param_spec_literals=allow_param_spec_literals, + allow_unpack=allow_unpack, + ) + if analyzed is None: + # Type variables are special: we need to place them in the symbol table + # soon, even if upper bound is not ready yet. Otherwise avoiding + # a "deadlock" in this common pattern would be tricky: + # T = TypeVar('T', bound=Custom[Any]) + # class Custom(Generic[T]): + # ... + analyzed = PlaceholderType(None, [], context.line) + typ = get_proper_type(analyzed) + if report_invalid_typevar_arg and isinstance(typ, AnyType) and typ.is_from_error: + self.fail( + message_registry.TYPEVAR_ARG_MUST_BE_TYPE.format(typevarlike_name, param_name), + param_value, + ) + # Note: we do not return 'None' here -- we want to continue + # using the AnyType. + return typ + except TypeTranslationError: + if report_invalid_typevar_arg: + self.fail( + message_registry.TYPEVAR_ARG_MUST_BE_TYPE.format(typevarlike_name, param_name), + param_value, + ) + return None def extract_typevarlike_name(self, s: AssignmentStmt, call: CallExpr) -> str | None: if not call: @@ -4165,11 +4651,50 @@ def process_paramspec_declaration(self, s: AssignmentStmt) -> bool: if name is None: return False - # ParamSpec is different from a regular TypeVar: - # arguments are not semantically valid. But, allowed in runtime. - # So, we need to warn users about possible invalid usage. - if len(call.args) > 1: - self.fail("Only the first argument to ParamSpec has defined semantics", s) + n_values = call.arg_kinds[1:].count(ARG_POS) + if n_values != 0: + self.fail('Too many positional arguments for "ParamSpec"', s) + + default: Type = AnyType(TypeOfAny.from_omitted_generics) + for param_value, param_name in zip( + call.args[1 + n_values :], call.arg_names[1 + n_values :] + ): + if param_name == "default": + tv_arg = self.get_typevarlike_argument( + "ParamSpec", + param_name, + param_value, + s, + allow_unbound_tvars=True, + allow_param_spec_literals=True, + report_invalid_typevar_arg=False, + ) + default = tv_arg or AnyType(TypeOfAny.from_error) + if isinstance(tv_arg, Parameters): + for i, arg_type in enumerate(tv_arg.arg_types): + typ = get_proper_type(arg_type) + if isinstance(typ, AnyType) and typ.is_from_error: + self.fail( + f"Argument {i} of ParamSpec default must be a type", param_value + ) + elif ( + isinstance(default, AnyType) + and default.is_from_error + or not isinstance(default, (AnyType, UnboundType)) + ): + self.fail( + "The default argument to ParamSpec must be a list expression, ellipsis, or a ParamSpec", + param_value, + ) + default = AnyType(TypeOfAny.from_error) + else: + # ParamSpec is different from a regular TypeVar: + # arguments are not semantically valid. But, allowed in runtime. + # So, we need to warn users about possible invalid usage. + self.fail( + "The variance and bound arguments to ParamSpec do not have defined semantics yet", + s, + ) # PEP 612 reserves the right to define bound, covariant and contravariant arguments to # ParamSpec in a later PEP. If and when that happens, we should do something @@ -4177,12 +4702,18 @@ def process_paramspec_declaration(self, s: AssignmentStmt) -> bool: if not call.analyzed: paramspec_var = ParamSpecExpr( - name, self.qualified_name(name), self.object_type(), INVARIANT + name, self.qualified_name(name), self.object_type(), default, INVARIANT ) paramspec_var.line = call.line call.analyzed = paramspec_var + updated = True else: assert isinstance(call.analyzed, ParamSpecExpr) + updated = default != call.analyzed.default + call.analyzed.default = default + if has_placeholder(default): + self.process_placeholder(None, "ParamSpec default", s, force_progress=updated) + self.add_symbol(name, call.analyzed, s) return True @@ -4197,11 +4728,33 @@ def process_typevartuple_declaration(self, s: AssignmentStmt) -> bool: if not call: return False - if len(call.args) > 1: - self.fail("Only the first argument to TypeVarTuple has defined semantics", s) + n_values = call.arg_kinds[1:].count(ARG_POS) + if n_values != 0: + self.fail('Too many positional arguments for "TypeVarTuple"', s) - if not self.incomplete_feature_enabled(TYPE_VAR_TUPLE, s): - return False + default: Type = AnyType(TypeOfAny.from_omitted_generics) + for param_value, param_name in zip( + call.args[1 + n_values :], call.arg_names[1 + n_values :] + ): + if param_name == "default": + tv_arg = self.get_typevarlike_argument( + "TypeVarTuple", + param_name, + param_value, + s, + allow_unbound_tvars=True, + report_invalid_typevar_arg=False, + allow_unpack=True, + ) + default = tv_arg or AnyType(TypeOfAny.from_error) + if not isinstance(default, UnpackType): + self.fail( + "The default argument to TypeVarTuple must be an Unpacked tuple", + param_value, + ) + default = AnyType(TypeOfAny.from_error) + else: + self.fail(f'Unexpected keyword argument "{param_name}" for "TypeVarTuple"', s) name = self.extract_typevarlike_name(s, call) if name is None: @@ -4211,12 +4764,24 @@ def process_typevartuple_declaration(self, s: AssignmentStmt) -> bool: if not call.analyzed: tuple_fallback = self.named_type("builtins.tuple", [self.object_type()]) typevartuple_var = TypeVarTupleExpr( - name, self.qualified_name(name), self.object_type(), tuple_fallback, INVARIANT + name, + self.qualified_name(name), + # Upper bound for *Ts is *tuple[object, ...], it can never be object. + tuple_fallback.copy_modified(), + tuple_fallback, + default, + INVARIANT, ) typevartuple_var.line = call.line call.analyzed = typevartuple_var + updated = True else: assert isinstance(call.analyzed, TypeVarTupleExpr) + updated = default != call.analyzed.default + call.analyzed.default = default + if has_placeholder(default): + self.process_placeholder(None, "TypeVarTuple default", s, force_progress=updated) + self.add_symbol(name, call.analyzed, s) return True @@ -4302,13 +4867,18 @@ def is_classvar(self, typ: Type) -> bool: return False return sym.node.fullname == "typing.ClassVar" - def is_final_type(self, typ: Type | None) -> bool: + def unwrap_final_type(self, typ: Type | None) -> UnboundType | None: + if typ is None: + return None + typ = typ.resolve_string_annotation() if not isinstance(typ, UnboundType): - return False + return None sym = self.lookup_qualified(typ.name, typ) if not sym or not sym.node: - return False - return sym.node.fullname in FINAL_TYPE_NAMES + return None + if sym.node.fullname in FINAL_TYPE_NAMES: + return typ + return None def fail_invalid_classvar(self, context: Context) -> None: self.fail(message_registry.CLASS_VAR_OUTSIDE_OF_CLASS, context) @@ -4433,7 +5003,6 @@ def process__slots__(self, s: AssignmentStmt) -> None: and s.lvalues[0].name == "__slots__" and s.lvalues[0].kind == MDEF ): - # We understand `__slots__` defined as string, tuple, list, set, and dict: if not isinstance(s.rvalue, (StrExpr, ListExpr, TupleExpr, SetExpr, DictExpr)): # For example, `__slots__` can be defined as a variable, @@ -4535,9 +5104,9 @@ def visit_operator_assignment_stmt(self, s: OperatorAssignmentStmt) -> None: def visit_while_stmt(self, s: WhileStmt) -> None: self.statement = s s.expr.accept(self) - self.loop_depth += 1 + self.loop_depth[-1] += 1 s.body.accept(self) - self.loop_depth -= 1 + self.loop_depth[-1] -= 1 self.visit_block_maybe(s.else_body) def visit_for_stmt(self, s: ForStmt) -> None: @@ -4559,20 +5128,20 @@ def visit_for_stmt(self, s: ForStmt) -> None: self.store_declared_types(s.index, analyzed) s.index_type = analyzed - self.loop_depth += 1 + self.loop_depth[-1] += 1 self.visit_block(s.body) - self.loop_depth -= 1 + self.loop_depth[-1] -= 1 self.visit_block_maybe(s.else_body) def visit_break_stmt(self, s: BreakStmt) -> None: self.statement = s - if self.loop_depth == 0: + if self.loop_depth[-1] == 0: self.fail('"break" outside loop', s, serious=True, blocker=True) def visit_continue_stmt(self, s: ContinueStmt) -> None: self.statement = s - if self.loop_depth == 0: + if self.loop_depth[-1] == 0: self.fail('"continue" outside loop', s, serious=True, blocker=True) def visit_if_stmt(self, s: IfStmt) -> None: @@ -4677,8 +5246,14 @@ def visit_nonlocal_decl(self, d: NonlocalDecl) -> None: self.fail("nonlocal declaration not allowed at module level", d) else: for name in d.names: - for table in reversed(self.locals[:-1]): + for table, scope_type in zip( + reversed(self.locals[:-1]), reversed(self.scope_stack[:-1]) + ): if table is not None and name in table: + if scope_type == SCOPE_ANNOTATION: + self.fail( + f'nonlocal binding not allowed for type parameter "{name}"', d + ) break else: self.fail(f'No binding for nonlocal "{name}" found', d) @@ -4705,6 +5280,79 @@ def visit_match_stmt(self, s: MatchStmt) -> None: guard.accept(self) self.visit_block(s.bodies[i]) + def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None: + self.statement = s + type_params = self.push_type_args(s.type_args, s) + if type_params is None: + self.defer(s) + return + all_type_params_names = [p.name for p in s.type_args] + + try: + tag = self.track_incomplete_refs() + res, alias_tvars, depends_on, qualified_tvars, empty_tuple_index = self.analyze_alias( + s.name.name, + s.value, + allow_placeholder=True, + declared_type_vars=type_params, + all_declared_type_params_names=all_type_params_names, + ) + if not res: + res = AnyType(TypeOfAny.from_error) + + if not self.is_func_scope(): + # Only marking incomplete for top-level placeholders makes recursive aliases like + # `A = Sequence[str | A]` valid here, similar to how we treat base classes in class + # definitions, allowing `class str(Sequence[str]): ...` + incomplete_target = isinstance(res, ProperType) and isinstance( + res, PlaceholderType + ) + else: + incomplete_target = has_placeholder(res) + + if self.found_incomplete_ref(tag) or incomplete_target: + # Since we have got here, we know this must be a type alias (incomplete refs + # may appear in nested positions), therefore use becomes_typeinfo=True. + self.mark_incomplete(s.name.name, s.value, becomes_typeinfo=True) + return + + self.add_type_alias_deps(depends_on) + # In addition to the aliases used, we add deps on unbound + # type variables, since they are erased from target type. + self.add_type_alias_deps(qualified_tvars) + # The above are only direct deps on other aliases. + # For subscripted aliases, type deps from expansion are added in deps.py + # (because the type is stored). + check_for_explicit_any( + res, self.options, self.is_typeshed_stub_file, self.msg, context=s + ) + # When this type alias gets "inlined", the Any is not explicit anymore, + # so we need to replace it with non-explicit Anys. + res = make_any_non_explicit(res) + eager = self.is_func_scope() + alias_node = TypeAlias( + res, + self.qualified_name(s.name.name), + s.line, + s.column, + alias_tvars=alias_tvars, + no_args=False, + eager=eager, + ) + + existing = self.current_symbol_table().get(s.name.name) + if ( + existing + and isinstance(existing.node, (PlaceholderNode, TypeAlias)) + and existing.node.line == s.line + ): + existing.node = alias_node + else: + self.add_symbol(s.name.name, alias_node, s) + + finally: + self.pop_type_args(s.type_args) + # # Expressions # @@ -4717,9 +5365,7 @@ def visit_name_expr(self, expr: NameExpr) -> None: def bind_name_expr(self, expr: NameExpr, sym: SymbolTableNode) -> None: """Bind name expression to a symbol table node.""" if isinstance(sym.node, TypeVarExpr) and self.tvar_scope.get_binding(sym): - self.fail( - '"{}" is a type variable and only valid in type ' "context".format(expr.name), expr - ) + self.fail(f'"{expr.name}" is a type variable and only valid in type context', expr) elif isinstance(sym.node, PlaceholderNode): self.process_placeholder(expr.name, "name", expr) else: @@ -4761,14 +5407,14 @@ def visit_dict_expr(self, expr: DictExpr) -> None: def visit_star_expr(self, expr: StarExpr) -> None: if not expr.valid: - self.fail("Can use starred expression only as assignment target", expr, blocker=True) + self.fail("can't use starred expression here", expr, blocker=True) else: expr.expr.accept(self) def visit_yield_from_expr(self, e: YieldFromExpr) -> None: if not self.is_func_scope(): self.fail('"yield from" outside function', e, serious=True, blocker=True) - elif self.is_comprehension_stack[-1]: + elif self.scope_stack[-1] == SCOPE_COMPREHENSION: self.fail( '"yield from" inside comprehension or generator expression', e, @@ -4821,7 +5467,17 @@ def visit_call_expr(self, expr: CallExpr) -> None: elif refers_to_fullname(expr.callee, REVEAL_TYPE_NAMES): if not self.check_fixed_args(expr, 1, "reveal_type"): return - expr.analyzed = RevealExpr(kind=REVEAL_TYPE, expr=expr.args[0]) + reveal_imported = False + reveal_type_node = self.lookup("reveal_type", expr, suppress_errors=True) + if ( + reveal_type_node + and isinstance(reveal_type_node.node, FuncBase) + and reveal_type_node.fullname in IMPORTED_REVEAL_TYPE_NAMES + ): + reveal_imported = True + expr.analyzed = RevealExpr( + kind=REVEAL_TYPE, expr=expr.args[0], is_imported=reveal_imported + ) expr.analyzed.line = expr.line expr.analyzed.column = expr.column expr.analyzed.accept(self) @@ -4882,6 +5538,12 @@ def visit_call_expr(self, expr: CallExpr) -> None: expr.analyzed = OpExpr("divmod", expr.args[0], expr.args[1]) expr.analyzed.line = expr.line expr.analyzed.accept(self) + elif refers_to_fullname( + expr.callee, ("typing.TypeAliasType", "typing_extensions.TypeAliasType") + ): + with self.allow_unbound_tvars_set(): + for a in expr.args: + a.accept(self) else: # Normal call expression. for a in expr.args: @@ -4892,7 +5554,7 @@ def visit_call_expr(self, expr: CallExpr) -> None: and isinstance(expr.callee.expr, NameExpr) and expr.callee.expr.name == "__all__" and expr.callee.expr.kind == GDEF - and expr.callee.name in ("append", "extend") + and expr.callee.name in ("append", "extend", "remove") ): if expr.callee.name == "append" and expr.args: self.add_exports(expr.args[0]) @@ -4902,20 +5564,26 @@ def visit_call_expr(self, expr: CallExpr) -> None: and isinstance(expr.args[0], (ListExpr, TupleExpr)) ): self.add_exports(expr.args[0].items) + elif ( + expr.callee.name == "remove" + and expr.args + and isinstance(expr.args[0], StrExpr) + ): + self.all_exports = [n for n in self.all_exports if n != expr.args[0].value] def translate_dict_call(self, call: CallExpr) -> DictExpr | None: """Translate 'dict(x=y, ...)' to {'x': y, ...} and 'dict()' to {}. For other variants of dict(...), return None. """ - if not all(kind == ARG_NAMED for kind in call.arg_kinds): + if not all(kind in (ARG_NAMED, ARG_STAR2) for kind in call.arg_kinds): # Must still accept those args. for a in call.args: a.accept(self) return None expr = DictExpr( [ - (StrExpr(cast(str, key)), value) # since they are all ARG_NAMED + (StrExpr(key) if key is not None else None, value) for key, value in zip(call.arg_names, call.args) ] ) @@ -5083,29 +5751,32 @@ def analyze_type_application_args(self, expr: IndexExpr) -> list[Type] | None: else: items = [index] - # whether param spec literals be allowed here - # TODO: should this be computed once and passed in? - # or is there a better way to do this? + # TODO: this needs a clean-up. + # Probably always allow Parameters literals, and validate in semanal_typeargs.py base = expr.base if isinstance(base, RefExpr) and isinstance(base.node, TypeAlias): + allow_unpack = base.node.tvar_tuple_index is not None alias = base.node - target = get_proper_type(alias.target) - if isinstance(target, Instance): - has_param_spec = target.type.has_param_spec_type - num_args = len(target.type.type_vars) + if any(isinstance(t, ParamSpecType) for t in alias.alias_tvars): + has_param_spec = True + num_args = len(alias.alias_tvars) else: has_param_spec = False num_args = -1 - elif isinstance(base, NameExpr) and isinstance(base.node, TypeInfo): + elif isinstance(base, RefExpr) and isinstance(base.node, TypeInfo): + allow_unpack = ( + base.node.has_type_var_tuple_type or base.node.fullname == "builtins.tuple" + ) has_param_spec = base.node.has_param_spec_type num_args = len(base.node.type_vars) else: + allow_unpack = False has_param_spec = False num_args = -1 for item in items: try: - typearg = self.expr_to_unanalyzed_type(item) + typearg = self.expr_to_unanalyzed_type(item, allow_unpack=True) except TypeTranslationError: self.fail("Type expected within [...]", expr) return None @@ -5117,20 +5788,16 @@ def analyze_type_application_args(self, expr: IndexExpr) -> list[Type] | None: allow_unbound_tvars=self.allow_unbound_tvars, allow_placeholder=True, allow_param_spec_literals=has_param_spec, + allow_unpack=allow_unpack, ) if analyzed is None: return None types.append(analyzed) - if has_param_spec and num_args == 1 and len(types) > 0: + if has_param_spec and num_args == 1 and types: first_arg = get_proper_type(types[0]) if not ( - len(types) == 1 - and ( - isinstance(first_arg, Parameters) - or isinstance(first_arg, ParamSpecType) - or isinstance(first_arg, AnyType) - ) + len(types) == 1 and isinstance(first_arg, (Parameters, ParamSpecType, AnyType)) ): types = [Parameters(types, [ARG_POS] * len(types), [None] * len(types))] @@ -5245,7 +5912,7 @@ def visit__promote_expr(self, expr: PromoteExpr) -> None: def visit_yield_expr(self, e: YieldExpr) -> None: if not self.is_func_scope(): self.fail('"yield" outside function', e, serious=True, blocker=True) - elif self.is_comprehension_stack[-1]: + elif self.scope_stack[-1] == SCOPE_COMPREHENSION: self.fail( '"yield" inside comprehension or generator expression', e, @@ -5253,11 +5920,8 @@ def visit_yield_expr(self, e: YieldExpr) -> None: blocker=True, ) elif self.function_stack[-1].is_coroutine: - if self.options.python_version < (3, 6): - self.fail('"yield" in async function', e, serious=True, blocker=True) - else: - self.function_stack[-1].is_generator = True - self.function_stack[-1].is_async_generator = True + self.function_stack[-1].is_generator = True + self.function_stack[-1].is_async_generator = True else: self.function_stack[-1].is_generator = True if e.expr: @@ -5266,9 +5930,16 @@ def visit_yield_expr(self, e: YieldExpr) -> None: def visit_await_expr(self, expr: AwaitExpr) -> None: if not self.is_func_scope() or not self.function_stack: # We check both because is_function_scope() returns True inside comprehensions. - self.fail('"await" outside function', expr, serious=True, blocker=True) + # This is not a blocker, because some enviroments (like ipython) + # support top level awaits. + self.fail('"await" outside function', expr, serious=True, code=codes.TOP_LEVEL_AWAIT) elif not self.function_stack[-1].is_coroutine: - self.fail('"await" outside coroutine ("async def")', expr, serious=True, blocker=True) + self.fail( + '"await" outside coroutine ("async def")', + expr, + serious=True, + code=codes.AWAIT_NOT_ASYNC, + ) expr.expr.accept(self) # @@ -5343,7 +6014,7 @@ def lookup( if not suppress_errors: self.name_not_defined(name, ctx) return None - # 2. Class attributes (if within class definition) + # 2a. Class attributes (if within class definition) if self.type and not self.is_func_scope() and name in self.type.names: node = self.type.names[name] if not node.implicit: @@ -5353,10 +6024,14 @@ def lookup( # Defined through self.x assignment implicit_name = True implicit_node = node + # 2b. Class attributes __qualname__ and __module__ + if self.type and not self.is_func_scope() and name in {"__qualname__", "__module__"}: + return SymbolTableNode(MDEF, Var(name, self.str_type())) # 3. Local (function) scopes for table in reversed(self.locals): if table is not None and name in table: return table[name] + # 4. Current file global scope if name in self.globals: return self.globals[name] @@ -5526,9 +6201,7 @@ def get_module_symbol(self, node: MypyFile, name: str) -> SymbolTableNode | None sym = SymbolTableNode(GDEF, self.modules[fullname]) elif self.is_incomplete_namespace(module): self.record_incomplete_ref() - elif "__getattr__" in names and ( - node.is_stub or self.options.python_version >= (3, 7) - ): + elif "__getattr__" in names: gvar = self.create_getattr_var(names["__getattr__"], name, fullname) if gvar: sym = SymbolTableNode(GDEF, gvar) @@ -5671,6 +6344,8 @@ def add_symbol( module_hidden: bool = False, can_defer: bool = True, escape_comprehensions: bool = False, + no_progress: bool = False, + type_param: bool = False, ) -> bool: """Add symbol to the currently active symbol table. @@ -5692,7 +6367,9 @@ def add_symbol( symbol = SymbolTableNode( kind, node, module_public=module_public, module_hidden=module_hidden ) - return self.add_symbol_table_node(name, symbol, context, can_defer, escape_comprehensions) + return self.add_symbol_table_node( + name, symbol, context, can_defer, escape_comprehensions, no_progress, type_param + ) def add_symbol_skip_local(self, name: str, node: SymbolNode) -> None: """Same as above, but skipping the local namespace. @@ -5723,6 +6400,8 @@ def add_symbol_table_node( context: Context | None = None, can_defer: bool = True, escape_comprehensions: bool = False, + no_progress: bool = False, + type_param: bool = False, ) -> bool: """Add symbol table node to the currently active symbol table. @@ -5742,7 +6421,9 @@ def add_symbol_table_node( can_defer: if True, defer current target if adding a placeholder context: error context (see above about None value) """ - names = self.current_symbol_table(escape_comprehensions=escape_comprehensions) + names = self.current_symbol_table( + escape_comprehensions=escape_comprehensions, type_param=type_param + ) existing = names.get(name) if isinstance(symbol.node, PlaceholderNode) and can_defer: if context is not None: @@ -5771,7 +6452,8 @@ def add_symbol_table_node( self.name_already_defined(name, context, existing) elif name not in self.missing_names[-1] and "*" not in self.missing_names[-1]: names[name] = symbol - self.progress = True + if not no_progress: + self.progress = True return True return False @@ -5822,7 +6504,7 @@ def _get_node_for_class_scoped_import( # mypyc is absolutely convinced that `symbol_node` narrows to a Var in the following, # when it can also be a FuncBase. Once fixed, `f` in the following can be removed. # See also https://github.com/mypyc/mypyc/issues/892 - f = cast(Any, lambda x: x) + f: Callable[[object], Any] = lambda x: x if isinstance(f(symbol_node), (Decorator, FuncBase, Var)): # For imports in class scope, we construct a new node to represent the symbol and # set its `info` attribute to `self.type`. @@ -6040,7 +6722,7 @@ def process_placeholder( def cannot_resolve_name(self, name: str | None, kind: str, ctx: Context) -> None: name_format = f' "{name}"' if name else "" self.fail(f"Cannot resolve {kind}{name_format} (possible cyclic definition)", ctx) - if not self.options.disable_recursive_aliases and self.is_func_scope(): + if self.is_func_scope(): self.note("Recursive types are not allowed at function scope", ctx) def qualified_name(self, name: str) -> str: @@ -6059,28 +6741,33 @@ def enter( names = self.saved_locals.setdefault(function, SymbolTable()) self.locals.append(names) is_comprehension = isinstance(function, (GeneratorExpr, DictionaryComprehension)) - self.is_comprehension_stack.append(is_comprehension) + self.scope_stack.append(SCOPE_FUNC if not is_comprehension else SCOPE_COMPREHENSION) self.global_decls.append(set()) self.nonlocal_decls.append(set()) # -1 since entering block will increment this to 0. self.block_depth.append(-1) + self.loop_depth.append(0) self.missing_names.append(set()) try: yield finally: self.locals.pop() - self.is_comprehension_stack.pop() + self.scope_stack.pop() self.global_decls.pop() self.nonlocal_decls.pop() self.block_depth.pop() + self.loop_depth.pop() self.missing_names.pop() def is_func_scope(self) -> bool: - return self.locals[-1] is not None + scope_type = self.scope_stack[-1] + if scope_type == SCOPE_ANNOTATION: + scope_type = self.scope_stack[-2] + return scope_type in (SCOPE_FUNC, SCOPE_COMPREHENSION) def is_nested_within_func_scope(self) -> bool: """Are we underneath a function scope, even if we are in a nested class also?""" - return any(l is not None for l in self.locals) + return any(s in (SCOPE_FUNC, SCOPE_COMPREHENSION) for s in self.scope_stack) def is_class_scope(self) -> bool: return self.type is not None and not self.is_func_scope() @@ -6097,14 +6784,24 @@ def current_symbol_kind(self) -> int: kind = GDEF return kind - def current_symbol_table(self, escape_comprehensions: bool = False) -> SymbolTable: - if self.is_func_scope(): - assert self.locals[-1] is not None + def current_symbol_table( + self, escape_comprehensions: bool = False, type_param: bool = False + ) -> SymbolTable: + if type_param and self.scope_stack[-1] == SCOPE_ANNOTATION: + n = self.locals[-1] + assert n is not None + return n + elif self.is_func_scope(): + if self.scope_stack[-1] == SCOPE_ANNOTATION: + n = self.locals[-2] + else: + n = self.locals[-1] + assert n is not None if escape_comprehensions: - assert len(self.locals) == len(self.is_comprehension_stack) + assert len(self.locals) == len(self.scope_stack) # Retrieve the symbol table from the enclosing non-comprehension scope. - for i, is_comprehension in enumerate(reversed(self.is_comprehension_stack)): - if not is_comprehension: + for i, scope_type in enumerate(reversed(self.scope_stack)): + if scope_type != SCOPE_COMPREHENSION: if i == len(self.locals) - 1: # The last iteration. # The caller of the comprehension is in the global space. names = self.globals @@ -6118,7 +6815,7 @@ def current_symbol_table(self, escape_comprehensions: bool = False) -> SymbolTab else: assert False, "Should have at least one non-comprehension scope" else: - names = self.locals[-1] + names = n assert names is not None elif self.type is not None: names = self.type.names @@ -6285,6 +6982,9 @@ def expr_to_analyzed_type( report_invalid_types: bool = True, allow_placeholder: bool = False, allow_type_any: bool = False, + allow_unbound_tvars: bool = False, + allow_param_spec_literals: bool = False, + allow_unpack: bool = False, ) -> Type | None: if isinstance(expr, CallExpr): # This is a legacy syntax intended mostly for Python 2, we keep it for @@ -6313,6 +7013,9 @@ def expr_to_analyzed_type( report_invalid_types=report_invalid_types, allow_placeholder=allow_placeholder, allow_type_any=allow_type_any, + allow_unbound_tvars=allow_unbound_tvars, + allow_param_spec_literals=allow_param_spec_literals, + allow_unpack=allow_unpack, ) def analyze_type_expr(self, expr: Expression) -> None: @@ -6334,6 +7037,7 @@ def type_analyzer( allow_placeholder: bool = False, allow_required: bool = False, allow_param_spec_literals: bool = False, + allow_unpack: bool = False, report_invalid_types: bool = True, prohibit_self_type: str | None = None, allow_type_any: bool = False, @@ -6352,6 +7056,7 @@ def type_analyzer( allow_placeholder=allow_placeholder, allow_required=allow_required, allow_param_spec_literals=allow_param_spec_literals, + allow_unpack=allow_unpack, prohibit_self_type=prohibit_self_type, allow_type_any=allow_type_any, ) @@ -6359,8 +7064,10 @@ def type_analyzer( tpan.global_scope = not self.type and not self.function_stack return tpan - def expr_to_unanalyzed_type(self, node: Expression) -> ProperType: - return expr_to_unanalyzed_type(node, self.options, self.is_stub_file) + def expr_to_unanalyzed_type(self, node: Expression, allow_unpack: bool = False) -> ProperType: + return expr_to_unanalyzed_type( + node, self.options, self.is_stub_file, allow_unpack=allow_unpack + ) def anal_type( self, @@ -6372,6 +7079,7 @@ def anal_type( allow_placeholder: bool = False, allow_required: bool = False, allow_param_spec_literals: bool = False, + allow_unpack: bool = False, report_invalid_types: bool = True, prohibit_self_type: str | None = None, allow_type_any: bool = False, @@ -6409,6 +7117,7 @@ def anal_type( allow_placeholder=allow_placeholder, allow_required=allow_required, allow_param_spec_literals=allow_param_spec_literals, + allow_unpack=allow_unpack, report_invalid_types=report_invalid_types, prohibit_self_type=prohibit_self_type, allow_type_any=allow_type_any, @@ -6472,12 +7181,8 @@ def is_initial_mangled_global(self, name: str) -> bool: return name == unmangle(name) + "'" def parse_bool(self, expr: Expression) -> bool | None: - if isinstance(expr, NameExpr): - if expr.fullname == "builtins.True": - return True - if expr.fullname == "builtins.False": - return False - return None + # This wrapper is preserved for plugins. + return parse_bool(expr) def parse_str_literal(self, expr: Expression) -> str | None: """Attempt to find the string literal value of the given expression. Returns `None` if no @@ -6535,13 +7240,13 @@ def parse_dataclass_transform_spec(self, call: CallExpr) -> DataclassTransformSp def parse_dataclass_transform_field_specifiers(self, arg: Expression) -> tuple[str, ...]: if not isinstance(arg, TupleExpr): self.fail('"field_specifiers" argument must be a tuple literal', arg) - return tuple() + return () names = [] for specifier in arg.items: if not isinstance(specifier, RefExpr): self.fail('"field_specifiers" must only contain identifiers', specifier) - return tuple() + return () names.append(specifier.fullname) return tuple(names) @@ -6697,7 +7402,7 @@ def is_trivial_body(block: Block) -> bool: "..." (ellipsis), or "raise NotImplementedError()". A trivial body may also start with a statement containing just a string (e.g. a docstring). - Note: functions that raise other kinds of exceptions do not count as + Note: Functions that raise other kinds of exceptions do not count as "trivial". We use this function to help us determine when it's ok to relax certain checks on body, but functions that raise arbitrary exceptions are more likely to do non-trivial work. For example: @@ -6707,11 +7412,18 @@ def halt(self, reason: str = ...) -> NoReturn: A function that raises just NotImplementedError is much less likely to be this complex. + + Note: If you update this, you may also need to update + mypy.fastparse.is_possible_trivial_body! """ body = block.body + if not body: + # Functions have empty bodies only if the body is stripped or the function is + # generated or deserialized. In these cases the body is unknown. + return False # Skip a docstring - if body and isinstance(body[0], ExpressionStmt) and isinstance(body[0].expr, StrExpr): + if isinstance(body[0], ExpressionStmt) and isinstance(body[0].expr, StrExpr): body = block.body[1:] if len(body) == 0: diff --git a/mypy/semanal_classprop.py b/mypy/semanal_classprop.py index 3f5bc9c4c2de..b5f1b2181761 100644 --- a/mypy/semanal_classprop.py +++ b/mypy/semanal_classprop.py @@ -5,7 +5,7 @@ from __future__ import annotations -from typing_extensions import Final +from typing import Final from mypy.errors import Errors from mypy.nodes import ( @@ -46,6 +46,8 @@ def calculate_class_abstract_status(typ: TypeInfo, is_stub_file: bool, errors: E abstract attribute. Also compute a list of abstract attributes. Report error is required ABCMeta metaclass is missing. """ + typ.is_abstract = False + typ.abstract_attributes = [] if typ.typeddict_type: return # TypedDict can't be abstract concrete: set[str] = set() @@ -56,7 +58,6 @@ def calculate_class_abstract_status(typ: TypeInfo, is_stub_file: bool, errors: E # Special case: NewTypes are considered as always non-abstract, so they can be used as: # Config = NewType('Config', Mapping[str, str]) # default = Config({'cannot': 'modify'}) # OK - typ.abstract_attributes = [] return for base in typ.mro: for name, symnode in base.names.items(): diff --git a/mypy/semanal_enum.py b/mypy/semanal_enum.py index c7b8e44f65aa..30e0bd56c312 100644 --- a/mypy/semanal_enum.py +++ b/mypy/semanal_enum.py @@ -5,8 +5,7 @@ from __future__ import annotations -from typing import cast -from typing_extensions import Final +from typing import Final, cast from mypy.nodes import ( ARG_NAMED, @@ -27,6 +26,7 @@ TupleExpr, TypeInfo, Var, + is_StrExpr_list, ) from mypy.options import Options from mypy.semanal_shared import SemanticAnalyzerInterface @@ -103,19 +103,29 @@ class A(enum.Enum): fullname = callee.fullname if fullname not in ENUM_BASES: return None - items, values, ok = self.parse_enum_call_args(call, fullname.split(".")[-1]) + + new_class_name, items, values, ok = self.parse_enum_call_args( + call, fullname.split(".")[-1] + ) if not ok: # Error. Construct dummy return value. - info = self.build_enum_call_typeinfo(var_name, [], fullname, node.line) + name = var_name + if is_func_scope: + name += "@" + str(call.line) + info = self.build_enum_call_typeinfo(name, [], fullname, node.line) else: + if new_class_name != var_name: + msg = f'String argument 1 "{new_class_name}" to {fullname}(...) does not match variable name "{var_name}"' + self.fail(msg, call) + name = cast(StrExpr, call.args[0]).value if name != var_name or is_func_scope: # Give it a unique name derived from the line number. name += "@" + str(call.line) info = self.build_enum_call_typeinfo(name, items, fullname, call.line) - # Store generated TypeInfo under both names, see semanal_namedtuple for more details. - if name != var_name or is_func_scope: - self.api.add_symbol_skip_local(name, info) + # Store generated TypeInfo under both names, see semanal_namedtuple for more details. + if name != var_name or is_func_scope: + self.api.add_symbol_skip_local(name, info) call.analyzed = EnumCallExpr(info, items, values) call.analyzed.set_line(call) info.line = node.line @@ -139,13 +149,13 @@ def build_enum_call_typeinfo( def parse_enum_call_args( self, call: CallExpr, class_name: str - ) -> tuple[list[str], list[Expression | None], bool]: + ) -> tuple[str, list[str], list[Expression | None], bool]: """Parse arguments of an Enum call. Return a tuple of fields, values, was there an error. """ args = call.args - if not all([arg_kind in [ARG_POS, ARG_NAMED] for arg_kind in call.arg_kinds]): + if not all(arg_kind in [ARG_POS, ARG_NAMED] for arg_kind in call.arg_kinds): return self.fail_enum_call_arg(f"Unexpected arguments to {class_name}()", call) if len(args) < 2: return self.fail_enum_call_arg(f"Too few arguments for {class_name}()", call) @@ -169,6 +179,8 @@ def parse_enum_call_args( return self.fail_enum_call_arg( f"{class_name}() expects a string literal as the first argument", call ) + new_class_name = value.value + items = [] values: list[Expression | None] = [] if isinstance(names, StrExpr): @@ -177,8 +189,8 @@ def parse_enum_call_args( items.append(field) elif isinstance(names, (TupleExpr, ListExpr)): seq_items = names.items - if all(isinstance(seq_item, StrExpr) for seq_item in seq_items): - items = [cast(StrExpr, seq_item).value for seq_item in seq_items] + if is_StrExpr_list(seq_items): + items = [seq_item.value for seq_item in seq_items] elif all( isinstance(seq_item, (TupleExpr, ListExpr)) and len(seq_item.items) == 2 @@ -231,18 +243,18 @@ def parse_enum_call_args( % class_name, call, ) - if len(items) == 0: + if not items: return self.fail_enum_call_arg(f"{class_name}() needs at least one item", call) if not values: values = [None] * len(items) assert len(items) == len(values) - return items, values, True + return new_class_name, items, values, True def fail_enum_call_arg( self, message: str, context: Context - ) -> tuple[list[str], list[Expression | None], bool]: + ) -> tuple[str, list[str], list[Expression | None], bool]: self.fail(message, context) - return [], [], False + return "", [], [], False # Helpers diff --git a/mypy/semanal_main.py b/mypy/semanal_main.py index a5e85878e931..1185a3821553 100644 --- a/mypy/semanal_main.py +++ b/mypy/semanal_main.py @@ -27,8 +27,8 @@ from __future__ import annotations from contextlib import nullcontext -from typing import TYPE_CHECKING, Callable, List, Optional, Tuple, Union -from typing_extensions import Final, TypeAlias as _TypeAlias +from typing import TYPE_CHECKING, Callable, Final, List, Optional, Tuple, Union +from typing_extensions import TypeAlias as _TypeAlias import mypy.build import mypy.state @@ -190,7 +190,7 @@ def process_top_levels(graph: Graph, scc: list[str], patches: Patches) -> None: # Initially all namespaces in the SCC are incomplete (well they are empty). state.manager.incomplete_namespaces.update(scc) - worklist = scc[:] + worklist = scc.copy() # HACK: process core stuff first. This is mostly needed to support defining # named tuples in builtin SCC. if all(m in worklist for m in core_modules): @@ -218,7 +218,7 @@ def process_top_levels(graph: Graph, scc: list[str], patches: Patches) -> None: state = graph[next_id] assert state.tree is not None deferred, incomplete, progress = semantic_analyze_target( - next_id, state, state.tree, None, final_iteration, patches + next_id, next_id, state, state.tree, None, final_iteration, patches ) all_deferred += deferred any_progress = any_progress or progress @@ -289,7 +289,7 @@ def process_top_level_function( # OK, this is one last pass, now missing names will be reported. analyzer.incomplete_namespaces.discard(module) deferred, incomplete, progress = semantic_analyze_target( - target, state, node, active_type, final_iteration, patches + target, module, state, node, active_type, final_iteration, patches ) if final_iteration: assert not deferred, "Must not defer during final iteration" @@ -318,6 +318,7 @@ def get_all_leaf_targets(file: MypyFile) -> list[TargetInfo]: def semantic_analyze_target( target: str, + module: str, state: State, node: MypyFile | FuncDef | OverloadedFuncDef | Decorator, active_type: TypeInfo | None, @@ -331,7 +332,7 @@ def semantic_analyze_target( - was some definition incomplete (need to run another pass) - were any new names defined (or placeholders replaced) """ - state.manager.processed_targets.append(target) + state.manager.processed_targets.append((module, target)) tree = state.tree assert tree is not None analyzer = state.manager.semantic_analyzer @@ -379,7 +380,8 @@ def check_type_arguments(graph: Graph, scc: list[str], errors: Errors) -> None: analyzer = TypeArgumentAnalyzer( errors, state.options, - is_typeshed_file(state.options.abs_custom_typeshed_dir, state.path or ""), + state.tree.is_typeshed_file(state.options), + state.manager.semantic_analyzer.named_type, ) with state.wrap_context(): with mypy.state.state.strict_optional_set(state.options.strict_optional): @@ -398,6 +400,7 @@ def check_type_arguments_in_targets( errors, state.options, is_typeshed_file(state.options.abs_custom_typeshed_dir, state.path or ""), + state.manager.semantic_analyzer.named_type, ) with state.wrap_context(): with mypy.state.state.strict_optional_set(state.options.strict_optional): diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index 1194557836b1..753deafe103b 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -5,11 +5,13 @@ from __future__ import annotations +import keyword from contextlib import contextmanager -from typing import Iterator, List, Mapping, cast -from typing_extensions import Final +from typing import Container, Final, Iterator, List, Mapping, cast +from mypy.errorcodes import ARG_TYPE, ErrorCode from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type +from mypy.messages import MessageBuilder from mypy.nodes import ( ARG_NAMED_OPT, ARG_OPT, @@ -41,6 +43,7 @@ TypeInfo, TypeVarExpr, Var, + is_StrExpr_list, ) from mypy.options import Options from mypy.semanal_shared import ( @@ -84,16 +87,19 @@ ) NAMEDTUP_CLASS_ERROR: Final = ( - "Invalid statement in NamedTuple definition; " 'expected "field_name: field_type [= default]"' + 'Invalid statement in NamedTuple definition; expected "field_name: field_type [= default]"' ) SELF_TVAR_NAME: Final = "_NT" class NamedTupleAnalyzer: - def __init__(self, options: Options, api: SemanticAnalyzerInterface) -> None: + def __init__( + self, options: Options, api: SemanticAnalyzerInterface, msg: MessageBuilder + ) -> None: self.options = options self.api = api + self.msg = msg def analyze_namedtuple_classdef( self, defn: ClassDef, is_stub_file: bool, is_func_scope: bool @@ -142,9 +148,6 @@ def check_namedtuple_classdef( * valid statements or None, if any of the types are not ready. """ - if self.options.python_version < (3, 6) and not is_stub_file: - self.fail("NamedTuple class syntax is only supported in Python 3.6", defn) - return [], [], {}, [] if len(defn.base_type_exprs) > 1: self.fail("NamedTuple should be a single base", defn) items: list[str] = [] @@ -185,8 +188,7 @@ def check_namedtuple_classdef( # it would be inconsistent with type aliases. analyzed = self.api.anal_type( stmt.type, - allow_placeholder=not self.options.disable_recursive_aliases - and not self.api.is_func_scope(), + allow_placeholder=not self.api.is_func_scope(), prohibit_self_type="NamedTuple item type", ) if analyzed is None: @@ -208,6 +210,10 @@ def check_namedtuple_classdef( ) else: default_items[name] = stmt.rvalue + if defn.keywords: + for_function = ' for "__init_subclass__" of "NamedTuple"' + for key in defn.keywords: + self.msg.unexpected_keyword_argument_for_function(for_function, key, defn) return items, types, default_items, statements def check_namedtuple( @@ -280,7 +286,7 @@ def check_namedtuple( # two methods of a class can define a named tuple with the same name, # and they will be stored in the same namespace (see below). name += "@" + str(call.line) - if len(defaults) > 0: + if defaults: default_items = { arg_name: default for arg_name, default in zip(items[-len(defaults) :], defaults) } @@ -348,6 +354,7 @@ def parse_namedtuple_args( self.fail(f'Too few arguments for "{type_name}()"', call) return None defaults: list[Expression] = [] + rename = False if len(args) > 2: # Typed namedtuple doesn't support additional arguments. if fullname in TYPED_NAMEDTUPLE_NAMES: @@ -366,14 +373,24 @@ def parse_namedtuple_args( "{}()".format(type_name), arg, ) - break + elif arg_name == "rename": + arg = args[i] + if isinstance(arg, NameExpr) and arg.name in ("True", "False"): + rename = arg.name == "True" + else: + self.fail( + 'Boolean literal expected as the "rename" argument to ' + f"{type_name}()", + arg, + code=ARG_TYPE, + ) if call.arg_kinds[:2] != [ARG_POS, ARG_POS]: self.fail(f'Unexpected arguments to "{type_name}()"', call) return None if not isinstance(args[0], StrExpr): self.fail(f'"{type_name}()" expects a string literal as the first argument', call) return None - typename = cast(StrExpr, call.args[0]).value + typename = args[0].value types: list[Type] = [] tvar_defs = [] if not isinstance(args[1], (ListExpr, TupleExpr)): @@ -392,10 +409,10 @@ def parse_namedtuple_args( listexpr = args[1] if fullname == "collections.namedtuple": # The fields argument contains just names, with implicit Any types. - if any(not isinstance(item, StrExpr) for item in listexpr.items): + if not is_StrExpr_list(listexpr.items): self.fail('String literal expected as "namedtuple()" item', call) return None - items = [cast(StrExpr, item).value for item in listexpr.items] + items = [item.value for item in listexpr.items] else: type_exprs = [ t.items[1] @@ -413,17 +430,28 @@ def parse_namedtuple_args( return [], [], [], typename, [], False if not types: types = [AnyType(TypeOfAny.unannotated) for _ in items] - underscore = [item for item in items if item.startswith("_")] - if underscore: - self.fail( - f'"{type_name}()" field names cannot start with an underscore: ' - + ", ".join(underscore), - call, - ) + processed_items = [] + seen_names: set[str] = set() + for i, item in enumerate(items): + problem = self.check_namedtuple_field_name(item, seen_names) + if problem is None: + processed_items.append(item) + seen_names.add(item) + else: + if not rename: + self.fail(f'"{type_name}()" {problem}', call) + # Even if rename=False, we pretend that it is True. + # At runtime namedtuple creation would throw an error; + # applying the rename logic means we create a more sensible + # namedtuple. + new_name = f"_{i}" + processed_items.append(new_name) + seen_names.add(new_name) + if len(defaults) > len(items): self.fail(f'Too many defaults given in call to "{type_name}()"', call) defaults = defaults[: len(items)] - return items, types, defaults, typename, tvar_defs, True + return processed_items, types, defaults, typename, tvar_defs, True def parse_namedtuple_fields_with_types( self, nodes: list[Expression], context: Context @@ -453,8 +481,7 @@ def parse_namedtuple_fields_with_types( # We never allow recursive types at function scope. analyzed = self.api.anal_type( type, - allow_placeholder=not self.options.disable_recursive_aliases - and not self.api.is_func_scope(), + allow_placeholder=not self.api.is_func_scope(), prohibit_self_type="NamedTuple item type", ) # Workaround #4987 and avoid introducing a bogus UnboundType @@ -543,11 +570,12 @@ def add_field( assert info.tuple_type is not None # Set by update_tuple_type() above. tvd = TypeVarType( - SELF_TVAR_NAME, - info.fullname + "." + SELF_TVAR_NAME, - self.api.tvar_scope.new_unique_func_id(), - [], - info.tuple_type, + name=SELF_TVAR_NAME, + fullname=info.fullname + "." + SELF_TVAR_NAME, + id=self.api.tvar_scope.new_unique_func_id(), + values=[], + upper_bound=info.tuple_type, + default=AnyType(TypeOfAny.from_omitted_generics), ) selftype = tvd @@ -595,6 +623,12 @@ def add_method( ret=selftype, args=[Argument(var, var.type, EllipsisExpr(), ARG_NAMED_OPT) for var in vars], ) + if self.options.python_version >= (3, 13): + add_method( + "__replace__", + ret=selftype, + args=[Argument(var, var.type, EllipsisExpr(), ARG_NAMED_OPT) for var in vars], + ) def make_init_arg(var: Var) -> Argument: default = default_items.get(var.name, None) @@ -603,20 +637,19 @@ def make_init_arg(var: Var) -> Argument: add_method("__new__", ret=selftype, args=[make_init_arg(var) for var in vars], is_new=True) add_method("_asdict", args=[], ret=ordereddictype) - special_form_any = AnyType(TypeOfAny.special_form) add_method( "_make", ret=selftype, is_classmethod=True, - args=[ - Argument(Var("iterable", iterable_type), iterable_type, None, ARG_POS), - Argument(Var("new"), special_form_any, EllipsisExpr(), ARG_NAMED_OPT), - Argument(Var("len"), special_form_any, EllipsisExpr(), ARG_NAMED_OPT), - ], + args=[Argument(Var("iterable", iterable_type), iterable_type, None, ARG_POS)], ) self_tvar_expr = TypeVarExpr( - SELF_TVAR_NAME, info.fullname + "." + SELF_TVAR_NAME, [], info.tuple_type + SELF_TVAR_NAME, + info.fullname + "." + SELF_TVAR_NAME, + [], + info.tuple_type, + AnyType(TypeOfAny.from_omitted_generics), ) info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr) return info @@ -663,5 +696,17 @@ def save_namedtuple_body(self, named_tuple_info: TypeInfo) -> Iterator[None]: # Helpers - def fail(self, msg: str, ctx: Context) -> None: - self.api.fail(msg, ctx) + def check_namedtuple_field_name(self, field: str, seen_names: Container[str]) -> str | None: + """Return None for valid fields, a string description for invalid ones.""" + if field in seen_names: + return f'has duplicate field name "{field}"' + elif not field.isidentifier(): + return f'field name "{field}" is not a valid identifier' + elif field.startswith("_"): + return f'field name "{field}" starts with an underscore' + elif keyword.iskeyword(field): + return f'field name "{field}" is a keyword' + return None + + def fail(self, msg: str, ctx: Context, code: ErrorCode | None = None) -> None: + self.api.fail(msg, ctx, code=code) diff --git a/mypy/semanal_newtype.py b/mypy/semanal_newtype.py index cb1055a62186..c9c0c46f7aee 100644 --- a/mypy/semanal_newtype.py +++ b/mypy/semanal_newtype.py @@ -105,7 +105,11 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> bool: else: if old_type is not None: message = "Argument 2 to NewType(...) must be subclassable (got {})" - self.fail(message.format(format_type(old_type)), s, code=codes.VALID_NEWTYPE) + self.fail( + message.format(format_type(old_type, self.options)), + s, + code=codes.VALID_NEWTYPE, + ) # Otherwise the error was already reported. old_type = AnyType(TypeOfAny.from_error) object_type = self.api.named_type("builtins.object") @@ -143,7 +147,7 @@ def analyze_newtype_declaration(self, s: AssignmentStmt) -> tuple[str | None, Ca and isinstance(s.lvalues[0], NameExpr) and isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.callee, RefExpr) - and s.rvalue.callee.fullname == "typing.NewType" + and (s.rvalue.callee.fullname in ("typing.NewType", "typing_extensions.NewType")) ): name = s.lvalues[0].name @@ -203,8 +207,7 @@ def check_newtype_args( self.api.anal_type( unanalyzed_type, report_invalid_types=False, - allow_placeholder=not self.options.disable_recursive_aliases - and not self.api.is_func_scope(), + allow_placeholder=not self.api.is_func_scope(), ) ) should_defer = False diff --git a/mypy/semanal_pass1.py b/mypy/semanal_pass1.py index 55430be00a1e..aaa01969217a 100644 --- a/mypy/semanal_pass1.py +++ b/mypy/semanal_pass1.py @@ -45,10 +45,9 @@ class SemanticAnalyzerPreAnalysis(TraverserVisitor): import sys - def do_stuff(): - # type: () -> None: - if sys.python_version < (3,): - import xyz # Only available in Python 2 + def do_stuff() -> None: + if sys.version_info >= (3, 10): + import xyz # Only available in Python 3.10+ xyz.whatever() ... @@ -62,6 +61,7 @@ def visit_file(self, file: MypyFile, fnam: str, mod_id: str, options: Options) - self.cur_mod_node = file self.options = options self.is_global_scope = True + self.skipped_lines: set[int] = set() for i, defn in enumerate(file.defs): defn.accept(self) @@ -69,8 +69,14 @@ def visit_file(self, file: MypyFile, fnam: str, mod_id: str, options: Options) - # We've encountered an assert that's always false, # e.g. assert sys.platform == 'lol'. Truncate the # list of statements. This mutates file.defs too. + if i < len(file.defs) - 1: + next_def, last = file.defs[i + 1], file.defs[-1] + if last.end_line is not None: + # We are on a Python version recent enough to support end lines. + self.skipped_lines |= set(range(next_def.line, last.end_line + 1)) del file.defs[i + 1 :] break + file.skipped_lines = self.skipped_lines def visit_func_def(self, node: FuncDef) -> None: old_global_scope = self.is_global_scope @@ -118,6 +124,9 @@ def visit_if_stmt(self, s: IfStmt) -> None: def visit_block(self, b: Block) -> None: if b.is_unreachable: + if b.end_line is not None: + # We are on a Python version recent enough to support end lines. + self.skipped_lines |= set(range(b.line, b.end_line + 1)) return super().visit_block(b) diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index 03efbe6ca1b8..b5ec2bb52a0d 100644 --- a/mypy/semanal_shared.py +++ b/mypy/semanal_shared.py @@ -3,8 +3,8 @@ from __future__ import annotations from abc import abstractmethod -from typing import Callable, overload -from typing_extensions import Final, Literal, Protocol +from typing import Callable, Final, overload +from typing_extensions import Literal, Protocol from mypy_extensions import trait @@ -18,6 +18,7 @@ Decorator, Expression, FuncDef, + NameExpr, Node, OverloadedFuncDef, RefExpr, @@ -31,6 +32,7 @@ from mypy.type_visitor import ANY_STRATEGY, BoolTypeQuery from mypy.types import ( TPDICT_FB_NAMES, + AnyType, FunctionLike, Instance, Parameters, @@ -40,8 +42,11 @@ ProperType, TupleType, Type, + TypeOfAny, TypeVarId, TypeVarLikeType, + TypeVarTupleType, + UnpackType, get_proper_type, ) @@ -283,12 +288,27 @@ def calculate_tuple_fallback(typ: TupleType) -> None: """ fallback = typ.partial_fallback assert fallback.type.fullname == "builtins.tuple" - fallback.args = (join.join_type_list(list(typ.items)),) + fallback.args[1:] + items = [] + for item in typ.items: + # TODO: this duplicates some logic in typeops.tuple_fallback(). + if isinstance(item, UnpackType): + unpacked_type = get_proper_type(item.type) + if isinstance(unpacked_type, TypeVarTupleType): + unpacked_type = get_proper_type(unpacked_type.upper_bound) + if ( + isinstance(unpacked_type, Instance) + and unpacked_type.type.fullname == "builtins.tuple" + ): + items.append(unpacked_type.args[0]) + else: + raise NotImplementedError + else: + items.append(item) + fallback.args = (join.join_type_list(items),) class _NamedTypeCallback(Protocol): - def __call__(self, fully_qualified_name: str, args: list[Type] | None = None) -> Instance: - ... + def __call__(self, fully_qualified_name: str, args: list[Type] | None = None) -> Instance: ... def paramspec_args( @@ -307,6 +327,7 @@ def paramspec_args( id, flavor=ParamSpecFlavor.ARGS, upper_bound=named_type_func("builtins.tuple", [named_type_func("builtins.object")]), + default=AnyType(TypeOfAny.from_omitted_generics), line=line, column=column, prefix=prefix, @@ -331,6 +352,7 @@ def paramspec_kwargs( upper_bound=named_type_func( "builtins.dict", [named_type_func("builtins.str"), named_type_func("builtins.object")] ), + default=AnyType(TypeOfAny.from_omitted_generics), line=line, column=column, prefix=prefix, @@ -430,8 +452,7 @@ def require_bool_literal_argument( expression: Expression, name: str, default: Literal[True] | Literal[False], -) -> bool: - ... +) -> bool: ... @overload @@ -440,8 +461,7 @@ def require_bool_literal_argument( expression: Expression, name: str, default: None = None, -) -> bool | None: - ... +) -> bool | None: ... def require_bool_literal_argument( @@ -451,7 +471,7 @@ def require_bool_literal_argument( default: bool | None = None, ) -> bool | None: """Attempt to interpret an expression as a boolean literal, and fail analysis if we can't.""" - value = api.parse_bool(expression) + value = parse_bool(expression) if value is None: api.fail( f'"{name}" argument must be a True or False literal', expression, code=LITERAL_REQ @@ -459,3 +479,12 @@ def require_bool_literal_argument( return default return value + + +def parse_bool(expr: Expression) -> bool | None: + if isinstance(expr, NameExpr): + if expr.fullname == "builtins.True": + return True + if expr.fullname == "builtins.False": + return False + return None diff --git a/mypy/semanal_typeargs.py b/mypy/semanal_typeargs.py index b9965236c379..15ea15d612c0 100644 --- a/mypy/semanal_typeargs.py +++ b/mypy/semanal_typeargs.py @@ -7,19 +7,20 @@ from __future__ import annotations -from typing import Sequence +from typing import Callable from mypy import errorcodes as codes, message_registry from mypy.errorcodes import ErrorCode from mypy.errors import Errors from mypy.messages import format_type from mypy.mixedtraverser import MixedTraverserVisitor -from mypy.nodes import Block, ClassDef, Context, FakeInfo, FuncItem, MypyFile +from mypy.nodes import ARG_STAR, Block, ClassDef, Context, FakeInfo, FuncItem, MypyFile from mypy.options import Options from mypy.scope import Scope from mypy.subtypes import is_same_type, is_subtype from mypy.types import ( AnyType, + CallableType, Instance, Parameters, ParamSpecType, @@ -32,17 +33,26 @@ TypeVarType, UnboundType, UnpackType, + flatten_nested_tuples, get_proper_type, get_proper_types, + split_with_prefix_and_suffix, ) class TypeArgumentAnalyzer(MixedTraverserVisitor): - def __init__(self, errors: Errors, options: Options, is_typeshed_file: bool) -> None: + def __init__( + self, + errors: Errors, + options: Options, + is_typeshed_file: bool, + named_type: Callable[[str, list[Type]], Instance], + ) -> None: super().__init__() self.errors = errors self.options = options self.is_typeshed_file = is_typeshed_file + self.named_type = named_type self.scope = Scope() # Should we also analyze function definitions, or only module top-levels? self.recurse_into_functions = True @@ -77,30 +87,61 @@ def visit_type_alias_type(self, t: TypeAliasType) -> None: # types, since errors there have already been reported. return self.seen_aliases.add(t) - # Some recursive aliases may produce spurious args. In principle this is not very - # important, as we would simply ignore them when expanding, but it is better to keep - # correct aliases. - if t.alias and len(t.args) != len(t.alias.alias_tvars): - t.args = [AnyType(TypeOfAny.from_error) for _ in t.alias.alias_tvars] assert t.alias is not None, f"Unfixed type alias {t.type_ref}" - is_error = self.validate_args(t.alias.name, t.args, t.alias.alias_tvars, t) + is_error = self.validate_args(t.alias.name, tuple(t.args), t.alias.alias_tvars, t) if not is_error: # If there was already an error for the alias itself, there is no point in checking # the expansion, most likely it will result in the same kind of error. get_proper_type(t).accept(self) + def visit_tuple_type(self, t: TupleType) -> None: + t.items = flatten_nested_tuples(t.items) + # We could also normalize Tuple[*tuple[X, ...]] -> tuple[X, ...] like in + # expand_type() but we can't do this here since it is not a translator visitor, + # and we need to return an Instance instead of TupleType. + super().visit_tuple_type(t) + + def visit_callable_type(self, t: CallableType) -> None: + super().visit_callable_type(t) + # Normalize trivial unpack in var args as *args: *tuple[X, ...] -> *args: X + if t.is_var_arg: + star_index = t.arg_kinds.index(ARG_STAR) + star_type = t.arg_types[star_index] + if isinstance(star_type, UnpackType): + p_type = get_proper_type(star_type.type) + if isinstance(p_type, Instance): + assert p_type.type.fullname == "builtins.tuple" + t.arg_types[star_index] = p_type.args[0] + def visit_instance(self, t: Instance) -> None: + super().visit_instance(t) # Type argument counts were checked in the main semantic analyzer pass. We assume # that the counts are correct here. info = t.type if isinstance(info, FakeInfo): return # https://github.com/python/mypy/issues/11079 self.validate_args(info.name, t.args, info.defn.type_vars, t) - super().visit_instance(t) + if t.type.fullname == "builtins.tuple" and len(t.args) == 1: + # Normalize Tuple[*Tuple[X, ...], ...] -> Tuple[X, ...] + arg = t.args[0] + if isinstance(arg, UnpackType): + unpacked = get_proper_type(arg.type) + if isinstance(unpacked, Instance): + assert unpacked.type.fullname == "builtins.tuple" + t.args = unpacked.args def validate_args( - self, name: str, args: Sequence[Type], type_vars: list[TypeVarLikeType], ctx: Context + self, name: str, args: tuple[Type, ...], type_vars: list[TypeVarLikeType], ctx: Context ) -> bool: + if any(isinstance(v, TypeVarTupleType) for v in type_vars): + prefix = next(i for (i, v) in enumerate(type_vars) if isinstance(v, TypeVarTupleType)) + tvt = type_vars[prefix] + assert isinstance(tvt, TypeVarTupleType) + start, middle, end = split_with_prefix_and_suffix( + tuple(args), prefix, len(type_vars) - prefix - 1 + ) + args = start + (TupleType(list(middle), tvt.tuple_fallback),) + end + is_error = False for (i, arg), tvar in zip(enumerate(args), type_vars): if isinstance(tvar, TypeVarType): @@ -133,7 +174,14 @@ def validate_args( arg_values = [arg] if self.check_type_var_values(name, arg_values, tvar.name, tvar.values, ctx): is_error = True - if not is_subtype(arg, tvar.upper_bound): + # Check against upper bound. Since it's object the vast majority of the time, + # add fast path to avoid a potentially slow subtype check. + upper_bound = tvar.upper_bound + object_upper_bound = ( + type(upper_bound) is Instance + and upper_bound.type.fullname == "builtins.object" + ) + if not object_upper_bound and not is_subtype(arg, upper_bound): if self.in_type_alias_expr and isinstance(arg, TypeVarType): # Type aliases are allowed to use unconstrained type variables # error will be checked at substitution point. @@ -141,7 +189,9 @@ def validate_args( is_error = True self.fail( message_registry.INVALID_TYPEVAR_ARG_BOUND.format( - format_type(arg), name, format_type(tvar.upper_bound) + format_type(arg, self.options), + name, + format_type(upper_bound, self.options), ), ctx, code=codes.TYPE_VAR, @@ -152,25 +202,31 @@ def validate_args( ): self.fail( "Can only replace ParamSpec with a parameter types list or" - f" another ParamSpec, got {format_type(arg)}", + f" another ParamSpec, got {format_type(arg, self.options)}", ctx, ) return is_error def visit_unpack_type(self, typ: UnpackType) -> None: + super().visit_unpack_type(typ) proper_type = get_proper_type(typ.type) if isinstance(proper_type, TupleType): return if isinstance(proper_type, TypeVarTupleType): return + # TODO: this should probably be .has_base("builtins.tuple"), also elsewhere. This is + # tricky however, since this needs map_instance_to_supertype() available in many places. if isinstance(proper_type, Instance) and proper_type.type.fullname == "builtins.tuple": return - if isinstance(proper_type, AnyType) and proper_type.type_of_any == TypeOfAny.from_error: - return - - # TODO: Infer something when it can't be unpacked to allow rest of - # typechecking to work. - self.fail(message_registry.INVALID_UNPACK.format(proper_type), typ) + if not isinstance(proper_type, (UnboundType, AnyType)): + # Avoid extra errors if there were some errors already. Also interpret plain Any + # as tuple[Any, ...] (this is better for the code in type checker). + self.fail( + message_registry.INVALID_UNPACK.format(format_type(proper_type, self.options)), + typ.type, + code=codes.VALID_TYPE, + ) + typ.type = self.named_type("builtins.tuple", [AnyType(TypeOfAny.from_error)]) def check_type_var_values( self, name: str, actuals: list[Type], arg_name: str, valids: list[Type], context: Context diff --git a/mypy/semanal_typeddict.py b/mypy/semanal_typeddict.py index acb93edb7d2d..eee98d4d20fa 100644 --- a/mypy/semanal_typeddict.py +++ b/mypy/semanal_typeddict.py @@ -2,10 +2,11 @@ from __future__ import annotations -from typing_extensions import Final +from typing import Final from mypy import errorcodes as codes, message_registry from mypy.errorcodes import ErrorCode +from mypy.expandtype import expand_type from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type from mypy.messages import MessageBuilder from mypy.nodes import ( @@ -36,6 +37,7 @@ has_placeholder, require_bool_literal_argument, ) +from mypy.state import state from mypy.typeanal import check_for_explicit_any, has_any_from_unimported_type from mypy.types import ( TPDICT_NAMES, @@ -45,11 +47,10 @@ TypedDictType, TypeOfAny, TypeVarLikeType, - replace_alias_tvars, ) TPDICT_CLASS_ERROR: Final = ( - "Invalid statement in TypedDict definition; " 'expected "field_name: field_type"' + 'Invalid statement in TypedDict definition; expected "field_name: field_type"' ) @@ -78,6 +79,8 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> tuple[bool, TypeInfo | N """ possible = False for base_expr in defn.base_type_exprs: + if isinstance(base_expr, CallExpr): + base_expr = base_expr.callee if isinstance(base_expr, IndexExpr): base_expr = base_expr.base if isinstance(base_expr, RefExpr): @@ -101,6 +104,8 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> tuple[bool, TypeInfo | N fields, types, statements, required_keys = self.analyze_typeddict_classdef_fields(defn) if fields is None: return True, None # Defer + if self.api.is_func_scope() and "@" not in defn.name: + defn.name += "@" + str(defn.line) info = self.build_typeddict_typeinfo( defn.name, fields, types, required_keys, defn.line, existing_info ) @@ -114,7 +119,13 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> tuple[bool, TypeInfo | N typeddict_bases: list[Expression] = [] typeddict_bases_set = set() for expr in defn.base_type_exprs: - if isinstance(expr, RefExpr) and expr.fullname in TPDICT_NAMES: + ok, maybe_type_info, _ = self.check_typeddict(expr, None, False) + if ok and maybe_type_info is not None: + # expr is a CallExpr + info = maybe_type_info + typeddict_bases_set.add(info.fullname) + typeddict_bases.append(expr) + elif isinstance(expr, RefExpr) and expr.fullname in TPDICT_NAMES: if "TypedDict" not in typeddict_bases_set: typeddict_bases_set.add("TypedDict") else: @@ -145,12 +156,9 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> tuple[bool, TypeInfo | N # Iterate over bases in reverse order so that leftmost base class' keys take precedence for base in reversed(typeddict_bases): self.add_keys_and_types_from_base(base, keys, types, required_keys, defn) - ( - new_keys, - new_types, - new_statements, - new_required_keys, - ) = self.analyze_typeddict_classdef_fields(defn, keys) + (new_keys, new_types, new_statements, new_required_keys) = ( + self.analyze_typeddict_classdef_fields(defn, keys) + ) if new_keys is None: return True, None # Defer keys.extend(new_keys) @@ -173,12 +181,11 @@ def add_keys_and_types_from_base( required_keys: set[str], ctx: Context, ) -> None: + base_args: list[Type] = [] if isinstance(base, RefExpr): assert isinstance(base.node, TypeInfo) info = base.node - base_args: list[Type] = [] - else: - assert isinstance(base, IndexExpr) + elif isinstance(base, IndexExpr): assert isinstance(base.base, RefExpr) assert isinstance(base.base.node, TypeInfo) info = base.base.node @@ -186,6 +193,10 @@ def add_keys_and_types_from_base( if args is None: return base_args = args + else: + assert isinstance(base, CallExpr) + assert isinstance(base.analyzed, TypedDictExpr) + info = base.analyzed.info assert info.typeddict_type is not None base_typed_dict = info.typeddict_type @@ -201,7 +212,8 @@ def add_keys_and_types_from_base( any_kind = TypeOfAny.from_error base_args = [AnyType(any_kind) for _ in tvars] - valid_items = self.map_items_to_base(valid_items, tvars, base_args) + with state.strict_optional_set(self.options.strict_optional): + valid_items = self.map_items_to_base(valid_items, tvars, base_args) for key in base_items: if key in keys: self.fail(f'Overwriting TypedDict field "{key}" while merging', ctx) @@ -228,10 +240,7 @@ def analyze_base_args(self, base: IndexExpr, ctx: Context) -> list[Type] | None: self.fail("Invalid TypedDict type argument", ctx) return None analyzed = self.api.anal_type( - type, - allow_required=True, - allow_placeholder=not self.options.disable_recursive_aliases - and not self.api.is_func_scope(), + type, allow_required=True, allow_placeholder=not self.api.is_func_scope() ) if analyzed is None: return None @@ -243,10 +252,8 @@ def map_items_to_base( ) -> dict[str, Type]: """Map item types to how they would look in their base with type arguments applied. - We would normally use expand_type() for such task, but we can't use it during - semantic analysis, because it can (indirectly) call is_subtype() etc., and it - will crash on placeholder types. So we hijack replace_alias_tvars() that was initially - intended to deal with eager expansion of generic type aliases during semantic analysis. + Note it is safe to use expand_type() during semantic analysis, because it should never + (indirectly) call is_subtype(). """ mapped_items = {} for key in valid_items: @@ -254,10 +261,10 @@ def map_items_to_base( if not tvars: mapped_items[key] = type_in_base continue - mapped_type = replace_alias_tvars( - type_in_base, tvars, base_args, type_in_base.line, type_in_base.column + # TODO: simple zip can't be used for variadic types. + mapped_items[key] = expand_type( + type_in_base, {t.id: a for (t, a) in zip(tvars, base_args)} ) - mapped_items[key] = mapped_type return mapped_items def analyze_typeddict_classdef_fields( @@ -309,8 +316,7 @@ def analyze_typeddict_classdef_fields( analyzed = self.api.anal_type( stmt.type, allow_required=True, - allow_placeholder=not self.options.disable_recursive_aliases - and not self.api.is_func_scope(), + allow_placeholder=not self.api.is_func_scope(), prohibit_self_type="TypedDict item type", ) if analyzed is None: @@ -325,6 +331,12 @@ def analyze_typeddict_classdef_fields( total: bool | None = True if "total" in defn.keywords: total = require_bool_literal_argument(self.api, defn.keywords["total"], "total", True) + if defn.keywords and defn.keywords.keys() != {"total"}: + for_function = ' for "__init_subclass__" of "TypedDict"' + for key in defn.keywords: + if key == "total": + continue + self.msg.unexpected_keyword_argument_for_function(for_function, key, defn) required_keys = { field for (field, t) in zip(fields, types) @@ -368,7 +380,13 @@ def check_typeddict( name, items, types, total, tvar_defs, ok = res if not ok: # Error. Construct dummy return value. - info = self.build_typeddict_typeinfo("TypedDict", [], [], set(), call.line, None) + if var_name: + name = var_name + if is_func_scope: + name += "@" + str(call.line) + else: + name = var_name = "TypedDict@" + str(call.line) + info = self.build_typeddict_typeinfo(name, [], [], set(), call.line, None) else: if var_name is not None and name != var_name: self.fail( @@ -390,6 +408,17 @@ def check_typeddict( types = [ # unwrap Required[T] to just T t.item if isinstance(t, RequiredType) else t for t in types ] + + # Perform various validations after unwrapping. + for t in types: + check_for_explicit_any( + t, self.options, self.api.is_typeshed_stub_file, self.msg, context=call + ) + if self.options.disallow_any_unimported: + for t in types: + if has_any_from_unimported_type(t): + self.msg.unimported_type_becomes_any("Type of a TypedDict key", t, call) + existing_info = None if isinstance(node.analyzed, TypedDictExpr): existing_info = node.analyzed.info @@ -397,9 +426,9 @@ def check_typeddict( name, items, types, required_keys, call.line, existing_info ) info.line = node.line - # Store generated TypeInfo under both names, see semanal_namedtuple for more details. - if name != var_name or is_func_scope: - self.api.add_symbol_skip_local(name, info) + # Store generated TypeInfo under both names, see semanal_namedtuple for more details. + if name != var_name or is_func_scope: + self.api.add_symbol_skip_local(name, info) if var_name: self.api.add_symbol(var_name, info, node) call.analyzed = TypedDictExpr(info) @@ -447,15 +476,6 @@ def parse_typeddict_args( # One of the types is not ready, defer. return None items, types, ok = res - for t in types: - check_for_explicit_any( - t, self.options, self.api.is_typeshed_stub_file, self.msg, context=call - ) - - if self.options.disallow_any_unimported: - for t in types: - if has_any_from_unimported_type(t): - self.msg.unimported_type_becomes_any("Type of a TypedDict key", t, dictexpr) assert total is not None return args[0].value, items, types, total, tvar_defs, ok @@ -469,7 +489,7 @@ def parse_typeddict_fields_with_types( seen_keys = set() items: list[str] = [] types: list[Type] = [] - for (field_name_expr, field_type_expr) in dict_items: + for field_name_expr, field_type_expr in dict_items: if isinstance(field_name_expr, StrExpr): key = field_name_expr.value items.append(key) @@ -500,8 +520,7 @@ def parse_typeddict_fields_with_types( analyzed = self.api.anal_type( type, allow_required=True, - allow_placeholder=not self.options.disable_recursive_aliases - and not self.api.is_func_scope(), + allow_placeholder=not self.api.is_func_scope(), prohibit_self_type="TypedDict item type", ) if analyzed is None: diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index 40b60f1a69d8..5323bf2c57cb 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -52,7 +52,7 @@ class level -- these are handled at attribute level (say, 'mod.Cls.method' from __future__ import annotations -from typing import Sequence, Tuple, Union, cast +from typing import Sequence, Tuple, Union from typing_extensions import TypeAlias as _TypeAlias from mypy.expandtype import expand_type @@ -73,6 +73,8 @@ class level -- these are handled at attribute level (say, 'mod.Cls.method' TypeVarTupleExpr, Var, ) +from mypy.semanal_shared import find_dataclass_transform_spec +from mypy.state import state from mypy.types import ( AnyType, CallableType, @@ -189,6 +191,7 @@ def snapshot_symbol_table(name_prefix: str, table: SymbolTable) -> dict[str, Sym node.variance, [snapshot_type(value) for value in node.values], snapshot_type(node.upper_bound), + snapshot_type(node.default), ) elif isinstance(node, TypeAlias): result[name] = ( @@ -199,9 +202,19 @@ def snapshot_symbol_table(name_prefix: str, table: SymbolTable) -> dict[str, Sym snapshot_optional_type(node.target), ) elif isinstance(node, ParamSpecExpr): - result[name] = ("ParamSpec", node.variance, snapshot_type(node.upper_bound)) + result[name] = ( + "ParamSpec", + node.variance, + snapshot_type(node.upper_bound), + snapshot_type(node.default), + ) elif isinstance(node, TypeVarTupleExpr): - result[name] = ("TypeVarTuple", node.variance, snapshot_type(node.upper_bound)) + result[name] = ( + "TypeVarTuple", + node.variance, + snapshot_type(node.upper_bound), + snapshot_type(node.default), + ) else: assert symbol.kind != UNBOUND_IMPORTED if node and get_prefix(node.fullname) != name_prefix: @@ -230,6 +243,7 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb elif isinstance(node, OverloadedFuncDef) and node.impl: impl = node.impl.func if isinstance(node.impl, Decorator) else node.impl is_trivial_body = impl.is_trivial_body if impl else False + dataclass_transform_spec = find_dataclass_transform_spec(node) return ( "Func", common, @@ -239,6 +253,7 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb node.is_static, signature, is_trivial_body, + dataclass_transform_spec.serialize() if dataclass_transform_spec is not None else None, ) elif isinstance(node, Var): return ("Var", common, snapshot_optional_type(node.type), node.is_final) @@ -256,6 +271,10 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb snapshot_definition(node.func, common), ) elif isinstance(node, TypeInfo): + dataclass_transform_spec = node.dataclass_transform_spec + if dataclass_transform_spec is None: + dataclass_transform_spec = find_dataclass_transform_spec(node) + attrs = ( node.is_abstract, node.is_enum, @@ -280,6 +299,7 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb tuple(snapshot_type(tdef) for tdef in node.defn.type_vars), [snapshot_type(base) for base in node.bases], [snapshot_type(p) for p in node._promote], + dataclass_transform_spec.serialize() if dataclass_transform_spec is not None else None, ) prefix = node.fullname symbol_table = snapshot_symbol_table(prefix, node.names) @@ -374,6 +394,7 @@ def visit_type_var(self, typ: TypeVarType) -> SnapshotItem: typ.id.meta_level, snapshot_types(typ.values), snapshot_type(typ.upper_bound), + snapshot_type(typ.default), typ.variance, ) @@ -384,6 +405,7 @@ def visit_param_spec(self, typ: ParamSpecType) -> SnapshotItem: typ.id.meta_level, typ.flavor, snapshot_type(typ.upper_bound), + snapshot_type(typ.default), ) def visit_type_var_tuple(self, typ: TypeVarTupleType) -> SnapshotItem: @@ -392,6 +414,7 @@ def visit_type_var_tuple(self, typ: TypeVarTupleType) -> SnapshotItem: typ.id.raw_id, typ.id.meta_level, snapshot_type(typ.upper_bound), + snapshot_type(typ.default), ) def visit_unpack_type(self, typ: UnpackType) -> SnapshotItem: @@ -434,7 +457,8 @@ def normalize_callable_variables(self, typ: CallableType) -> CallableType: tv = v.copy_modified(id=tid) tvs.append(tv) tvmap[v.id] = tv - return cast(CallableType, expand_type(typ, tvmap)).copy_modified(variables=tvs) + with state.strict_optional_set(True): + return expand_type(typ, tvmap).copy_modified(variables=tvs) def visit_tuple_type(self, typ: TupleType) -> SnapshotItem: return ("TupleType", snapshot_types(typ.items)) diff --git a/mypy/server/astmerge.py b/mypy/server/astmerge.py index 1ec6d572a82c..e6648fbb4be7 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -73,7 +73,6 @@ SymbolNode, SymbolTable, TypeAlias, - TypeAliasExpr, TypedDictExpr, TypeInfo, Var, @@ -250,6 +249,15 @@ def process_type_var_def(self, tv: TypeVarType) -> None: for value in tv.values: self.fixup_type(value) self.fixup_type(tv.upper_bound) + self.fixup_type(tv.default) + + def process_param_spec_def(self, tv: ParamSpecType) -> None: + self.fixup_type(tv.upper_bound) + self.fixup_type(tv.default) + + def process_type_var_tuple_def(self, tv: TypeVarTupleType) -> None: + self.fixup_type(tv.upper_bound) + self.fixup_type(tv.default) def visit_assignment_stmt(self, node: AssignmentStmt) -> None: self.fixup_type(node.type) @@ -317,10 +325,6 @@ def visit_enum_call_expr(self, node: EnumCallExpr) -> None: self.process_synthetic_type_info(node.info) super().visit_enum_call_expr(node) - def visit_type_alias_expr(self, node: TypeAliasExpr) -> None: - self.fixup_type(node.type) - super().visit_type_alias_expr(node) - # Others def visit_var(self, node: Var) -> None: @@ -358,7 +362,8 @@ def fixup_and_reset_typeinfo(self, node: TypeInfo) -> TypeInfo: if node in self.replacements: # The subclass relationships may change, so reset all caches relevant to the # old MRO. - new = cast(TypeInfo, self.replacements[node]) + new = self.replacements[node] + assert isinstance(new, TypeInfo) type_state.reset_all_subtype_caches_for(new) return self.fixup(node) @@ -389,7 +394,7 @@ def process_synthetic_type_info(self, info: TypeInfo) -> None: # have bodies in the AST so we need to iterate over their symbol # tables separately, unlike normal classes. self.process_type_info(info) - for name, node in info.names.items(): + for node in info.names.values(): if node.node: node.node.accept(self) @@ -457,13 +462,13 @@ def visit_overloaded(self, t: Overloaded) -> None: def visit_erased_type(self, t: ErasedType) -> None: # This type should exist only temporarily during type inference - raise RuntimeError + raise RuntimeError("Cannot handle erased type") def visit_deleted_type(self, typ: DeletedType) -> None: pass def visit_partial_type(self, typ: PartialType) -> None: - raise RuntimeError + raise RuntimeError("Cannot handle partial type") def visit_tuple_type(self, typ: TupleType) -> None: for item in typ.items: @@ -477,14 +482,17 @@ def visit_type_type(self, typ: TypeType) -> None: def visit_type_var(self, typ: TypeVarType) -> None: typ.upper_bound.accept(self) + typ.default.accept(self) for value in typ.values: value.accept(self) def visit_param_spec(self, typ: ParamSpecType) -> None: - pass + typ.upper_bound.accept(self) + typ.default.accept(self) def visit_type_var_tuple(self, typ: TypeVarTupleType) -> None: typ.upper_bound.accept(self) + typ.default.accept(self) def visit_unpack_type(self, typ: UnpackType) -> None: typ.type.accept(self) @@ -499,7 +507,8 @@ def visit_typeddict_type(self, typ: TypedDictType) -> None: typ.fallback.accept(self) def visit_raw_expression_type(self, t: RawExpressionType) -> None: - pass + if t.node is not None: + t.node.accept(self) def visit_literal_type(self, typ: LiteralType) -> None: typ.fallback.accept(self) @@ -541,7 +550,7 @@ def fixup(self, node: SN) -> SN: def replace_nodes_in_symbol_table( symbols: SymbolTable, replacements: dict[SymbolNode, SymbolNode] ) -> None: - for name, node in symbols.items(): + for node in symbols.values(): if node.node: if node.node in replacements: new = replacements[node.node] diff --git a/mypy/server/deps.py b/mypy/server/deps.py index 50b66b70b8aa..9ed2d4549629 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -472,7 +472,7 @@ def visit_assignment_stmt(self, o: AssignmentStmt) -> None: self.add_dependency(make_trigger(class_name + ".__init__")) self.add_dependency(make_trigger(class_name + ".__new__")) if isinstance(rvalue, IndexExpr) and isinstance(rvalue.analyzed, TypeAliasExpr): - self.add_type_dependencies(rvalue.analyzed.type) + self.add_type_dependencies(rvalue.analyzed.node.target) elif typ: self.add_type_dependencies(typ) else: @@ -1027,7 +1027,7 @@ def visit_tuple_type(self, typ: TupleType) -> list[str]: def visit_type_type(self, typ: TypeType) -> list[str]: triggers = self.get_type_triggers(typ.item) if not self.use_logical_deps: - old_triggers = triggers[:] + old_triggers = triggers.copy() for trigger in old_triggers: triggers.append(trigger.rstrip(">") + ".__init__>") triggers.append(trigger.rstrip(">") + ".__new__>") @@ -1039,6 +1039,8 @@ def visit_type_var(self, typ: TypeVarType) -> list[str]: triggers.append(make_trigger(typ.fullname)) if typ.upper_bound: triggers.extend(self.get_type_triggers(typ.upper_bound)) + if typ.default: + triggers.extend(self.get_type_triggers(typ.default)) for val in typ.values: triggers.extend(self.get_type_triggers(val)) return triggers @@ -1047,6 +1049,10 @@ def visit_param_spec(self, typ: ParamSpecType) -> list[str]: triggers = [] if typ.fullname: triggers.append(make_trigger(typ.fullname)) + if typ.upper_bound: + triggers.extend(self.get_type_triggers(typ.upper_bound)) + if typ.default: + triggers.extend(self.get_type_triggers(typ.default)) triggers.extend(self.get_type_triggers(typ.upper_bound)) return triggers @@ -1054,6 +1060,10 @@ def visit_type_var_tuple(self, typ: TypeVarTupleType) -> list[str]: triggers = [] if typ.fullname: triggers.append(make_trigger(typ.fullname)) + if typ.upper_bound: + triggers.extend(self.get_type_triggers(typ.upper_bound)) + if typ.default: + triggers.extend(self.get_type_triggers(typ.default)) triggers.extend(self.get_type_triggers(typ.upper_bound)) return triggers diff --git a/mypy/server/mergecheck.py b/mypy/server/mergecheck.py index ef6f5b86c8f3..6f044a5ea8b9 100644 --- a/mypy/server/mergecheck.py +++ b/mypy/server/mergecheck.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing_extensions import Final +from typing import Final from mypy.nodes import Decorator, FakeInfo, FuncDef, SymbolNode, Var from mypy.server.objgraph import get_path, get_reachable_graph diff --git a/mypy/server/objgraph.py b/mypy/server/objgraph.py index 89a086b8a0ab..a13fd8412934 100644 --- a/mypy/server/objgraph.py +++ b/mypy/server/objgraph.py @@ -5,8 +5,7 @@ import types import weakref from collections.abc import Iterable -from typing import Iterator, Mapping -from typing_extensions import Final +from typing import Final, Iterator, Mapping method_descriptor_type: Final = type(object.__dir__) method_wrapper_type: Final = type(object().__ne__) @@ -46,7 +45,7 @@ def get_edge_candidates(o: object) -> Iterator[tuple[object, object]]: try: if attr not in ATTR_BLACKLIST and hasattr(o, attr) and not isproperty(o, attr): e = getattr(o, attr) - if not type(e) in ATOMIC_TYPE_BLACKLIST: + if type(e) not in ATOMIC_TYPE_BLACKLIST: yield attr, e except AssertionError: pass @@ -70,7 +69,7 @@ def get_edges(o: object) -> Iterator[tuple[object, object]]: if se is not o and se is not type(o) and hasattr(s, "__self__"): yield s.__self__, se else: - if not type(e) in TYPE_BLACKLIST: + if type(e) not in TYPE_BLACKLIST: yield s, e diff --git a/mypy/server/trigger.py b/mypy/server/trigger.py index 5f2115739d38..97b5f89cd3ba 100644 --- a/mypy/server/trigger.py +++ b/mypy/server/trigger.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing_extensions import Final +from typing import Final # Used as a suffix for triggers to handle "from m import *" dependencies (see also # make_wildcard_trigger) diff --git a/mypy/server/update.py b/mypy/server/update.py index 00b823c99dfd..0cc7a2229514 100644 --- a/mypy/server/update.py +++ b/mypy/server/update.py @@ -118,8 +118,8 @@ import re import sys import time -from typing import Callable, NamedTuple, Sequence, Union -from typing_extensions import Final, TypeAlias as _TypeAlias +from typing import Callable, Final, NamedTuple, Sequence, Union +from typing_extensions import TypeAlias as _TypeAlias from mypy.build import ( DEBUG_FINE_GRAINED, @@ -187,7 +187,7 @@ def __init__(self, result: BuildResult) -> None: # Merge in any root dependencies that may not have been loaded merge_dependencies(manager.load_fine_grained_deps(FAKE_ROOT_MODULE), self.deps) self.previous_targets_with_errors = manager.errors.targets() - self.previous_messages: list[str] = result.errors[:] + self.previous_messages: list[str] = result.errors.copy() # Module, if any, that had blocking errors in the last run as (id, path) tuple. self.blocking_error: tuple[str, str] | None = None # Module that we haven't processed yet but that are known to be stale. @@ -302,7 +302,7 @@ def update( break messages = sort_messages_preserving_file_order(messages, self.previous_messages) - self.previous_messages = messages[:] + self.previous_messages = messages.copy() return messages def trigger(self, target: str) -> list[str]: @@ -322,7 +322,7 @@ def trigger(self, target: str) -> list[str]: ) # Preserve state needed for the next update. self.previous_targets_with_errors = self.manager.errors.targets() - self.previous_messages = self.manager.errors.new_messages()[:] + self.previous_messages = self.manager.errors.new_messages().copy() return self.update(changed_modules, []) def flush_cache(self) -> None: @@ -986,6 +986,7 @@ def key(node: FineGrainedDeferredNode) -> int: manager.errors.set_file_ignored_lines( file_node.path, file_node.ignored_lines, options.ignore_errors or state.ignore_all ) + manager.errors.set_skipped_lines(file_node.path, file_node.skipped_lines) targets = set() for node in nodes: diff --git a/mypy/sharedparse.py b/mypy/sharedparse.py index 6f864ccce816..ef2e4f720664 100644 --- a/mypy/sharedparse.py +++ b/mypy/sharedparse.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing_extensions import Final +from typing import Final """Shared logic between our three mypy parser files.""" diff --git a/mypy/solve.py b/mypy/solve.py index b8304d29c1ce..9770364bf892 100644 --- a/mypy/solve.py +++ b/mypy/solve.py @@ -3,92 +3,560 @@ from __future__ import annotations from collections import defaultdict +from typing import Iterable, Sequence +from typing_extensions import TypeAlias as _TypeAlias -from mypy.constraints import SUPERTYPE_OF, Constraint +from mypy.constraints import SUBTYPE_OF, SUPERTYPE_OF, Constraint, infer_constraints, neg_op +from mypy.expandtype import expand_type +from mypy.graph_utils import prepare_sccs, strongly_connected_components, topsort from mypy.join import join_types -from mypy.meet import meet_types +from mypy.meet import meet_type_list, meet_types from mypy.subtypes import is_subtype +from mypy.typeops import get_all_type_vars from mypy.types import ( AnyType, + Instance, + NoneType, + ParamSpecType, ProperType, + TupleType, Type, TypeOfAny, TypeVarId, + TypeVarLikeType, + TypeVarTupleType, + TypeVarType, UninhabitedType, UnionType, + UnpackType, get_proper_type, ) from mypy.typestate import type_state +Bounds: _TypeAlias = "dict[TypeVarId, set[Type]]" +Graph: _TypeAlias = "set[tuple[TypeVarId, TypeVarId]]" +Solutions: _TypeAlias = "dict[TypeVarId, Type | None]" + def solve_constraints( - vars: list[TypeVarId], constraints: list[Constraint], strict: bool = True -) -> list[Type | None]: + original_vars: Sequence[TypeVarLikeType], + constraints: list[Constraint], + strict: bool = True, + allow_polymorphic: bool = False, + skip_unsatisfied: bool = False, +) -> tuple[list[Type | None], list[TypeVarLikeType]]: """Solve type constraints. - Return the best type(s) for type variables; each type can be None if the value of the variable - could not be solved. + Return the best type(s) for type variables; each type can be None if the value of + the variable could not be solved. If a variable has no constraints, if strict=True then arbitrarily - pick NoneType as the value of the type variable. If strict=False, - pick AnyType. + pick UninhabitedType as the value of the type variable. If strict=False, pick AnyType. + If allow_polymorphic=True, then use the full algorithm that can potentially return + free type variables in solutions (these require special care when applying). Otherwise, + use a simplified algorithm that just solves each type variable individually if possible. + + The skip_unsatisfied flag matches the same one in applytype.apply_generic_arguments(). """ + vars = [tv.id for tv in original_vars] + if not vars: + return [], [] + + originals = {tv.id: tv for tv in original_vars} + extra_vars: list[TypeVarId] = [] + # Get additional type variables from generic actuals. + for c in constraints: + extra_vars.extend([v.id for v in c.extra_tvars if v.id not in vars + extra_vars]) + originals.update({v.id: v for v in c.extra_tvars if v.id not in originals}) + + if allow_polymorphic: + # Constraints inferred from unions require special handling in polymorphic inference. + constraints = skip_reverse_union_constraints(constraints) + # Collect a list of constraints for each type variable. - cmap: dict[TypeVarId, list[Constraint]] = defaultdict(list) + cmap: dict[TypeVarId, list[Constraint]] = {tv: [] for tv in vars + extra_vars} for con in constraints: - cmap[con.type_var].append(con) + if con.type_var in vars + extra_vars: + cmap[con.type_var].append(con) - res: list[Type | None] = [] + if allow_polymorphic: + if constraints: + solutions, free_vars = solve_with_dependent( + vars + extra_vars, constraints, vars, originals + ) + else: + solutions = {} + free_vars = [] + else: + solutions = {} + free_vars = [] + for tv, cs in cmap.items(): + if not cs: + continue + lowers = [c.target for c in cs if c.op == SUPERTYPE_OF] + uppers = [c.target for c in cs if c.op == SUBTYPE_OF] + solution = solve_one(lowers, uppers) + + # Do not leak type variables in non-polymorphic solutions. + if solution is None or not get_vars( + solution, [tv for tv in extra_vars if tv not in vars] + ): + solutions[tv] = solution - # Solve each type variable separately. - for tvar in vars: - bottom: Type | None = None - top: Type | None = None - candidate: Type | None = None - - # Process each constraint separately, and calculate the lower and upper - # bounds based on constraints. Note that we assume that the constraint - # targets do not have constraint references. - for c in cmap.get(tvar, []): - if c.op == SUPERTYPE_OF: - if bottom is None: - bottom = c.target - else: - if type_state.infer_unions: - # This deviates from the general mypy semantics because - # recursive types are union-heavy in 95% of cases. - bottom = UnionType.make_union([bottom, c.target]) - else: - bottom = join_types(bottom, c.target) + res: list[Type | None] = [] + for v in vars: + if v in solutions: + res.append(solutions[v]) + else: + # No constraints for type variable -- 'UninhabitedType' is the most specific type. + candidate: Type + if strict: + candidate = UninhabitedType() + candidate.ambiguous = True else: - if top is None: - top = c.target - else: - top = meet_types(top, c.target) - - p_top = get_proper_type(top) - p_bottom = get_proper_type(bottom) - if isinstance(p_top, AnyType) or isinstance(p_bottom, AnyType): - source_any = top if isinstance(p_top, AnyType) else bottom - assert isinstance(source_any, ProperType) and isinstance(source_any, AnyType) - res.append(AnyType(TypeOfAny.from_another_any, source_any=source_any)) + candidate = AnyType(TypeOfAny.special_form) + res.append(candidate) + + if not free_vars and not skip_unsatisfied: + # Most of the validation for solutions is done in applytype.py, but here we can + # quickly test solutions w.r.t. to upper bounds, and use the latter (if possible), + # if solutions are actually not valid (due to poor inference context). + res = pre_validate_solutions(res, original_vars, constraints) + + return res, free_vars + + +def solve_with_dependent( + vars: list[TypeVarId], + constraints: list[Constraint], + original_vars: list[TypeVarId], + originals: dict[TypeVarId, TypeVarLikeType], +) -> tuple[Solutions, list[TypeVarLikeType]]: + """Solve set of constraints that may depend on each other, like T <: List[S]. + + The whole algorithm consists of five steps: + * Propagate via linear constraints and use secondary constraints to get transitive closure + * Find dependencies between type variables, group them in SCCs, and sort topologically + * Check that all SCC are intrinsically linear, we can't solve (express) T <: List[T] + * Variables in leaf SCCs that don't have constant bounds are free (choose one per SCC) + * Solve constraints iteratively starting from leafs, updating bounds after each step. + """ + graph, lowers, uppers = transitive_closure(vars, constraints) + + dmap = compute_dependencies(vars, graph, lowers, uppers) + sccs = list(strongly_connected_components(set(vars), dmap)) + if not all(check_linear(scc, lowers, uppers) for scc in sccs): + return {}, [] + raw_batches = list(topsort(prepare_sccs(sccs, dmap))) + + free_vars = [] + free_solutions = {} + for scc in raw_batches[0]: + # If there are no bounds on this SCC, then the only meaningful solution we can + # express, is that each variable is equal to a new free variable. For example, + # if we have T <: S, S <: U, we deduce: T = S = U = . + if all(not lowers[tv] and not uppers[tv] for tv in scc): + best_free = choose_free([originals[tv] for tv in scc], original_vars) + if best_free: + # TODO: failing to choose may cause leaking type variables, + # we need to fail gracefully instead. + free_vars.append(best_free.id) + free_solutions[best_free.id] = best_free + + # Update lowers/uppers with free vars, so these can now be used + # as valid solutions. + for l, u in graph: + if l in free_vars: + lowers[u].add(free_solutions[l]) + if u in free_vars: + uppers[l].add(free_solutions[u]) + + # Flatten the SCCs that are independent, we can solve them together, + # since we don't need to update any targets in between. + batches = [] + for batch in raw_batches: + next_bc = [] + for scc in batch: + next_bc.extend(list(scc)) + batches.append(next_bc) + + solutions: dict[TypeVarId, Type | None] = {} + for flat_batch in batches: + res = solve_iteratively(flat_batch, graph, lowers, uppers) + solutions.update(res) + return solutions, [free_solutions[tv] for tv in free_vars] + + +def solve_iteratively( + batch: list[TypeVarId], graph: Graph, lowers: Bounds, uppers: Bounds +) -> Solutions: + """Solve transitive closure sequentially, updating upper/lower bounds after each step. + + Transitive closure is represented as a linear graph plus lower/upper bounds for each + type variable, see transitive_closure() docstring for details. + + We solve for type variables that appear in `batch`. If a bound is not constant (i.e. it + looks like T :> F[S, ...]), we substitute solutions found so far in the target F[S, ...] + after solving the batch. + + Importantly, after solving each variable in a batch, we move it from linear graph to + upper/lower bounds, this way we can guarantee consistency of solutions (see comment below + for an example when this is important). + """ + solutions = {} + s_batch = set(batch) + while s_batch: + for tv in sorted(s_batch, key=lambda x: x.raw_id): + if lowers[tv] or uppers[tv]: + solvable_tv = tv + break + else: + break + # Solve each solvable type variable separately. + s_batch.remove(solvable_tv) + result = solve_one(lowers[solvable_tv], uppers[solvable_tv]) + solutions[solvable_tv] = result + if result is None: + # TODO: support backtracking lower/upper bound choices and order within SCCs. + # (will require switching this function from iterative to recursive). continue - elif bottom is None: - if top: - candidate = top + + # Update the (transitive) bounds from graph if there is a solution. + # This is needed to guarantee solutions will never contradict the initial + # constraints. For example, consider {T <: S, T <: A, S :> B} with A :> B. + # If we would not update the uppers/lowers from graph, we would infer T = A, S = B + # which is not correct. + for l, u in graph.copy(): + if l == u: + continue + if l == solvable_tv: + lowers[u].add(result) + graph.remove((l, u)) + if u == solvable_tv: + uppers[l].add(result) + graph.remove((l, u)) + + # We can update uppers/lowers only once after solving the whole SCC, + # since uppers/lowers can't depend on type variables in the SCC + # (and we would reject such SCC as non-linear and therefore not solvable). + subs = {tv: s for (tv, s) in solutions.items() if s is not None} + for tv in lowers: + lowers[tv] = {expand_type(lt, subs) for lt in lowers[tv]} + for tv in uppers: + uppers[tv] = {expand_type(ut, subs) for ut in uppers[tv]} + return solutions + + +def solve_one(lowers: Iterable[Type], uppers: Iterable[Type]) -> Type | None: + """Solve constraints by finding by using meets of upper bounds, and joins of lower bounds.""" + bottom: Type | None = None + top: Type | None = None + candidate: Type | None = None + + # Filter out previous results of failed inference, they will only spoil the current pass... + new_uppers = [] + for u in uppers: + pu = get_proper_type(u) + if not isinstance(pu, UninhabitedType) or not pu.ambiguous: + new_uppers.append(u) + uppers = new_uppers + + # ...unless this is the only information we have, then we just pass it on. + if not uppers and not lowers: + candidate = UninhabitedType() + candidate.ambiguous = True + return candidate + + # Process each bound separately, and calculate the lower and upper + # bounds based on constraints. Note that we assume that the constraint + # targets do not have constraint references. + for target in lowers: + if bottom is None: + bottom = target + else: + if type_state.infer_unions: + # This deviates from the general mypy semantics because + # recursive types are union-heavy in 95% of cases. + bottom = UnionType.make_union([bottom, target]) else: - # No constraints for type variable -- 'UninhabitedType' is the most specific type. - if strict: - candidate = UninhabitedType() - candidate.ambiguous = True - else: - candidate = AnyType(TypeOfAny.special_form) - elif top is None: - candidate = bottom - elif is_subtype(bottom, top): - candidate = bottom + bottom = join_types(bottom, target) + + for target in uppers: + if top is None: + top = target else: - candidate = None - res.append(candidate) + top = meet_types(top, target) + + p_top = get_proper_type(top) + p_bottom = get_proper_type(bottom) + if isinstance(p_top, AnyType) or isinstance(p_bottom, AnyType): + source_any = top if isinstance(p_top, AnyType) else bottom + assert isinstance(source_any, ProperType) and isinstance(source_any, AnyType) + return AnyType(TypeOfAny.from_another_any, source_any=source_any) + elif bottom is None: + if top: + candidate = top + else: + # No constraints for type variable + return None + elif top is None: + candidate = bottom + elif is_subtype(bottom, top): + candidate = bottom + else: + candidate = None + return candidate + + +def choose_free( + scc: list[TypeVarLikeType], original_vars: list[TypeVarId] +) -> TypeVarLikeType | None: + """Choose the best solution for an SCC containing only type variables. + + This is needed to preserve e.g. the upper bound in a situation like this: + def dec(f: Callable[[T], S]) -> Callable[[T], S]: ... + @dec + def test(x: U) -> U: ... + + where U <: A. + """ + + if len(scc) == 1: + # Fast path, choice is trivial. + return scc[0] + + common_upper_bound = meet_type_list([t.upper_bound for t in scc]) + common_upper_bound_p = get_proper_type(common_upper_bound) + # We include None for when strict-optional is disabled. + if isinstance(common_upper_bound_p, (UninhabitedType, NoneType)): + # This will cause to infer Never, which is better than a free TypeVar + # that has an upper bound Never. + return None + + values: list[Type] = [] + for tv in scc: + if isinstance(tv, TypeVarType) and tv.values: + if values: + # It is too tricky to support multiple TypeVars with values + # within the same SCC. + return None + values = tv.values.copy() + + if values and not is_trivial_bound(common_upper_bound_p): + # If there are both values and upper bound present, we give up, + # since type variables having both are not supported. + return None + + # For convenience with current type application machinery, we use a stable + # choice that prefers the original type variables (not polymorphic ones) in SCC. + best = sorted(scc, key=lambda x: (x.id not in original_vars, x.id.raw_id))[0] + if isinstance(best, TypeVarType): + return best.copy_modified(values=values, upper_bound=common_upper_bound) + if is_trivial_bound(common_upper_bound_p, allow_tuple=True): + # TODO: support more cases for ParamSpecs/TypeVarTuples + return best + return None + + +def is_trivial_bound(tp: ProperType, allow_tuple: bool = False) -> bool: + if isinstance(tp, Instance) and tp.type.fullname == "builtins.tuple": + return allow_tuple and is_trivial_bound(get_proper_type(tp.args[0])) + return isinstance(tp, Instance) and tp.type.fullname == "builtins.object" + + +def find_linear(c: Constraint) -> tuple[bool, TypeVarId | None]: + """Find out if this constraint represent a linear relationship, return target id if yes.""" + if isinstance(c.origin_type_var, TypeVarType): + if isinstance(c.target, TypeVarType): + return True, c.target.id + if isinstance(c.origin_type_var, ParamSpecType): + if isinstance(c.target, ParamSpecType) and not c.target.prefix.arg_types: + return True, c.target.id + if isinstance(c.origin_type_var, TypeVarTupleType): + target = get_proper_type(c.target) + if isinstance(target, TupleType) and len(target.items) == 1: + item = target.items[0] + if isinstance(item, UnpackType) and isinstance(item.type, TypeVarTupleType): + return True, item.type.id + return False, None + + +def transitive_closure( + tvars: list[TypeVarId], constraints: list[Constraint] +) -> tuple[Graph, Bounds, Bounds]: + """Find transitive closure for given constraints on type variables. + + Transitive closure gives maximal set of lower/upper bounds for each type variable, + such that we cannot deduce any further bounds by chaining other existing bounds. + + The transitive closure is represented by: + * A set of lower and upper bounds for each type variable, where only constant and + non-linear terms are included in the bounds. + * A graph of linear constraints between type variables (represented as a set of pairs) + Such separation simplifies reasoning, and allows an efficient and simple incremental + transitive closure algorithm that we use here. + + For example if we have initial constraints [T <: S, S <: U, U <: int], the transitive + closure is given by: + * {} <: T <: {int} + * {} <: S <: {int} + * {} <: U <: {int} + * {T <: S, S <: U, T <: U} + """ + uppers: Bounds = defaultdict(set) + lowers: Bounds = defaultdict(set) + graph: Graph = {(tv, tv) for tv in tvars} + + remaining = set(constraints) + while remaining: + c = remaining.pop() + # Note that ParamSpec constraint P <: Q may be considered linear only if Q has no prefix, + # for cases like P <: Concatenate[T, Q] we should consider this non-linear and put {P} and + # {T, Q} into separate SCCs. Similarly, Ts <: Tuple[*Us] considered linear, while + # Ts <: Tuple[*Us, U] is non-linear. + is_linear, target_id = find_linear(c) + if is_linear and target_id in tvars: + assert target_id is not None + if c.op == SUBTYPE_OF: + lower, upper = c.type_var, target_id + else: + lower, upper = target_id, c.type_var + if (lower, upper) in graph: + continue + graph |= { + (l, u) for l in tvars for u in tvars if (l, lower) in graph and (upper, u) in graph + } + for u in tvars: + if (upper, u) in graph: + lowers[u] |= lowers[lower] + for l in tvars: + if (l, lower) in graph: + uppers[l] |= uppers[upper] + for lt in lowers[lower]: + for ut in uppers[upper]: + add_secondary_constraints(remaining, lt, ut) + elif c.op == SUBTYPE_OF: + if c.target in uppers[c.type_var]: + continue + for l in tvars: + if (l, c.type_var) in graph: + uppers[l].add(c.target) + for lt in lowers[c.type_var]: + add_secondary_constraints(remaining, lt, c.target) + else: + assert c.op == SUPERTYPE_OF + if c.target in lowers[c.type_var]: + continue + for u in tvars: + if (c.type_var, u) in graph: + lowers[u].add(c.target) + for ut in uppers[c.type_var]: + add_secondary_constraints(remaining, c.target, ut) + return graph, lowers, uppers + + +def add_secondary_constraints(cs: set[Constraint], lower: Type, upper: Type) -> None: + """Add secondary constraints inferred between lower and upper (in place).""" + if isinstance(get_proper_type(upper), UnionType) and isinstance( + get_proper_type(lower), UnionType + ): + # When both types are unions, this can lead to inferring spurious constraints, + # for example Union[T, int] <: S <: Union[T, int] may infer T <: int. + # To avoid this, just skip them for now. + return + # TODO: what if secondary constraints result in inference against polymorphic actual? + cs.update(set(infer_constraints(lower, upper, SUBTYPE_OF))) + cs.update(set(infer_constraints(upper, lower, SUPERTYPE_OF))) + + +def compute_dependencies( + tvars: list[TypeVarId], graph: Graph, lowers: Bounds, uppers: Bounds +) -> dict[TypeVarId, list[TypeVarId]]: + """Compute dependencies between type variables induced by constraints. + + If we have a constraint like T <: List[S], we say that T depends on S, since + we will need to solve for S first before we can solve for T. + """ + res = {} + for tv in tvars: + deps = set() + for lt in lowers[tv]: + deps |= get_vars(lt, tvars) + for ut in uppers[tv]: + deps |= get_vars(ut, tvars) + for other in tvars: + if other == tv: + continue + if (tv, other) in graph or (other, tv) in graph: + deps.add(other) + res[tv] = list(deps) return res + + +def check_linear(scc: set[TypeVarId], lowers: Bounds, uppers: Bounds) -> bool: + """Check there are only linear constraints between type variables in SCC. + + Linear are constraints like T <: S (while T <: F[S] are non-linear). + """ + for tv in scc: + if any(get_vars(lt, list(scc)) for lt in lowers[tv]): + return False + if any(get_vars(ut, list(scc)) for ut in uppers[tv]): + return False + return True + + +def skip_reverse_union_constraints(cs: list[Constraint]) -> list[Constraint]: + """Avoid ambiguities for constraints inferred from unions during polymorphic inference. + + Polymorphic inference implicitly relies on assumption that a reverse of a linear constraint + is a linear constraint. This is however not true in presence of union types, for example + T :> Union[S, int] vs S <: T. Trying to solve such constraints would be detected ambiguous + as (T, S) form a non-linear SCC. However, simply removing the linear part results in a valid + solution T = Union[S, int], S = . + + TODO: a cleaner solution may be to avoid inferring such constraints in first place, but + this would require passing around a flag through all infer_constraints() calls. + """ + reverse_union_cs = set() + for c in cs: + p_target = get_proper_type(c.target) + if isinstance(p_target, UnionType): + for item in p_target.items: + if isinstance(item, TypeVarType): + reverse_union_cs.add(Constraint(item, neg_op(c.op), c.origin_type_var)) + return [c for c in cs if c not in reverse_union_cs] + + +def get_vars(target: Type, vars: list[TypeVarId]) -> set[TypeVarId]: + """Find type variables for which we are solving in a target type.""" + return {tv.id for tv in get_all_type_vars(target)} & set(vars) + + +def pre_validate_solutions( + solutions: list[Type | None], + original_vars: Sequence[TypeVarLikeType], + constraints: list[Constraint], +) -> list[Type | None]: + """Check is each solution satisfies the upper bound of the corresponding type variable. + + If it doesn't satisfy the bound, check if bound itself satisfies all constraints, and + if yes, use it instead as a fallback solution. + """ + new_solutions: list[Type | None] = [] + for t, s in zip(original_vars, solutions): + if s is not None and not is_subtype(s, t.upper_bound): + bound_satisfies_all = True + for c in constraints: + if c.op == SUBTYPE_OF and not is_subtype(t.upper_bound, c.target): + bound_satisfies_all = False + break + if c.op == SUPERTYPE_OF and not is_subtype(c.target, t.upper_bound): + bound_satisfies_all = False + break + if bound_satisfies_all: + new_solutions.append(t.upper_bound) + continue + new_solutions.append(s) + return new_solutions diff --git a/mypy/state.py b/mypy/state.py index 2e44a936f819..cd3a360dd15f 100644 --- a/mypy/state.py +++ b/mypy/state.py @@ -1,8 +1,7 @@ from __future__ import annotations from contextlib import contextmanager -from typing import Iterator -from typing_extensions import Final +from typing import Final, Iterator # These are global mutable state. Don't add anything here unless there's a very # good reason. diff --git a/mypy/stats.py b/mypy/stats.py index b3a32c1ce72c..b167a41b0e34 100644 --- a/mypy/stats.py +++ b/mypy/stats.py @@ -5,8 +5,7 @@ import os from collections import Counter from contextlib import contextmanager -from typing import Iterator, cast -from typing_extensions import Final +from typing import Final, Iterator from mypy import nodes from mypy.argmap import map_formals_to_actuals @@ -154,10 +153,12 @@ def visit_func_def(self, o: FuncDef) -> None: ) return for defn in o.expanded: - self.visit_func_def(cast(FuncDef, defn)) + assert isinstance(defn, FuncDef) + self.visit_func_def(defn) else: if o.type: - sig = cast(CallableType, o.type) + assert isinstance(o.type, CallableType) + sig = o.type arg_types = sig.arg_types if sig.arg_names and sig.arg_names[0] == "self" and not self.inferred: arg_types = arg_types[1:] @@ -476,11 +477,6 @@ def is_complex(t: Type) -> bool: return is_generic(t) or isinstance(t, (FunctionLike, TupleType, TypeVarType)) -def ensure_dir_exists(dir: str) -> None: - if not os.path.exists(dir): - os.makedirs(dir) - - def is_special_form_any(t: AnyType) -> bool: return get_original_any(t).type_of_any == TypeOfAny.special_form diff --git a/mypy/strconv.py b/mypy/strconv.py index b2e9da5dbf6a..a96a27c45d75 100644 --- a/mypy/strconv.py +++ b/mypy/strconv.py @@ -7,11 +7,13 @@ from typing import TYPE_CHECKING, Any, Sequence import mypy.nodes +from mypy.options import Options from mypy.util import IdMapper, short_type from mypy.visitor import NodeVisitor if TYPE_CHECKING: import mypy.patterns + import mypy.types class StrConv(NodeVisitor[str]): @@ -26,12 +28,20 @@ class StrConv(NodeVisitor[str]): IntExpr(1))) """ - def __init__(self, show_ids: bool = False) -> None: + __slots__ = ["options", "show_ids", "id_mapper"] + + def __init__(self, *, show_ids: bool = False, options: Options) -> None: + self.options = options self.show_ids = show_ids self.id_mapper: IdMapper | None = None if show_ids: self.id_mapper = IdMapper() + def stringify_type(self, t: mypy.types.Type) -> str: + import mypy.types + + return t.accept(mypy.types.TypeStrVisitor(id_mapper=self.id_mapper, options=self.options)) + def get_id(self, o: object) -> int | None: if self.id_mapper: return self.id_mapper.id(o) @@ -76,6 +86,9 @@ def func_helper(self, o: mypy.nodes.FuncItem) -> list[object]: elif kind == mypy.nodes.ARG_STAR2: extra.append(("DictVarArg", [arg.variable])) a: list[Any] = [] + if o.type_args: + for p in o.type_args: + a.append(self.type_param(p)) if args: a.append(("Args", args)) if o.type: @@ -144,7 +157,7 @@ def visit_func_def(self, o: mypy.nodes.FuncDef) -> str: return self.dump(a, o) def visit_overloaded_func_def(self, o: mypy.nodes.OverloadedFuncDef) -> str: - a: Any = o.items[:] + a: Any = o.items.copy() if o.type: a.insert(0, o.type) if o.impl: @@ -168,15 +181,18 @@ def visit_class_def(self, o: mypy.nodes.ClassDef) -> str: if o.type_vars: a.insert(1, ("TypeVars", o.type_vars)) if o.metaclass: - a.insert(1, f"Metaclass({o.metaclass})") + a.insert(1, f"Metaclass({o.metaclass.accept(self)})") if o.decorators: a.insert(1, ("Decorators", o.decorators)) if o.info and o.info._promote: - a.insert(1, f"Promote({o.info._promote})") + a.insert(1, f"Promote([{','.join(self.stringify_type(p) for p in o.info._promote)}])") if o.info and o.info.tuple_type: a.insert(1, ("TupleType", [o.info.tuple_type])) if o.info and o.info.fallback_to_any: a.insert(1, "FallbackToAny") + if o.type_args: + for p in reversed(o.type_args): + a.insert(1, self.type_param(p)) return self.dump(a, o) def visit_var(self, o: mypy.nodes.Var) -> str: @@ -313,6 +329,28 @@ def visit_match_stmt(self, o: mypy.nodes.MatchStmt) -> str: a.append(("Body", o.bodies[i].body)) return self.dump(a, o) + def visit_type_alias_stmt(self, o: mypy.nodes.TypeAliasStmt) -> str: + a: list[Any] = [o.name] + for p in o.type_args: + a.append(self.type_param(p)) + a.append(o.value) + return self.dump(a, o) + + def type_param(self, p: mypy.nodes.TypeParam) -> list[Any]: + a: list[Any] = [] + if p.kind == mypy.nodes.PARAM_SPEC_KIND: + prefix = "**" + elif p.kind == mypy.nodes.TYPE_VAR_TUPLE_KIND: + prefix = "*" + else: + prefix = "" + a.append(prefix + p.name) + if p.upper_bound: + a.append(p.upper_bound) + if p.values: + a.append(("Values", p.values)) + return [("TypeParam", a)] + # Expressions # Simple expressions @@ -473,7 +511,7 @@ def visit_type_var_expr(self, o: mypy.nodes.TypeVarExpr) -> str: if o.values: a += [("Values", o.values)] if not mypy.types.is_named_instance(o.upper_bound, "builtins.object"): - a += [f"UpperBound({o.upper_bound})"] + a += [f"UpperBound({self.stringify_type(o.upper_bound)})"] return self.dump(a, o) def visit_paramspec_expr(self, o: mypy.nodes.ParamSpecExpr) -> str: @@ -485,7 +523,7 @@ def visit_paramspec_expr(self, o: mypy.nodes.ParamSpecExpr) -> str: if o.variance == mypy.nodes.CONTRAVARIANT: a += ["Variance(CONTRAVARIANT)"] if not mypy.types.is_named_instance(o.upper_bound, "builtins.object"): - a += [f"UpperBound({o.upper_bound})"] + a += [f"UpperBound({self.stringify_type(o.upper_bound)})"] return self.dump(a, o) def visit_type_var_tuple_expr(self, o: mypy.nodes.TypeVarTupleExpr) -> str: @@ -497,14 +535,14 @@ def visit_type_var_tuple_expr(self, o: mypy.nodes.TypeVarTupleExpr) -> str: if o.variance == mypy.nodes.CONTRAVARIANT: a += ["Variance(CONTRAVARIANT)"] if not mypy.types.is_named_instance(o.upper_bound, "builtins.object"): - a += [f"UpperBound({o.upper_bound})"] + a += [f"UpperBound({self.stringify_type(o.upper_bound)})"] return self.dump(a, o) def visit_type_alias_expr(self, o: mypy.nodes.TypeAliasExpr) -> str: - return f"TypeAliasExpr({o.type})" + return f"TypeAliasExpr({self.stringify_type(o.node.target)})" def visit_namedtuple_expr(self, o: mypy.nodes.NamedTupleExpr) -> str: - return f"NamedTupleExpr:{o.line}({o.info.name}, {o.info.tuple_type})" + return f"NamedTupleExpr:{o.line}({o.info.name}, {self.stringify_type(o.info.tuple_type) if o.info.tuple_type is not None else None})" def visit_enum_call_expr(self, o: mypy.nodes.EnumCallExpr) -> str: return f"EnumCallExpr:{o.line}({o.info.name}, {o.items})" @@ -513,7 +551,7 @@ def visit_typeddict_expr(self, o: mypy.nodes.TypedDictExpr) -> str: return f"TypedDictExpr:{o.line}({o.info.name})" def visit__promote_expr(self, o: mypy.nodes.PromoteExpr) -> str: - return f"PromoteExpr:{o.line}({o.type})" + return f"PromoteExpr:{o.line}({self.stringify_type(o.type)})" def visit_newtype_expr(self, o: mypy.nodes.NewTypeExpr) -> str: return f"NewTypeExpr:{o.line}({o.name}, {self.dump([o.old_type], o)})" @@ -614,7 +652,9 @@ def dump_tagged(nodes: Sequence[object], tag: str | None, str_conv: StrConv) -> elif isinstance(n, mypy.nodes.Node): a.append(indent(n.accept(str_conv), 2)) elif isinstance(n, Type): - a.append(indent(n.accept(TypeStrVisitor(str_conv.id_mapper)), 2)) + a.append( + indent(n.accept(TypeStrVisitor(str_conv.id_mapper, options=str_conv.options)), 2) + ) elif n is not None: a.append(indent(str(n), 2)) if tag: diff --git a/mypy/stubdoc.py b/mypy/stubdoc.py index 7c8751bbd6ed..8c0a4dab696f 100644 --- a/mypy/stubdoc.py +++ b/mypy/stubdoc.py @@ -8,10 +8,13 @@ import contextlib import io +import keyword import re import tokenize -from typing import Any, MutableMapping, MutableSequence, NamedTuple, Sequence, Tuple -from typing_extensions import Final, TypeAlias as _TypeAlias +from typing import Any, Final, MutableMapping, MutableSequence, NamedTuple, Sequence, Tuple +from typing_extensions import TypeAlias as _TypeAlias + +import mypy.util # Type alias for signatures strings in format ('func_name', '(arg, opt_arg=False)'). Sig: _TypeAlias = Tuple[str, str] @@ -33,13 +36,25 @@ def is_valid_type(s: str) -> bool: class ArgSig: """Signature info for a single argument.""" - def __init__(self, name: str, type: str | None = None, default: bool = False): + def __init__( + self, + name: str, + type: str | None = None, + *, + default: bool = False, + default_value: str = "...", + ) -> None: self.name = name - if type and not is_valid_type(type): - raise ValueError("Invalid type: " + type) self.type = type # Does this argument have a default value? self.default = default + self.default_value = default_value + + def is_star_arg(self) -> bool: + return self.name.startswith("*") and not self.name.startswith("**") + + def is_star_kwarg(self) -> bool: + return self.name.startswith("**") def __repr__(self) -> str: return "ArgSig(name={}, type={}, default={})".format( @@ -52,6 +67,7 @@ def __eq__(self, other: Any) -> bool: self.name == other.name and self.type == other.type and self.default == other.default + and self.default_value == other.default_value ) return False @@ -59,7 +75,80 @@ def __eq__(self, other: Any) -> bool: class FunctionSig(NamedTuple): name: str args: list[ArgSig] - ret_type: str + ret_type: str | None + + def is_special_method(self) -> bool: + return bool( + self.name.startswith("__") + and self.name.endswith("__") + and self.args + and self.args[0].name in ("self", "cls") + ) + + def has_catchall_args(self) -> bool: + """Return if this signature has catchall args: (*args, **kwargs)""" + if self.args and self.args[0].name in ("self", "cls"): + args = self.args[1:] + else: + args = self.args + return ( + len(args) == 2 + and all(a.type in (None, "object", "Any", "typing.Any") for a in args) + and args[0].is_star_arg() + and args[1].is_star_kwarg() + ) + + def is_catchall_signature(self) -> bool: + """Return if this signature is the catchall identity: (*args, **kwargs) -> Any""" + return self.has_catchall_args() and self.ret_type in (None, "Any", "typing.Any") + + def format_sig( + self, + indent: str = "", + is_async: bool = False, + any_val: str | None = None, + docstring: str | None = None, + ) -> str: + args: list[str] = [] + for arg in self.args: + arg_def = arg.name + + if arg_def in keyword.kwlist: + arg_def = "_" + arg_def + + if ( + arg.type is None + and any_val is not None + and arg.name not in ("self", "cls") + and not arg.name.startswith("*") + ): + arg_type: str | None = any_val + else: + arg_type = arg.type + if arg_type: + arg_def += ": " + arg_type + if arg.default: + arg_def += f" = {arg.default_value}" + + elif arg.default: + arg_def += f"={arg.default_value}" + + args.append(arg_def) + + retfield = "" + ret_type = self.ret_type if self.ret_type else any_val + if ret_type is not None: + retfield = " -> " + ret_type + + prefix = "async " if is_async else "" + sig = "{indent}{prefix}def {name}({args}){ret}:".format( + indent=indent, prefix=prefix, name=self.name, args=", ".join(args), ret=retfield + ) + if docstring: + suffix = f"\n{indent} {mypy.util.quote_docstring(docstring)}" + else: + suffix = " ..." + return f"{sig}{suffix}" # States of the docstring parser. @@ -176,17 +265,17 @@ def add_token(self, token: tokenize.TokenInfo) -> None: # arg_name is empty when there are no args. e.g. func() if self.arg_name: - try: + if self.arg_type and not is_valid_type(self.arg_type): + # wrong type, use Any + self.args.append( + ArgSig(name=self.arg_name, type=None, default=bool(self.arg_default)) + ) + else: self.args.append( ArgSig( name=self.arg_name, type=self.arg_type, default=bool(self.arg_default) ) ) - except ValueError: - # wrong type, use Any - self.args.append( - ArgSig(name=self.arg_name, type=None, default=bool(self.arg_default)) - ) self.arg_name = "" self.arg_type = None self.arg_default = None @@ -236,11 +325,11 @@ def args_kwargs(signature: FunctionSig) -> bool: return has_arg("*args", signature) and has_arg("**kwargs", signature) # Move functions with (*args, **kwargs) in their signature to last place. - return list(sorted(self.signatures, key=lambda x: 1 if args_kwargs(x) else 0)) + return sorted(self.signatures, key=lambda x: 1 if args_kwargs(x) else 0) def infer_sig_from_docstring(docstr: str | None, name: str) -> list[FunctionSig] | None: - """Convert function signature to list of TypedFunctionSig + """Convert function signature to list of FunctionSig Look for function signatures of function in docstring. Signature is a string of the format () -> or perhaps without @@ -254,7 +343,7 @@ def infer_sig_from_docstring(docstr: str | None, name: str) -> list[FunctionSig] * docstr: docstring * name: name of function for which signatures are to be found """ - if not docstr: + if not (isinstance(docstr, str) and docstr): return None state = DocStringParser(name) @@ -294,7 +383,8 @@ def infer_ret_type_sig_from_docstring(docstr: str, name: str) -> str | None: def infer_ret_type_sig_from_anon_docstring(docstr: str) -> str | None: """Convert signature in form of "(self: TestClass, arg0) -> int" to their return type.""" - return infer_ret_type_sig_from_docstring("stub" + docstr.strip(), "stub") + lines = ["stub" + line.strip() for line in docstr.splitlines() if line.strip().startswith("(")] + return infer_ret_type_sig_from_docstring("".join(lines), "stub") def parse_signature(sig: str) -> tuple[str, list[str], list[str]] | None: diff --git a/mypy/stubgen.py b/mypy/stubgen.py index 6cb4669887fe..7721366f5c0c 100755 --- a/mypy/stubgen.py +++ b/mypy/stubgen.py @@ -7,7 +7,7 @@ - or use mypy's mechanisms, if importing is prohibited * (optionally) semantically analysing the sources using mypy (as a single set) * emitting the stubs text: - - for Python modules: from ASTs using StubGenerator + - for Python modules: from ASTs using ASTStubGenerator - for C modules using runtime introspection and (optionally) Sphinx docs During first and third steps some problematic files can be skipped, but any @@ -22,7 +22,7 @@ => Generate out/urllib/parse.pyi. $ stubgen -p urllib - => Generate stubs for whole urlib package (recursively). + => Generate stubs for whole urllib package (recursively). For C modules, you can get more precise function signatures by parsing .rst (Sphinx) documentation for extra information. For this, use the --doc-dir option: @@ -42,14 +42,12 @@ from __future__ import annotations import argparse -import glob +import keyword import os import os.path import sys import traceback -from collections import defaultdict -from typing import Iterable, List, Mapping, cast -from typing_extensions import Final +from typing import Final, Iterable, Iterator import mypy.build import mypy.mixedtraverser @@ -66,22 +64,26 @@ SearchPaths, default_lib_path, ) -from mypy.moduleinspect import ModuleInspect +from mypy.moduleinspect import ModuleInspect, is_pyc_only from mypy.nodes import ( ARG_NAMED, ARG_POS, ARG_STAR, ARG_STAR2, IS_ABSTRACT, + NOT_ABSTRACT, AssignmentStmt, Block, BytesExpr, CallExpr, ClassDef, ComparisonExpr, + ComplexExpr, Decorator, + DictExpr, EllipsisExpr, Expression, + ExpressionStmt, FloatExpr, FuncBase, FuncDef, @@ -97,50 +99,56 @@ NameExpr, OpExpr, OverloadedFuncDef, + SetExpr, + StarExpr, Statement, StrExpr, + TempNode, TupleExpr, TypeInfo, UnaryExpr, + Var, ) from mypy.options import Options as MypyOptions -from mypy.stubdoc import Sig, find_unique_signatures, parse_all_signatures -from mypy.stubgenc import ( - DocstringSignatureGenerator, - ExternalSignatureGenerator, - FallbackSignatureGenerator, - SignatureGenerator, - generate_stub_for_c_module, -) +from mypy.sharedparse import MAGIC_METHODS_POS_ARGS_ONLY +from mypy.stubdoc import ArgSig, FunctionSig +from mypy.stubgenc import InspectionStubGenerator, generate_stub_for_c_module from mypy.stubutil import ( + TYPING_BUILTIN_REPLACEMENTS, + BaseStubGenerator, CantImport, + ClassInfo, + FunctionContext, common_dir_prefix, fail_missing, find_module_path_and_all_py3, generate_guarded, + infer_method_arg_types, + infer_method_ret_type, remove_misplaced_type_comments, report_missing, walk_packages, ) -from mypy.traverser import all_yield_expressions, has_return_statement, has_yield_expression +from mypy.traverser import ( + all_yield_expressions, + has_return_statement, + has_yield_expression, + has_yield_from_expression, +) from mypy.types import ( OVERLOAD_NAMES, + TPDICT_NAMES, + TYPED_NAMEDTUPLE_NAMES, AnyType, CallableType, Instance, - NoneType, TupleType, Type, - TypeList, - TypeStrVisitor, UnboundType, - UnionType, get_proper_type, ) from mypy.visitor import NodeVisitor -TYPING_MODULE_NAMES: Final = ("typing", "typing_extensions") - # Common ways of naming package containing vendored modules. VENDOR_PACKAGES: Final = ["packages", "vendor", "vendored", "_vendor", "_vendored_packages"] @@ -153,32 +161,6 @@ "/_vendored_packages/", ] -# Special-cased names that are implicitly exported from the stub (from m import y as y). -EXTRA_EXPORTED: Final = { - "pyasn1_modules.rfc2437.univ", - "pyasn1_modules.rfc2459.char", - "pyasn1_modules.rfc2459.univ", -} - -# These names should be omitted from generated stubs. -IGNORED_DUNDERS: Final = { - "__all__", - "__author__", - "__version__", - "__about__", - "__copyright__", - "__email__", - "__license__", - "__summary__", - "__title__", - "__uri__", - "__str__", - "__repr__", - "__getstate__", - "__setstate__", - "__slots__", -} - # These methods are expected to always return a non-trivial value. METHODS_WITH_RETURN_VALUE: Final = { "__ne__", @@ -191,22 +173,6 @@ "__iter__", } -# These magic methods always return the same type. -KNOWN_MAGIC_METHODS_RETURN_TYPES: Final = { - "__len__": "int", - "__length_hint__": "int", - "__init__": "None", - "__del__": "None", - "__bool__": "bool", - "__bytes__": "bytes", - "__format__": "str", - "__contains__": "bool", - "__complex__": "complex", - "__int__": "int", - "__float__": "float", - "__index__": "int", -} - class Options: """Represents stubgen options. @@ -218,6 +184,7 @@ def __init__( self, pyversion: tuple[int, int], no_import: bool, + inspect: bool, doc_dir: str, search_path: list[str], interpreter: str, @@ -231,10 +198,12 @@ def __init__( verbose: bool, quiet: bool, export_less: bool, + include_docstrings: bool, ) -> None: # See parse_options for descriptions of the flags. self.pyversion = pyversion self.no_import = no_import + self.inspect = inspect self.doc_dir = doc_dir self.search_path = search_path self.interpreter = interpreter @@ -249,6 +218,7 @@ def __init__( self.verbose = verbose self.quiet = quiet self.export_less = export_less + self.include_docstrings = include_docstrings class StubSource: @@ -265,6 +235,9 @@ def __init__( self.runtime_all = runtime_all self.ast: MypyFile | None = None + def __repr__(self) -> str: + return f"StubSource({self.source})" + @property def module(self) -> str: return self.source.module @@ -289,71 +262,13 @@ def path(self) -> str | None: ERROR_MARKER: Final = "" -class AnnotationPrinter(TypeStrVisitor): - """Visitor used to print existing annotations in a file. - - The main difference from TypeStrVisitor is a better treatment of - unbound types. - - Notes: - * This visitor doesn't add imports necessary for annotations, this is done separately - by ImportTracker. - * It can print all kinds of types, but the generated strings may not be valid (notably - callable types) since it prints the same string that reveal_type() does. - * For Instance types it prints the fully qualified names. - """ - - # TODO: Generate valid string representation for callable types. - # TODO: Use short names for Instances. - def __init__(self, stubgen: StubGenerator) -> None: - super().__init__() - self.stubgen = stubgen - - def visit_any(self, t: AnyType) -> str: - s = super().visit_any(t) - self.stubgen.import_tracker.require_name(s) - return s - - def visit_unbound_type(self, t: UnboundType) -> str: - s = t.name - self.stubgen.import_tracker.require_name(s) - if t.args: - s += f"[{self.args_str(t.args)}]" - return s - - def visit_none_type(self, t: NoneType) -> str: - return "None" - - def visit_type_list(self, t: TypeList) -> str: - return f"[{self.list_str(t.items)}]" - - def visit_union_type(self, t: UnionType) -> str: - return " | ".join([item.accept(self) for item in t.items]) - - def args_str(self, args: Iterable[Type]) -> str: - """Convert an array of arguments to strings and join the results with commas. - - The main difference from list_str is the preservation of quotes for string - arguments - """ - types = ["builtins.bytes", "builtins.str"] - res = [] - for arg in args: - arg_str = arg.accept(self) - if isinstance(arg, UnboundType) and arg.original_str_fallback in types: - res.append(f"'{arg_str}'") - else: - res.append(arg_str) - return ", ".join(res) - - class AliasPrinter(NodeVisitor[str]): """Visitor used to collect type aliases _and_ type variable definitions. Visit r.h.s of the definition to get the string representation of type alias. """ - def __init__(self, stubgen: StubGenerator) -> None: + def __init__(self, stubgen: ASTStubGenerator) -> None: self.stubgen = stubgen super().__init__() @@ -375,153 +290,59 @@ def visit_call_expr(self, node: CallExpr) -> str: raise ValueError(f"Unknown argument kind {kind} in call") return f"{callee}({', '.join(args)})" + def _visit_ref_expr(self, node: NameExpr | MemberExpr) -> str: + fullname = self.stubgen.get_fullname(node) + if fullname in TYPING_BUILTIN_REPLACEMENTS: + return self.stubgen.add_name(TYPING_BUILTIN_REPLACEMENTS[fullname], require=False) + qualname = get_qualified_name(node) + self.stubgen.import_tracker.require_name(qualname) + return qualname + def visit_name_expr(self, node: NameExpr) -> str: - self.stubgen.import_tracker.require_name(node.name) - return node.name + return self._visit_ref_expr(node) def visit_member_expr(self, o: MemberExpr) -> str: - node: Expression = o - trailer = "" - while isinstance(node, MemberExpr): - trailer = "." + node.name + trailer - node = node.expr - if not isinstance(node, NameExpr): - return ERROR_MARKER - self.stubgen.import_tracker.require_name(node.name) - return node.name + trailer + return self._visit_ref_expr(o) def visit_str_expr(self, node: StrExpr) -> str: return repr(node.value) def visit_index_expr(self, node: IndexExpr) -> str: + base_fullname = self.stubgen.get_fullname(node.base) + if base_fullname == "typing.Union": + if isinstance(node.index, TupleExpr): + return " | ".join([item.accept(self) for item in node.index.items]) + return node.index.accept(self) + if base_fullname == "typing.Optional": + return f"{node.index.accept(self)} | None" base = node.base.accept(self) index = node.index.accept(self) + if len(index) > 2 and index.startswith("(") and index.endswith(")"): + index = index[1:-1] return f"{base}[{index}]" def visit_tuple_expr(self, node: TupleExpr) -> str: - return ", ".join(n.accept(self) for n in node.items) + return f"({', '.join(n.accept(self) for n in node.items)})" def visit_list_expr(self, node: ListExpr) -> str: return f"[{', '.join(n.accept(self) for n in node.items)}]" + def visit_dict_expr(self, o: DictExpr) -> str: + dict_items = [] + for key, value in o.items: + # This is currently only used for TypedDict where all keys are strings. + assert isinstance(key, StrExpr) + dict_items.append(f"{key.accept(self)}: {value.accept(self)}") + return f"{{{', '.join(dict_items)}}}" + def visit_ellipsis(self, node: EllipsisExpr) -> str: return "..." def visit_op_expr(self, o: OpExpr) -> str: return f"{o.left.accept(self)} {o.op} {o.right.accept(self)}" - -class ImportTracker: - """Record necessary imports during stub generation.""" - - def __init__(self) -> None: - # module_for['foo'] has the module name where 'foo' was imported from, or None if - # 'foo' is a module imported directly; examples - # 'from pkg.m import f as foo' ==> module_for['foo'] == 'pkg.m' - # 'from m import f' ==> module_for['f'] == 'm' - # 'import m' ==> module_for['m'] == None - # 'import pkg.m' ==> module_for['pkg.m'] == None - # ==> module_for['pkg'] == None - self.module_for: dict[str, str | None] = {} - - # direct_imports['foo'] is the module path used when the name 'foo' was added to the - # namespace. - # import foo.bar.baz ==> direct_imports['foo'] == 'foo.bar.baz' - # ==> direct_imports['foo.bar'] == 'foo.bar.baz' - # ==> direct_imports['foo.bar.baz'] == 'foo.bar.baz' - self.direct_imports: dict[str, str] = {} - - # reverse_alias['foo'] is the name that 'foo' had originally when imported with an - # alias; examples - # 'import numpy as np' ==> reverse_alias['np'] == 'numpy' - # 'import foo.bar as bar' ==> reverse_alias['bar'] == 'foo.bar' - # 'from decimal import Decimal as D' ==> reverse_alias['D'] == 'Decimal' - self.reverse_alias: dict[str, str] = {} - - # required_names is the set of names that are actually used in a type annotation - self.required_names: set[str] = set() - - # Names that should be reexported if they come from another module - self.reexports: set[str] = set() - - def add_import_from(self, module: str, names: list[tuple[str, str | None]]) -> None: - for name, alias in names: - if alias: - # 'from {module} import {name} as {alias}' - self.module_for[alias] = module - self.reverse_alias[alias] = name - else: - # 'from {module} import {name}' - self.module_for[name] = module - self.reverse_alias.pop(name, None) - self.direct_imports.pop(alias or name, None) - - def add_import(self, module: str, alias: str | None = None) -> None: - if alias: - # 'import {module} as {alias}' - self.module_for[alias] = None - self.reverse_alias[alias] = module - else: - # 'import {module}' - name = module - # add module and its parent packages - while name: - self.module_for[name] = None - self.direct_imports[name] = module - self.reverse_alias.pop(name, None) - name = name.rpartition(".")[0] - - def require_name(self, name: str) -> None: - self.required_names.add(name.split(".")[0]) - - def reexport(self, name: str) -> None: - """Mark a given non qualified name as needed in __all__. - - This means that in case it comes from a module, it should be - imported with an alias even is the alias is the same as the name. - """ - self.require_name(name) - self.reexports.add(name) - - def import_lines(self) -> list[str]: - """The list of required import lines (as strings with python code).""" - result = [] - - # To summarize multiple names imported from a same module, we collect those - # in the `module_map` dictionary, mapping a module path to the list of names that should - # be imported from it. the names can also be alias in the form 'original as alias' - module_map: Mapping[str, list[str]] = defaultdict(list) - - for name in sorted(self.required_names): - # If we haven't seen this name in an import statement, ignore it - if name not in self.module_for: - continue - - m = self.module_for[name] - if m is not None: - # This name was found in a from ... import ... - # Collect the name in the module_map - if name in self.reverse_alias: - name = f"{self.reverse_alias[name]} as {name}" - elif name in self.reexports: - name = f"{name} as {name}" - module_map[m].append(name) - else: - # This name was found in an import ... - # We can already generate the import line - if name in self.reverse_alias: - source = self.reverse_alias[name] - result.append(f"import {source} as {name}\n") - elif name in self.reexports: - assert "." not in name # Because reexports only has nonqualified names - result.append(f"import {name} as {name}\n") - else: - result.append(f"import {self.direct_imports[name]}\n") - - # Now generate all the from ... import ... lines collected in module_map - for module, names in sorted(module_map.items()): - result.append(f"from {module} import {', '.join(sorted(names))}\n") - return result + def visit_star_expr(self, o: StarExpr) -> str: + return f"*{o.expr.accept(self)}" def find_defined_names(file: MypyFile) -> set[str]: @@ -530,11 +351,17 @@ def find_defined_names(file: MypyFile) -> set[str]: return finder.names +def get_assigned_names(lvalues: Iterable[Expression]) -> Iterator[str]: + for lvalue in lvalues: + if isinstance(lvalue, NameExpr): + yield lvalue.name + elif isinstance(lvalue, TupleExpr): + yield from get_assigned_names(lvalue.items) + + class DefinitionFinder(mypy.traverser.TraverserVisitor): """Find names of things defined at the top level of a module.""" - # TODO: Assignment statements etc. - def __init__(self) -> None: # Short names of things defined at the top level. self.names: set[str] = set() @@ -547,6 +374,10 @@ def visit_func_def(self, o: FuncDef) -> None: # Don't recurse, as we only keep track of top-level definitions. self.names.add(o.name) + def visit_assignment_stmt(self, o: AssignmentStmt) -> None: + for name in get_assigned_names(o.lvalues): + self.names.add(name) + def find_referenced_names(file: MypyFile) -> set[str]: finder = ReferenceFinder() @@ -554,6 +385,10 @@ def find_referenced_names(file: MypyFile) -> set[str]: return finder.refs +def is_none_expr(expr: Expression) -> bool: + return isinstance(expr, NameExpr) and expr.name == "None" + + class ReferenceFinder(mypy.mixedtraverser.MixedTraverserVisitor): """Find all name references (both local and global).""" @@ -571,7 +406,7 @@ def visit_name_expr(self, e: NameExpr) -> None: self.refs.add(e.name) def visit_instance(self, t: Instance) -> None: - self.add_ref(t.type.fullname) + self.add_ref(t.type.name) super().visit_instance(t) def visit_unbound_type(self, t: UnboundType) -> None: @@ -590,123 +425,75 @@ def visit_callable_type(self, t: CallableType) -> None: t.ret_type.accept(self) def add_ref(self, fullname: str) -> None: - self.refs.add(fullname.split(".")[-1]) + self.refs.add(fullname) + while "." in fullname: + fullname = fullname.rsplit(".", 1)[0] + self.refs.add(fullname) -class StubGenerator(mypy.traverser.TraverserVisitor): +class ASTStubGenerator(BaseStubGenerator, mypy.traverser.TraverserVisitor): """Generate stub text from a mypy AST.""" def __init__( self, - _all_: list[str] | None, + _all_: list[str] | None = None, include_private: bool = False, analyzed: bool = False, export_less: bool = False, + include_docstrings: bool = False, ) -> None: - # Best known value of __all__. - self._all_ = _all_ - self._output: list[str] = [] + super().__init__(_all_, include_private, export_less, include_docstrings) self._decorators: list[str] = [] - self._import_lines: list[str] = [] - # Current indent level (indent is hardcoded to 4 spaces). - self._indent = "" # Stack of defined variables (per scope). self._vars: list[list[str]] = [[]] # What was generated previously in the stub file. self._state = EMPTY - self._toplevel_names: list[str] = [] - self._include_private = include_private - self.import_tracker = ImportTracker() + self._current_class: ClassDef | None = None # Was the tree semantically analysed before? self.analyzed = analyzed - # Disable implicit exports of package-internal imports? - self.export_less = export_less - # Add imports that could be implicitly generated - self.import_tracker.add_import_from("typing", [("NamedTuple", None)]) - # Names in __all__ are required - for name in _all_ or (): - if name not in IGNORED_DUNDERS: - self.import_tracker.reexport(name) - self.defined_names: set[str] = set() # Short names of methods defined in the body of the current class self.method_names: set[str] = set() + self.processing_dataclass = False def visit_mypy_file(self, o: MypyFile) -> None: - self.module = o.fullname # Current module being processed + self.module_name = o.fullname # Current module being processed self.path = o.path - self.defined_names = find_defined_names(o) + self.set_defined_names(find_defined_names(o)) self.referenced_names = find_referenced_names(o) - known_imports = { - "_typeshed": ["Incomplete"], - "typing": ["Any", "TypeVar"], - "collections.abc": ["Generator"], - } - for pkg, imports in known_imports.items(): - for t in imports: - if t not in self.defined_names: - alias = None - else: - alias = "_" + t - self.import_tracker.add_import_from(pkg, [(t, alias)]) super().visit_mypy_file(o) - undefined_names = [name for name in self._all_ or [] if name not in self._toplevel_names] - if undefined_names: - if self._state != EMPTY: - self.add("\n") - self.add("# Names in __all__ with no definition:\n") - for name in sorted(undefined_names): - self.add(f"# {name}\n") + self.check_undefined_names() def visit_overloaded_func_def(self, o: OverloadedFuncDef) -> None: - """@property with setters and getters, or @overload chain""" + """@property with setters and getters, @overload chain and some others.""" overload_chain = False for item in o.items: if not isinstance(item, Decorator): continue - if self.is_private_name(item.func.name, item.func.fullname): continue - is_abstract, is_overload = self.process_decorator(item) - + self.process_decorator(item) if not overload_chain: - self.visit_func_def(item.func, is_abstract=is_abstract, is_overload=is_overload) - if is_overload: + self.visit_func_def(item.func) + if item.func.is_overload: overload_chain = True - elif is_overload: - self.visit_func_def(item.func, is_abstract=is_abstract, is_overload=is_overload) + elif item.func.is_overload: + self.visit_func_def(item.func) else: # skip the overload implementation and clear the decorator we just processed self.clear_decorators() - def visit_func_def( - self, o: FuncDef, is_abstract: bool = False, is_overload: bool = False - ) -> None: - if ( - self.is_private_name(o.name, o.fullname) - or self.is_not_in_all(o.name) - or (self.is_recorded_name(o.name) and not is_overload) - ): - self.clear_decorators() - return - if not self._indent and self._state not in (EMPTY, FUNC) and not o.is_awaitable_coroutine: - self.add("\n") - if not self.is_top_level(): - self_inits = find_self_initializers(o) - for init, value in self_inits: - if init in self.method_names: - # Can't have both an attribute and a method/property with the same name. - continue - init_code = self.get_init(init, value) - if init_code: - self.add(init_code) - # dump decorators, just before "def ..." - for s in self._decorators: - self.add(s) - self.clear_decorators() - self.add(f"{self._indent}{'async ' if o.is_coroutine else ''}def {o.name}(") - self.record_name(o.name) - args: list[str] = [] + def get_default_function_sig(self, func_def: FuncDef, ctx: FunctionContext) -> FunctionSig: + args = self._get_func_args(func_def, ctx) + retname = self._get_func_return(func_def, ctx) + return FunctionSig(func_def.name, args, retname) + + def _get_func_args(self, o: FuncDef, ctx: FunctionContext) -> list[ArgSig]: + args: list[ArgSig] = [] + + # Ignore pos-only status of magic methods whose args names are elided by mypy at parse + actually_pos_only_args = o.name not in MAGIC_METHODS_POS_ARGS_ONLY + pos_only_marker_position = 0 # Where to insert "/", if any for i, arg_ in enumerate(o.arguments): var = arg_.variable kind = arg_.kind @@ -720,222 +507,221 @@ def visit_func_def( # name their 0th argument other than self/cls is_self_arg = i == 0 and name == "self" is_cls_arg = i == 0 and name == "cls" - annotation = "" + typename: str | None = None if annotated_type and not is_self_arg and not is_cls_arg: # Luckily, an argument explicitly annotated with "Any" has # type "UnboundType" and will not match. if not isinstance(get_proper_type(annotated_type), AnyType): - annotation = f": {self.print_annotation(annotated_type)}" + typename = self.print_annotation(annotated_type) - if kind.is_named() and not any(arg.startswith("*") for arg in args): - args.append("*") + if actually_pos_only_args and arg_.pos_only: + pos_only_marker_position += 1 + if kind.is_named() and not any(arg.name.startswith("*") for arg in args): + args.append(ArgSig("*")) + + default = "..." if arg_.initializer: - if not annotation: + if not typename: typename = self.get_str_type_of_node(arg_.initializer, True, False) - if typename == "": - annotation = "=..." - else: - annotation = f": {typename} = ..." - else: - annotation += " = ..." - arg = name + annotation + potential_default, valid = self.get_str_default_of_node(arg_.initializer) + if valid and len(potential_default) <= 200: + default = potential_default elif kind == ARG_STAR: - arg = f"*{name}{annotation}" + name = f"*{name}" elif kind == ARG_STAR2: - arg = f"**{name}{annotation}" - else: - arg = name + annotation - args.append(arg) - retname = None + name = f"**{name}" + + args.append( + ArgSig(name, typename, default=bool(arg_.initializer), default_value=default) + ) + if pos_only_marker_position: + args.insert(pos_only_marker_position, ArgSig("/")) + + if ctx.class_info is not None and all( + arg.type is None and arg.default is False for arg in args + ): + new_args = infer_method_arg_types( + ctx.name, ctx.class_info.self_var, [arg.name for arg in args] + ) + if new_args is not None: + args = new_args + + return args + + def _get_func_return(self, o: FuncDef, ctx: FunctionContext) -> str | None: if o.name != "__init__" and isinstance(o.unanalyzed_type, CallableType): if isinstance(get_proper_type(o.unanalyzed_type.ret_type), AnyType): # Luckily, a return type explicitly annotated with "Any" has # type "UnboundType" and will enter the else branch. - retname = None # implicit Any + return None # implicit Any else: - retname = self.print_annotation(o.unanalyzed_type.ret_type) - elif o.abstract_status == IS_ABSTRACT or o.name in METHODS_WITH_RETURN_VALUE: + return self.print_annotation(o.unanalyzed_type.ret_type) + if o.abstract_status == IS_ABSTRACT or o.name in METHODS_WITH_RETURN_VALUE: # Always assume abstract methods return Any unless explicitly annotated. Also # some dunder methods should not have a None return type. - retname = None # implicit Any - elif o.name in KNOWN_MAGIC_METHODS_RETURN_TYPES: - retname = KNOWN_MAGIC_METHODS_RETURN_TYPES[o.name] - elif has_yield_expression(o): - self.add_abc_import("Generator") + return None # implicit Any + retname = infer_method_ret_type(o.name) + if retname is not None: + return retname + if has_yield_expression(o) or has_yield_from_expression(o): + generator_name = self.add_name("collections.abc.Generator") yield_name = "None" send_name = "None" return_name = "None" - for expr, in_assignment in all_yield_expressions(o): - if expr.expr is not None and not self.is_none_expr(expr.expr): - self.add_typing_import("Incomplete") - yield_name = "Incomplete" - if in_assignment: - self.add_typing_import("Incomplete") - send_name = "Incomplete" + if has_yield_from_expression(o): + yield_name = send_name = self.add_name("_typeshed.Incomplete") + else: + for expr, in_assignment in all_yield_expressions(o): + if expr.expr is not None and not is_none_expr(expr.expr): + yield_name = self.add_name("_typeshed.Incomplete") + if in_assignment: + send_name = self.add_name("_typeshed.Incomplete") if has_return_statement(o): - self.add_typing_import("Incomplete") - return_name = "Incomplete" - generator_name = self.typing_name("Generator") - retname = f"{generator_name}[{yield_name}, {send_name}, {return_name}]" - elif not has_return_statement(o) and not is_abstract: - retname = "None" - retfield = "" - if retname is not None: - retfield = " -> " + retname - - self.add(", ".join(args)) - self.add(f"){retfield}: ...\n") - self._state = FUNC - - def is_none_expr(self, expr: Expression) -> bool: - return isinstance(expr, NameExpr) and expr.name == "None" + return_name = self.add_name("_typeshed.Incomplete") + return f"{generator_name}[{yield_name}, {send_name}, {return_name}]" + if not has_return_statement(o) and o.abstract_status == NOT_ABSTRACT: + return "None" + return None + + def _get_func_docstring(self, node: FuncDef) -> str | None: + if not node.body.body: + return None + expr = node.body.body[0] + if isinstance(expr, ExpressionStmt) and isinstance(expr.expr, StrExpr): + return expr.expr.value + return None - def visit_decorator(self, o: Decorator) -> None: - if self.is_private_name(o.func.name, o.func.fullname): + def visit_func_def(self, o: FuncDef) -> None: + is_dataclass_generated = ( + self.analyzed and self.processing_dataclass and o.info.names[o.name].plugin_generated + ) + if is_dataclass_generated and o.name != "__init__": + # Skip methods generated by the @dataclass decorator (except for __init__) return + if ( + self.is_private_name(o.name, o.fullname) + or self.is_not_in_all(o.name) + or (self.is_recorded_name(o.name) and not o.is_overload) + ): + self.clear_decorators() + return + if self.is_top_level() and self._state not in (EMPTY, FUNC): + self.add("\n") + if not self.is_top_level(): + self_inits = find_self_initializers(o) + for init, value in self_inits: + if init in self.method_names: + # Can't have both an attribute and a method/property with the same name. + continue + init_code = self.get_init(init, value) + if init_code: + self.add(init_code) - is_abstract, _ = self.process_decorator(o) - self.visit_func_def(o.func, is_abstract=is_abstract) + if self._current_class is not None: + if len(o.arguments): + self_var = o.arguments[0].variable.name + else: + self_var = "self" + class_info = ClassInfo(self._current_class.name, self_var) + else: + class_info = None + + ctx = FunctionContext( + module_name=self.module_name, + name=o.name, + docstring=self._get_func_docstring(o), + is_abstract=o.abstract_status != NOT_ABSTRACT, + class_info=class_info, + ) - def process_decorator(self, o: Decorator) -> tuple[bool, bool]: - """Process a series of decorators. + self.record_name(o.name) - Only preserve certain special decorators such as @abstractmethod. + default_sig = self.get_default_function_sig(o, ctx) + sigs = self.get_signatures(default_sig, self.sig_generators, ctx) - Return a pair of booleans: - - True if any of the decorators makes a method abstract. - - True if any of the decorators is typing.overload. - """ - is_abstract = False - is_overload = False - for decorator in o.original_decorators: - if isinstance(decorator, NameExpr): - i_is_abstract, i_is_overload = self.process_name_expr_decorator(decorator, o) - is_abstract = is_abstract or i_is_abstract - is_overload = is_overload or i_is_overload - elif isinstance(decorator, MemberExpr): - i_is_abstract, i_is_overload = self.process_member_expr_decorator(decorator, o) - is_abstract = is_abstract or i_is_abstract - is_overload = is_overload or i_is_overload - return is_abstract, is_overload - - def process_name_expr_decorator(self, expr: NameExpr, context: Decorator) -> tuple[bool, bool]: - """Process a function decorator of form @foo. + for output in self.format_func_def( + sigs, is_coroutine=o.is_coroutine, decorators=self._decorators, docstring=ctx.docstring + ): + self.add(output + "\n") - Only preserve certain special decorators such as @abstractmethod. + self.clear_decorators() + self._state = FUNC - Return a pair of booleans: - - True if the decorator makes a method abstract. - - True if the decorator is typing.overload. - """ - is_abstract = False - is_overload = False - name = expr.name - if name in ("property", "staticmethod", "classmethod"): - self.add_decorator(name) - elif self.import_tracker.module_for.get(name) in ( - "asyncio", - "asyncio.coroutines", - "types", - ): - self.add_coroutine_decorator(context.func, name, name) - elif self.refers_to_fullname(name, "abc.abstractmethod"): - self.add_decorator(name) - self.import_tracker.require_name(name) - is_abstract = True - elif self.refers_to_fullname(name, "abc.abstractproperty"): - self.add_decorator("property") - self.add_decorator("abc.abstractmethod") - is_abstract = True - elif self.refers_to_fullname(name, OVERLOAD_NAMES): - self.add_decorator(name) - self.add_typing_import("overload") - is_overload = True - return is_abstract, is_overload - - def refers_to_fullname(self, name: str, fullname: str | tuple[str, ...]) -> bool: - if isinstance(fullname, tuple): - return any(self.refers_to_fullname(name, fname) for fname in fullname) - module, short = fullname.rsplit(".", 1) - return self.import_tracker.module_for.get(name) == module and ( - name == short or self.import_tracker.reverse_alias.get(name) == short - ) + def visit_decorator(self, o: Decorator) -> None: + if self.is_private_name(o.func.name, o.func.fullname): + return + self.process_decorator(o) + self.visit_func_def(o.func) - def process_member_expr_decorator( - self, expr: MemberExpr, context: Decorator - ) -> tuple[bool, bool]: - """Process a function decorator of form @foo.bar. + def process_decorator(self, o: Decorator) -> None: + """Process a series of decorators. Only preserve certain special decorators such as @abstractmethod. - - Return a pair of booleans: - - True if the decorator makes a method abstract. - - True if the decorator is typing.overload. """ - is_abstract = False - is_overload = False - if expr.name == "setter" and isinstance(expr.expr, NameExpr): - self.add_decorator(f"{expr.expr.name}.setter") - elif ( - isinstance(expr.expr, NameExpr) - and ( - expr.expr.name == "abc" - or self.import_tracker.reverse_alias.get(expr.expr.name) == "abc" - ) - and expr.name in ("abstractmethod", "abstractproperty") - ): - if expr.name == "abstractproperty": - self.import_tracker.require_name(expr.expr.name) - self.add_decorator("%s" % ("property")) - self.add_decorator("{}.{}".format(expr.expr.name, "abstractmethod")) - else: - self.import_tracker.require_name(expr.expr.name) - self.add_decorator(f"{expr.expr.name}.{expr.name}") - is_abstract = True - elif expr.name == "coroutine": - if ( - isinstance(expr.expr, MemberExpr) - and expr.expr.name == "coroutines" - and isinstance(expr.expr.expr, NameExpr) - and ( - expr.expr.expr.name == "asyncio" - or self.import_tracker.reverse_alias.get(expr.expr.expr.name) == "asyncio" - ) + o.func.is_overload = False + for decorator in o.original_decorators: + if not isinstance(decorator, (NameExpr, MemberExpr)): + continue + qualname = get_qualified_name(decorator) + fullname = self.get_fullname(decorator) + if fullname in ( + "builtins.property", + "builtins.staticmethod", + "builtins.classmethod", + "functools.cached_property", ): - self.add_coroutine_decorator( - context.func, - f"{expr.expr.expr.name}.coroutines.coroutine", - expr.expr.expr.name, - ) - elif isinstance(expr.expr, NameExpr) and ( - expr.expr.name in ("asyncio", "types") - or self.import_tracker.reverse_alias.get(expr.expr.name) - in ("asyncio", "asyncio.coroutines", "types") + self.add_decorator(qualname, require_name=True) + elif fullname in ( + "asyncio.coroutine", + "asyncio.coroutines.coroutine", + "types.coroutine", ): - self.add_coroutine_decorator( - context.func, expr.expr.name + ".coroutine", expr.expr.name - ) - elif ( - isinstance(expr.expr, NameExpr) - and ( - expr.expr.name in TYPING_MODULE_NAMES - or self.import_tracker.reverse_alias.get(expr.expr.name) in TYPING_MODULE_NAMES - ) - and expr.name == "overload" + o.func.is_awaitable_coroutine = True + self.add_decorator(qualname, require_name=True) + elif fullname == "abc.abstractmethod": + self.add_decorator(qualname, require_name=True) + o.func.abstract_status = IS_ABSTRACT + elif fullname in ( + "abc.abstractproperty", + "abc.abstractstaticmethod", + "abc.abstractclassmethod", + ): + abc_module = qualname.rpartition(".")[0] + if not abc_module: + self.import_tracker.add_import("abc") + builtin_decorator_replacement = fullname[len("abc.abstract") :] + self.add_decorator(builtin_decorator_replacement, require_name=False) + self.add_decorator(f"{abc_module or 'abc'}.abstractmethod", require_name=True) + o.func.abstract_status = IS_ABSTRACT + elif fullname in OVERLOAD_NAMES: + self.add_decorator(qualname, require_name=True) + o.func.is_overload = True + elif qualname.endswith((".setter", ".deleter")): + self.add_decorator(qualname, require_name=False) + + def get_fullname(self, expr: Expression) -> str: + """Return the expression's full name.""" + if ( + self.analyzed + and isinstance(expr, (NameExpr, MemberExpr)) + and expr.fullname + and not (isinstance(expr.node, Var) and expr.node.is_suppressed_import) ): - self.import_tracker.require_name(expr.expr.name) - self.add_decorator(f"{expr.expr.name}.overload") - is_overload = True - return is_abstract, is_overload + return expr.fullname + name = get_qualified_name(expr) + return self.resolve_name(name) def visit_class_def(self, o: ClassDef) -> None: + self._current_class = o self.method_names = find_method_names(o.defs.body) sep: int | None = None - if not self._indent and self._state != EMPTY: + if self.is_top_level() and self._state != EMPTY: sep = len(self._output) self.add("\n") - self.add(f"{self._indent}class {o.name}") + decorators = self.get_class_decorators(o) + for d in decorators: + self.add(f"{self._indent}@{d}\n") self.record_name(o.name) base_types = self.get_base_types(o) if base_types: @@ -944,50 +730,92 @@ def visit_class_def(self, o: ClassDef) -> None: if isinstance(o.metaclass, (NameExpr, MemberExpr)): meta = o.metaclass.accept(AliasPrinter(self)) base_types.append("metaclass=" + meta) - elif self.analyzed and o.info.is_protocol: - type_str = "Protocol" - if o.info.type_vars: - type_str += f'[{", ".join(o.info.type_vars)}]' - base_types.append(type_str) - self.add_typing_import("Protocol") - elif self.analyzed and o.info.is_abstract: + elif self.analyzed and o.info.is_abstract and not o.info.is_protocol: base_types.append("metaclass=abc.ABCMeta") self.import_tracker.add_import("abc") self.import_tracker.require_name("abc") - if base_types: - self.add(f"({', '.join(base_types)})") - self.add(":\n") + bases = f"({', '.join(base_types)})" if base_types else "" + self.add(f"{self._indent}class {o.name}{bases}:\n") + self.indent() + if self._include_docstrings and o.docstring: + docstring = mypy.util.quote_docstring(o.docstring) + self.add(f"{self._indent}{docstring}\n") n = len(self._output) - self._indent += " " self._vars.append([]) super().visit_class_def(o) - self._indent = self._indent[:-4] + self.dedent() self._vars.pop() self._vars[-1].append(o.name) if len(self._output) == n: if self._state == EMPTY_CLASS and sep is not None: self._output[sep] = "" - self._output[-1] = self._output[-1][:-1] + " ...\n" + if not (self._include_docstrings and o.docstring): + self._output[-1] = self._output[-1][:-1] + " ...\n" self._state = EMPTY_CLASS else: self._state = CLASS self.method_names = set() + self.processing_dataclass = False + self._current_class = None def get_base_types(self, cdef: ClassDef) -> list[str]: """Get list of base classes for a class.""" base_types: list[str] = [] - for base in cdef.base_type_exprs: - if isinstance(base, NameExpr): - if base.name != "object": - base_types.append(base.name) - elif isinstance(base, MemberExpr): - modname = get_qualified_name(base.expr) - base_types.append(f"{modname}.{base.name}") + p = AliasPrinter(self) + for base in cdef.base_type_exprs + cdef.removed_base_type_exprs: + if isinstance(base, (NameExpr, MemberExpr)): + if self.get_fullname(base) != "builtins.object": + base_types.append(get_qualified_name(base)) elif isinstance(base, IndexExpr): - p = AliasPrinter(self) base_types.append(base.accept(p)) + elif isinstance(base, CallExpr): + # namedtuple(typename, fields), NamedTuple(typename, fields) calls can + # be used as a base class. The first argument is a string literal that + # is usually the same as the class name. + # + # Note: + # A call-based named tuple as a base class cannot be safely converted to + # a class-based NamedTuple definition because class attributes defined + # in the body of the class inheriting from the named tuple call are not + # namedtuple fields at runtime. + if self.is_namedtuple(base): + nt_fields = self._get_namedtuple_fields(base) + assert isinstance(base.args[0], StrExpr) + typename = base.args[0].value + if nt_fields is None: + # Invalid namedtuple() call, cannot determine fields + base_types.append(self.add_name("_typeshed.Incomplete")) + continue + fields_str = ", ".join(f"({f!r}, {t})" for f, t in nt_fields) + namedtuple_name = self.add_name("typing.NamedTuple") + base_types.append(f"{namedtuple_name}({typename!r}, [{fields_str}])") + elif self.is_typed_namedtuple(base): + base_types.append(base.accept(p)) + else: + # At this point, we don't know what the base class is, so we + # just use Incomplete as the base class. + base_types.append(self.add_name("_typeshed.Incomplete")) + for name, value in cdef.keywords.items(): + if name == "metaclass": + continue # handled separately + base_types.append(f"{name}={value.accept(p)}") return base_types + def get_class_decorators(self, cdef: ClassDef) -> list[str]: + decorators: list[str] = [] + p = AliasPrinter(self) + for d in cdef.decorators: + if self.is_dataclass(d): + decorators.append(d.accept(p)) + self.import_tracker.require_name(get_qualified_name(d)) + self.processing_dataclass = True + return decorators + + def is_dataclass(self, expr: Expression) -> bool: + if isinstance(expr, CallExpr): + expr = expr.callee + return self.get_fullname(expr) == "dataclasses.dataclass" + def visit_block(self, o: Block) -> None: # Unreachable statements may be partially uninitialized and that may # cause trouble. @@ -998,21 +826,25 @@ def visit_assignment_stmt(self, o: AssignmentStmt) -> None: foundl = [] for lvalue in o.lvalues: - if isinstance(lvalue, NameExpr) and self.is_namedtuple(o.rvalue): - assert isinstance(o.rvalue, CallExpr) - self.process_namedtuple(lvalue, o.rvalue) - continue + if isinstance(lvalue, NameExpr) and isinstance(o.rvalue, CallExpr): + if self.is_namedtuple(o.rvalue) or self.is_typed_namedtuple(o.rvalue): + self.process_namedtuple(lvalue, o.rvalue) + foundl.append(False) # state is updated in process_namedtuple + continue + if self.is_typeddict(o.rvalue): + self.process_typeddict(lvalue, o.rvalue) + foundl.append(False) # state is updated in process_typeddict + continue if ( isinstance(lvalue, NameExpr) and not self.is_private_name(lvalue.name) - and # it is never an alias with explicit annotation - not o.unanalyzed_type + and not o.unanalyzed_type and self.is_alias_expression(o.rvalue) ): self.process_typealias(lvalue, o.rvalue) continue - if isinstance(lvalue, TupleExpr) or isinstance(lvalue, ListExpr): + if isinstance(lvalue, (TupleExpr, ListExpr)): items = lvalue.items if isinstance(o.unanalyzed_type, TupleType): # type: ignore[misc] annotations: Iterable[Type | None] = o.unanalyzed_type.items @@ -1028,7 +860,7 @@ def visit_assignment_stmt(self, o: AssignmentStmt) -> None: init = self.get_init(item.name, o.rvalue, annotation) if init: found = True - if not sep and not self._indent and self._state not in (EMPTY, VAR): + if not sep and self.is_top_level() and self._state not in (EMPTY, VAR): init = "\n" + init sep = True self.add(init) @@ -1038,36 +870,136 @@ def visit_assignment_stmt(self, o: AssignmentStmt) -> None: if all(foundl): self._state = VAR - def is_namedtuple(self, expr: Expression) -> bool: - if not isinstance(expr, CallExpr): - return False - callee = expr.callee - return (isinstance(callee, NameExpr) and callee.name.endswith("namedtuple")) or ( - isinstance(callee, MemberExpr) and callee.name == "namedtuple" - ) + def is_namedtuple(self, expr: CallExpr) -> bool: + return self.get_fullname(expr.callee) == "collections.namedtuple" + + def is_typed_namedtuple(self, expr: CallExpr) -> bool: + return self.get_fullname(expr.callee) in TYPED_NAMEDTUPLE_NAMES + + def _get_namedtuple_fields(self, call: CallExpr) -> list[tuple[str, str]] | None: + if self.is_namedtuple(call): + fields_arg = call.args[1] + if isinstance(fields_arg, StrExpr): + field_names = fields_arg.value.replace(",", " ").split() + elif isinstance(fields_arg, (ListExpr, TupleExpr)): + field_names = [] + for field in fields_arg.items: + if not isinstance(field, StrExpr): + return None + field_names.append(field.value) + else: + return None # Invalid namedtuple fields type + if field_names: + incomplete = self.add_name("_typeshed.Incomplete") + return [(field_name, incomplete) for field_name in field_names] + else: + return [] + + elif self.is_typed_namedtuple(call): + fields_arg = call.args[1] + if not isinstance(fields_arg, (ListExpr, TupleExpr)): + return None + fields: list[tuple[str, str]] = [] + p = AliasPrinter(self) + for field in fields_arg.items: + if not (isinstance(field, TupleExpr) and len(field.items) == 2): + return None + field_name, field_type = field.items + if not isinstance(field_name, StrExpr): + return None + fields.append((field_name.value, field_type.accept(p))) + return fields + else: + return None # Not a named tuple call def process_namedtuple(self, lvalue: NameExpr, rvalue: CallExpr) -> None: - if self._state != EMPTY: + if self._state == CLASS: self.add("\n") - if isinstance(rvalue.args[1], StrExpr): - items = rvalue.args[1].value.replace(",", " ").split() - elif isinstance(rvalue.args[1], (ListExpr, TupleExpr)): - list_items = cast(List[StrExpr], rvalue.args[1].items) - items = [item.value for item in list_items] - else: - self.add(f"{self._indent}{lvalue.name}: Incomplete") - self.import_tracker.require_name("Incomplete") + + if not isinstance(rvalue.args[0], StrExpr): + self.annotate_as_incomplete(lvalue) + return + + fields = self._get_namedtuple_fields(rvalue) + if fields is None: + self.annotate_as_incomplete(lvalue) return - self.import_tracker.require_name("NamedTuple") - self.add(f"{self._indent}class {lvalue.name}(NamedTuple):") - if len(items) == 0: - self.add(" ...\n") + bases = self.add_name("typing.NamedTuple") + # TODO: Add support for generic NamedTuples. Requires `Generic` as base class. + class_def = f"{self._indent}class {lvalue.name}({bases}):" + if len(fields) == 0: + self.add(f"{class_def} ...\n") + self._state = EMPTY_CLASS else: - self.import_tracker.require_name("Incomplete") + if self._state not in (EMPTY, CLASS): + self.add("\n") + self.add(f"{class_def}\n") + for f_name, f_type in fields: + self.add(f"{self._indent} {f_name}: {f_type}\n") + self._state = CLASS + + def is_typeddict(self, expr: CallExpr) -> bool: + return self.get_fullname(expr.callee) in TPDICT_NAMES + + def process_typeddict(self, lvalue: NameExpr, rvalue: CallExpr) -> None: + if self._state == CLASS: self.add("\n") - for item in items: - self.add(f"{self._indent} {item}: Incomplete\n") - self._state = CLASS + + if not isinstance(rvalue.args[0], StrExpr): + self.annotate_as_incomplete(lvalue) + return + + items: list[tuple[str, Expression]] = [] + total: Expression | None = None + if len(rvalue.args) > 1 and rvalue.arg_kinds[1] == ARG_POS: + if not isinstance(rvalue.args[1], DictExpr): + self.annotate_as_incomplete(lvalue) + return + for attr_name, attr_type in rvalue.args[1].items: + if not isinstance(attr_name, StrExpr): + self.annotate_as_incomplete(lvalue) + return + items.append((attr_name.value, attr_type)) + if len(rvalue.args) > 2: + if rvalue.arg_kinds[2] != ARG_NAMED or rvalue.arg_names[2] != "total": + self.annotate_as_incomplete(lvalue) + return + total = rvalue.args[2] + else: + for arg_name, arg in zip(rvalue.arg_names[1:], rvalue.args[1:]): + if not isinstance(arg_name, str): + self.annotate_as_incomplete(lvalue) + return + if arg_name == "total": + total = arg + else: + items.append((arg_name, arg)) + p = AliasPrinter(self) + if any(not key.isidentifier() or keyword.iskeyword(key) for key, _ in items): + # Keep the call syntax if there are non-identifier or reserved keyword keys. + self.add(f"{self._indent}{lvalue.name} = {rvalue.accept(p)}\n") + self._state = VAR + else: + bases = self.add_name("typing_extensions.TypedDict") + # TODO: Add support for generic TypedDicts. Requires `Generic` as base class. + if total is not None: + bases += f", total={total.accept(p)}" + class_def = f"{self._indent}class {lvalue.name}({bases}):" + if len(items) == 0: + self.add(f"{class_def} ...\n") + self._state = EMPTY_CLASS + else: + if self._state not in (EMPTY, CLASS): + self.add("\n") + self.add(f"{class_def}\n") + for key, key_type in items: + self.add(f"{self._indent} {key}: {key_type.accept(p)}\n") + self._state = CLASS + + def annotate_as_incomplete(self, lvalue: NameExpr) -> None: + incomplete = self.add_name("_typeshed.Incomplete") + self.add(f"{self._indent}{lvalue.name}: {incomplete}\n") + self._state = VAR def is_alias_expression(self, expr: Expression, top_level: bool = True) -> bool: """Return True for things that look like target for an alias. @@ -1075,11 +1007,14 @@ def is_alias_expression(self, expr: Expression, top_level: bool = True) -> bool: Used to know if assignments look like type aliases, function alias, or module alias. """ - # Assignment of TypeVar(...) are passed through - if ( - isinstance(expr, CallExpr) - and isinstance(expr.callee, NameExpr) - and expr.callee.name == "TypeVar" + # Assignment of TypeVar(...) and other typevar-likes are passed through + if isinstance(expr, CallExpr) and self.get_fullname(expr.callee) in ( + "typing.TypeVar", + "typing_extensions.TypeVar", + "typing.ParamSpec", + "typing_extensions.ParamSpec", + "typing.TypeVarTuple", + "typing_extensions.TypeVarTuple", ): return True elif isinstance(expr, EllipsisExpr): @@ -1098,10 +1033,15 @@ def is_alias_expression(self, expr: Expression, top_level: bool = True) -> bool: and isinstance(expr.node, (FuncDef, Decorator, MypyFile)) or isinstance(expr.node, TypeInfo) ) and not self.is_private_member(expr.node.fullname) - elif ( - isinstance(expr, IndexExpr) - and isinstance(expr.base, NameExpr) - and not self.is_private_name(expr.base.name) + elif isinstance(expr, IndexExpr) and ( + (isinstance(expr.base, NameExpr) and not self.is_private_name(expr.base.name)) + or ( # Also some known aliases that could be member expression + isinstance(expr.base, MemberExpr) + and not self.is_private_member(get_qualified_name(expr.base)) + and self.get_fullname(expr.base).startswith( + ("builtins.", "typing.", "typing_extensions.", "collections.abc.") + ) + ) ): if isinstance(expr.index, TupleExpr): indices = expr.index.items @@ -1145,9 +1085,9 @@ def visit_import_from(self, o: ImportFrom) -> None: exported_names: set[str] = set() import_names = [] module, relative = translate_module_name(o.id, o.relative) - if self.module: + if self.module_name: full_module, ok = mypy.util.correct_relative_import( - self.module, relative, module, self.path.endswith(".__init__.py") + self.module_name, relative, module, self.path.endswith(".__init__.py") ) if not ok: full_module = module @@ -1160,35 +1100,7 @@ def visit_import_from(self, o: ImportFrom) -> None: # Vendored six -- translate into plain 'import six'. self.visit_import(Import([("six", None)])) continue - exported = False - if as_name is None and self.module and (self.module + "." + name) in EXTRA_EXPORTED: - # Special case certain names that should be exported, against our general rules. - exported = True - is_private = self.is_private_name(name, full_module + "." + name) - if ( - as_name is None - and name not in self.referenced_names - and (not self._all_ or name in IGNORED_DUNDERS) - and not is_private - and module not in ("abc", "asyncio") + TYPING_MODULE_NAMES - ): - # An imported name that is never referenced in the module is assumed to be - # exported, unless there is an explicit __all__. Note that we need to special - # case 'abc' since some references are deleted during semantic analysis. - exported = True - top_level = full_module.split(".")[0] - if ( - as_name is None - and not self.export_less - and (not self._all_ or name in IGNORED_DUNDERS) - and self.module - and not is_private - and top_level in (self.module.split(".")[0], "_" + self.module.split(".")[0]) - ): - # Export imports from the same package, since we can't reliably tell whether they - # are part of the public API. - exported = True - if exported: + if self.should_reexport(name, full_module, as_name is not None): self.import_tracker.reexport(name) as_name = name import_names.append((name, as_name)) @@ -1202,7 +1114,7 @@ def visit_import_from(self, o: ImportFrom) -> None: names = [ name for name, alias in o.names - if name in self._all_ and alias is None and name not in IGNORED_DUNDERS + if name in self._all_ and alias is None and name not in self.IGNORED_DUNDERS ] exported_names.update(names) @@ -1236,84 +1148,47 @@ def get_init( isinstance(annotation, UnboundType) and not annotation.args and annotation.name == "Final" - and self.import_tracker.module_for.get("Final") in TYPING_MODULE_NAMES + and self.import_tracker.module_for.get("Final") in self.TYPING_MODULE_NAMES ): # Final without type argument is invalid in stubs. final_arg = self.get_str_type_of_node(rvalue) typename += f"[{final_arg}]" + elif self.processing_dataclass: + # attribute without annotation is not a dataclass field, don't add annotation. + return f"{self._indent}{lvalue} = ...\n" else: typename = self.get_str_type_of_node(rvalue) - return f"{self._indent}{lvalue}: {typename}\n" + initializer = self.get_assign_initializer(rvalue) + return f"{self._indent}{lvalue}: {typename}{initializer}\n" + + def get_assign_initializer(self, rvalue: Expression) -> str: + """Does this rvalue need some special initializer value?""" + if not self._current_class: + return "" + # Current rules + # 1. Return `...` if we are dealing with `NamedTuple` or `dataclass` field and + # it has an existing default value + if ( + self._current_class.info + and self._current_class.info.is_named_tuple + and not isinstance(rvalue, TempNode) + ): + return " = ..." + if self.processing_dataclass and not (isinstance(rvalue, TempNode) and rvalue.no_rhs): + return " = ..." + # TODO: support other possible cases, where initializer is important - def add(self, string: str) -> None: - """Add text to generated stub.""" - self._output.append(string) + # By default, no initializer is required: + return "" - def add_decorator(self, name: str) -> None: - if not self._indent and self._state not in (EMPTY, FUNC): - self._decorators.append("\n") - self._decorators.append(f"{self._indent}@{name}\n") + def add_decorator(self, name: str, require_name: bool = False) -> None: + if require_name: + self.import_tracker.require_name(name) + self._decorators.append(f"@{name}") def clear_decorators(self) -> None: self._decorators.clear() - def typing_name(self, name: str) -> str: - if name in self.defined_names: - # Avoid name clash between name from typing and a name defined in stub. - return "_" + name - else: - return name - - def add_typing_import(self, name: str) -> None: - """Add a name to be imported from typing, unless it's imported already. - - The import will be internal to the stub. - """ - name = self.typing_name(name) - self.import_tracker.require_name(name) - - def add_abc_import(self, name: str) -> None: - """Add a name to be imported from collections.abc, unless it's imported already. - - The import will be internal to the stub. - """ - name = self.typing_name(name) - self.import_tracker.require_name(name) - - def add_import_line(self, line: str) -> None: - """Add a line of text to the import section, unless it's already there.""" - if line not in self._import_lines: - self._import_lines.append(line) - - def add_coroutine_decorator(self, func: FuncDef, name: str, require_name: str) -> None: - func.is_awaitable_coroutine = True - self.add_decorator(name) - self.import_tracker.require_name(require_name) - - def output(self) -> str: - """Return the text for the stub.""" - imports = "" - if self._import_lines: - imports += "".join(self._import_lines) - imports += "".join(self.import_tracker.import_lines()) - if imports and self._output: - imports += "\n" - return imports + "".join(self._output) - - def is_not_in_all(self, name: str) -> bool: - if self.is_private_name(name): - return False - if self._all_: - return self.is_top_level() and name not in self._all_ - return False - - def is_private_name(self, name: str, fullname: str | None = None) -> bool: - if self._include_private: - return False - if fullname in EXTRA_EXPORTED: - return False - return name.startswith("_") and (not name.endswith("__") or name in IGNORED_DUNDERS) - def is_private_member(self, fullname: str) -> bool: parts = fullname.split(".") return any(self.is_private_name(part) for part in parts) @@ -1321,6 +1196,8 @@ def is_private_member(self, fullname: str) -> bool: def get_str_type_of_node( self, rvalue: Expression, can_infer_optional: bool = False, can_be_any: bool = True ) -> str: + rvalue = self.maybe_unwrap_unary_expr(rvalue) + if isinstance(rvalue, IntExpr): return "int" if isinstance(rvalue, StrExpr): @@ -1329,38 +1206,134 @@ def get_str_type_of_node( return "bytes" if isinstance(rvalue, FloatExpr): return "float" - if isinstance(rvalue, UnaryExpr) and isinstance(rvalue.expr, IntExpr): - return "int" + if isinstance(rvalue, ComplexExpr): # 1j + return "complex" + if isinstance(rvalue, OpExpr) and rvalue.op in ("-", "+"): # -1j + 1 + if isinstance(self.maybe_unwrap_unary_expr(rvalue.left), ComplexExpr) or isinstance( + self.maybe_unwrap_unary_expr(rvalue.right), ComplexExpr + ): + return "complex" if isinstance(rvalue, NameExpr) and rvalue.name in ("True", "False"): return "bool" if can_infer_optional and isinstance(rvalue, NameExpr) and rvalue.name == "None": - self.add_typing_import("Incomplete") - return f"{self.typing_name('Incomplete')} | None" + return f"{self.add_name('_typeshed.Incomplete')} | None" if can_be_any: - self.add_typing_import("Incomplete") - return self.typing_name("Incomplete") + return self.add_name("_typeshed.Incomplete") else: return "" - def print_annotation(self, t: Type) -> str: - printer = AnnotationPrinter(self) - return t.accept(printer) + def maybe_unwrap_unary_expr(self, expr: Expression) -> Expression: + """Unwrap (possibly nested) unary expressions. - def is_top_level(self) -> bool: - """Are we processing the top level of a file?""" - return self._indent == "" - - def record_name(self, name: str) -> None: - """Mark a name as defined. - - This only does anything if at the top level of a module. + But, some unary expressions can change the type of expression. + While we want to preserve it. For example, `~True` is `int`. + So, we only allow a subset of unary expressions to be unwrapped. """ - if self.is_top_level(): - self._toplevel_names.append(name) + if not isinstance(expr, UnaryExpr): + return expr + + # First, try to unwrap `[+-]+ (int|float|complex)` expr: + math_ops = ("+", "-") + if expr.op in math_ops: + while isinstance(expr, UnaryExpr): + if expr.op not in math_ops or not isinstance( + expr.expr, (IntExpr, FloatExpr, ComplexExpr, UnaryExpr) + ): + break + expr = expr.expr + return expr + + # Next, try `not bool` expr: + if expr.op == "not": + while isinstance(expr, UnaryExpr): + if expr.op != "not" or not isinstance(expr.expr, (NameExpr, UnaryExpr)): + break + if isinstance(expr.expr, NameExpr) and expr.expr.name not in ("True", "False"): + break + expr = expr.expr + return expr + + # This is some other unary expr, we cannot do anything with it (yet?). + return expr + + def get_str_default_of_node(self, rvalue: Expression) -> tuple[str, bool]: + """Get a string representation of the default value of a node. + + Returns a 2-tuple of the default and whether or not it is valid. + """ + if isinstance(rvalue, NameExpr): + if rvalue.name in ("None", "True", "False"): + return rvalue.name, True + elif isinstance(rvalue, (IntExpr, FloatExpr)): + return f"{rvalue.value}", True + elif isinstance(rvalue, UnaryExpr): + if isinstance(rvalue.expr, (IntExpr, FloatExpr)): + return f"{rvalue.op}{rvalue.expr.value}", True + elif isinstance(rvalue, StrExpr): + return repr(rvalue.value), True + elif isinstance(rvalue, BytesExpr): + return "b" + repr(rvalue.value).replace("\\\\", "\\"), True + elif isinstance(rvalue, TupleExpr): + items_defaults = [] + for e in rvalue.items: + e_default, valid = self.get_str_default_of_node(e) + if not valid: + break + items_defaults.append(e_default) + else: + closing = ",)" if len(items_defaults) == 1 else ")" + default = "(" + ", ".join(items_defaults) + closing + return default, True + elif isinstance(rvalue, ListExpr): + items_defaults = [] + for e in rvalue.items: + e_default, valid = self.get_str_default_of_node(e) + if not valid: + break + items_defaults.append(e_default) + else: + default = "[" + ", ".join(items_defaults) + "]" + return default, True + elif isinstance(rvalue, SetExpr): + items_defaults = [] + for e in rvalue.items: + e_default, valid = self.get_str_default_of_node(e) + if not valid: + break + items_defaults.append(e_default) + else: + if items_defaults: + default = "{" + ", ".join(items_defaults) + "}" + return default, True + elif isinstance(rvalue, DictExpr): + items_defaults = [] + for k, v in rvalue.items: + if k is None: + break + k_default, k_valid = self.get_str_default_of_node(k) + v_default, v_valid = self.get_str_default_of_node(v) + if not (k_valid and v_valid): + break + items_defaults.append(f"{k_default}: {v_default}") + else: + default = "{" + ", ".join(items_defaults) + "}" + return default, True + return "...", False - def is_recorded_name(self, name: str) -> bool: - """Has this name been recorded previously?""" - return self.is_top_level() and name in self._toplevel_names + def should_reexport(self, name: str, full_module: str, name_is_alias: bool) -> bool: + is_private = self.is_private_name(name, full_module + "." + name) + if ( + not name_is_alias + and name not in self.referenced_names + and (not self._all_ or name in self.IGNORED_DUNDERS) + and not is_private + and full_module not in ("abc", "asyncio") + self.TYPING_MODULE_NAMES + ): + # An imported name that is never referenced in the module is assumed to be + # exported, unless there is an explicit __all__. Note that we need to special + # case 'abc' since some references are deleted during semantic analysis. + return True + return super().should_reexport(name, full_module, name_is_alias) def find_method_names(defs: list[Statement]) -> set[str]: @@ -1416,6 +1389,17 @@ def remove_blacklisted_modules(modules: list[StubSource]) -> list[StubSource]: ] +def split_pyc_from_py(modules: list[StubSource]) -> tuple[list[StubSource], list[StubSource]]: + py_modules = [] + pyc_modules = [] + for mod in modules: + if is_pyc_only(mod.path): + pyc_modules.append(mod) + else: + py_modules.append(mod) + return pyc_modules, py_modules + + def is_blacklisted_path(path: str) -> bool: return any(substr in (normalize_path_separators(path) + "\n") for substr in BLACKLIST) @@ -1428,10 +1412,10 @@ def normalize_path_separators(path: str) -> str: def collect_build_targets( options: Options, mypy_opts: MypyOptions -) -> tuple[list[StubSource], list[StubSource]]: +) -> tuple[list[StubSource], list[StubSource], list[StubSource]]: """Collect files for which we need to generate stubs. - Return list of Python modules and C modules. + Return list of py modules, pyc modules, and C modules. """ if options.packages or options.modules: if options.no_import: @@ -1454,8 +1438,8 @@ def collect_build_targets( c_modules = [] py_modules = remove_blacklisted_modules(py_modules) - - return py_modules, c_modules + pyc_mod, py_mod = split_pyc_from_py(py_modules) + return py_mod, pyc_mod, c_modules def find_module_paths_using_imports( @@ -1575,6 +1559,15 @@ def mypy_options(stubgen_options: Options) -> MypyOptions: options.python_version = stubgen_options.pyversion options.show_traceback = True options.transform_source = remove_misplaced_type_comments + options.preserve_asts = True + options.include_docstrings = stubgen_options.include_docstrings + + # Override cache_dir if provided in the environment + environ_cache_dir = os.getenv("MYPY_CACHE_DIR", "") + if environ_cache_dir.strip(): + options.cache_dir = environ_cache_dir + options.cache_dir = os.path.expanduser(options.cache_dir) + return options @@ -1588,7 +1581,7 @@ def parse_source_file(mod: StubSource, mypy_options: MypyOptions) -> None: with open(mod.path, "rb") as f: data = f.read() source = mypy.util.decode_python_encoding(data) - errors = Errors() + errors = Errors(mypy_options) mod.ast = mypy.parse.parse( source, fnam=mod.path, module=mod.module, errors=errors, options=mypy_options ) @@ -1625,98 +1618,106 @@ def generate_asts_for_modules( mod.runtime_all = res.manager.semantic_analyzer.export_map[mod.module] -def generate_stub_from_ast( +def generate_stub_for_py_module( mod: StubSource, target: str, + *, parse_only: bool = False, + inspect: bool = False, include_private: bool = False, export_less: bool = False, + include_docstrings: bool = False, + doc_dir: str = "", + all_modules: list[str], ) -> None: """Use analysed (or just parsed) AST to generate type stub for single file. If directory for target doesn't exist it will created. Existing stub will be overwritten. """ - gen = StubGenerator( - mod.runtime_all, - include_private=include_private, - analyzed=not parse_only, - export_less=export_less, - ) - assert mod.ast is not None, "This function must be used only with analyzed modules" - mod.ast.accept(gen) + if inspect: + ngen = InspectionStubGenerator( + module_name=mod.module, + known_modules=all_modules, + _all_=mod.runtime_all, + doc_dir=doc_dir, + include_private=include_private, + export_less=export_less, + include_docstrings=include_docstrings, + ) + ngen.generate_module() + output = ngen.output() + + else: + gen = ASTStubGenerator( + mod.runtime_all, + include_private=include_private, + analyzed=not parse_only, + export_less=export_less, + include_docstrings=include_docstrings, + ) + assert mod.ast is not None, "This function must be used only with analyzed modules" + mod.ast.accept(gen) + output = gen.output() # Write output to file. subdir = os.path.dirname(target) if subdir and not os.path.isdir(subdir): os.makedirs(subdir) - with open(target, "w") as file: - file.write("".join(gen.output())) - - -def get_sig_generators(options: Options) -> List[SignatureGenerator]: - sig_generators: List[SignatureGenerator] = [ - DocstringSignatureGenerator(), - FallbackSignatureGenerator(), - ] - if options.doc_dir: - # Collect info from docs (if given). Always check these first. - sigs, class_sigs = collect_docs_signatures(options.doc_dir) - sig_generators.insert(0, ExternalSignatureGenerator(sigs, class_sigs)) - return sig_generators - - -def collect_docs_signatures(doc_dir: str) -> tuple[dict[str, str], dict[str, str]]: - """Gather all function and class signatures in the docs. - - Return a tuple (function signatures, class signatures). - Currently only used for C modules. - """ - all_sigs: list[Sig] = [] - all_class_sigs: list[Sig] = [] - for path in glob.glob(f"{doc_dir}/*.rst"): - with open(path) as f: - loc_sigs, loc_class_sigs = parse_all_signatures(f.readlines()) - all_sigs += loc_sigs - all_class_sigs += loc_class_sigs - sigs = dict(find_unique_signatures(all_sigs)) - class_sigs = dict(find_unique_signatures(all_class_sigs)) - return sigs, class_sigs + with open(target, "w", encoding="utf-8") as file: + file.write(output) def generate_stubs(options: Options) -> None: """Main entry point for the program.""" mypy_opts = mypy_options(options) - py_modules, c_modules = collect_build_targets(options, mypy_opts) - sig_generators = get_sig_generators(options) + py_modules, pyc_modules, c_modules = collect_build_targets(options, mypy_opts) + all_modules = py_modules + pyc_modules + c_modules + all_module_names = sorted(m.module for m in all_modules) # Use parsed sources to generate stubs for Python modules. generate_asts_for_modules(py_modules, options.parse_only, mypy_opts, options.verbose) files = [] - for mod in py_modules: + for mod in py_modules + pyc_modules: assert mod.path is not None, "Not found module was not skipped" target = mod.module.replace(".", "/") - if os.path.basename(mod.path) == "__init__.py": + if os.path.basename(mod.path) in ["__init__.py", "__init__.pyc"]: target += "/__init__.pyi" else: target += ".pyi" target = os.path.join(options.output_dir, target) files.append(target) with generate_guarded(mod.module, target, options.ignore_errors, options.verbose): - generate_stub_from_ast( - mod, target, options.parse_only, options.include_private, options.export_less + generate_stub_for_py_module( + mod, + target, + parse_only=options.parse_only, + inspect=options.inspect or mod in pyc_modules, + include_private=options.include_private, + export_less=options.export_less, + include_docstrings=options.include_docstrings, + doc_dir=options.doc_dir, + all_modules=all_module_names, ) # Separately analyse C modules using different logic. for mod in c_modules: - if any(py_mod.module.startswith(mod.module + ".") for py_mod in py_modules + c_modules): + if any(py_mod.module.startswith(mod.module + ".") for py_mod in all_modules): target = mod.module.replace(".", "/") + "/__init__.pyi" else: target = mod.module.replace(".", "/") + ".pyi" target = os.path.join(options.output_dir, target) files.append(target) with generate_guarded(mod.module, target, options.ignore_errors, options.verbose): - generate_stub_for_c_module(mod.module, target, sig_generators=sig_generators) - num_modules = len(py_modules) + len(c_modules) + generate_stub_for_c_module( + mod.module, + target, + known_modules=all_module_names, + doc_dir=options.doc_dir, + include_private=options.include_private, + export_less=options.export_less, + include_docstrings=options.include_docstrings, + ) + num_modules = len(all_modules) if not options.quiet and num_modules > 0: print("Processed %d modules" % num_modules) if len(files) == 1: @@ -1752,10 +1753,21 @@ def parse_options(args: list[str]) -> Options: "respect __all__)", ) parser.add_argument( + "--no-analysis", "--parse-only", + dest="parse_only", action="store_true", help="don't perform semantic analysis of sources, just parse them " - "(only applies to Python modules, might affect quality of stubs)", + "(only applies to Python modules, might affect quality of stubs. " + "Not compatible with --inspect)", + ) + parser.add_argument( + "--inspect-mode", + dest="inspect", + action="store_true", + help="import and inspect modules instead of parsing source code." + "This is the default behavior for c modules and pyc-only packages, but " + "it is also useful for pure python modules with dynamically generated members.", ) parser.add_argument( "--include-private", @@ -1768,6 +1780,11 @@ def parse_options(args: list[str]) -> Options: action="store_true", help="don't implicitly export all names imported from other modules in the same package", ) + parser.add_argument( + "--include-docstrings", + action="store_true", + help="include existing docstrings with the stubs", + ) parser.add_argument("-v", "--verbose", action="store_true", help="show more verbose messages") parser.add_argument("-q", "--quiet", action="store_true", help="show fewer messages") parser.add_argument( @@ -1827,14 +1844,16 @@ def parse_options(args: list[str]) -> Options: parser.error("May only specify one of: modules/packages or files.") if ns.quiet and ns.verbose: parser.error("Cannot specify both quiet and verbose messages") + if ns.inspect and ns.parse_only: + parser.error("Cannot specify both --parse-only/--no-analysis and --inspect-mode") # Create the output folder if it doesn't already exist. - if not os.path.exists(ns.output_dir): - os.makedirs(ns.output_dir) + os.makedirs(ns.output_dir, exist_ok=True) return Options( pyversion=pyversion, no_import=ns.no_import, + inspect=ns.inspect, doc_dir=ns.doc_dir, search_path=ns.search_path.split(":"), interpreter=ns.interpreter, @@ -1848,6 +1867,7 @@ def parse_options(args: list[str]) -> Options: verbose=ns.verbose, quiet=ns.quiet, export_less=ns.export_less, + include_docstrings=ns.include_docstrings, ) diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index add33e66cee3..29b2636d39cc 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -6,61 +6,42 @@ from __future__ import annotations +import glob import importlib import inspect +import keyword import os.path -import re -from abc import abstractmethod -from types import ModuleType -from typing import Any, Iterable, Mapping -from typing_extensions import Final +from types import FunctionType, ModuleType +from typing import Any, Mapping +from mypy.fastparse import parse_type_comment from mypy.moduleinspect import is_c_module from mypy.stubdoc import ( ArgSig, FunctionSig, + Sig, + find_unique_signatures, infer_arg_sig_from_anon_docstring, infer_prop_type_from_docstring, infer_ret_type_sig_from_anon_docstring, infer_ret_type_sig_from_docstring, infer_sig_from_docstring, + parse_all_signatures, ) - -# Members of the typing module to consider for importing by default. -_DEFAULT_TYPING_IMPORTS: Final = ( - "Any", - "Callable", - "ClassVar", - "Dict", - "Iterable", - "Iterator", - "List", - "Optional", - "Tuple", - "Union", +from mypy.stubutil import ( + BaseStubGenerator, + ClassInfo, + FunctionContext, + SignatureGenerator, + infer_method_arg_types, + infer_method_ret_type, ) -class SignatureGenerator: - """Abstract base class for extracting a list of FunctionSigs for each function.""" - - @abstractmethod - def get_function_sig( - self, func: object, module_name: str, name: str - ) -> list[FunctionSig] | None: - pass - - @abstractmethod - def get_method_sig( - self, func: object, module_name: str, class_name: str, name: str, self_var: str - ) -> list[FunctionSig] | None: - pass - - class ExternalSignatureGenerator(SignatureGenerator): def __init__( self, func_sigs: dict[str, str] | None = None, class_sigs: dict[str, str] | None = None - ): + ) -> None: """ Takes a mapping of function/method names to signatures and class name to class signatures (usually corresponds to __init__). @@ -68,83 +49,107 @@ class signatures (usually corresponds to __init__). self.func_sigs = func_sigs or {} self.class_sigs = class_sigs or {} - def get_function_sig( - self, func: object, module_name: str, name: str - ) -> list[FunctionSig] | None: - if name in self.func_sigs: - return [ - FunctionSig( - name=name, - args=infer_arg_sig_from_anon_docstring(self.func_sigs[name]), - ret_type="Any", - ) - ] - else: - return None + @classmethod + def from_doc_dir(cls, doc_dir: str) -> ExternalSignatureGenerator: + """Instantiate from a directory of .rst files.""" + all_sigs: list[Sig] = [] + all_class_sigs: list[Sig] = [] + for path in glob.glob(f"{doc_dir}/*.rst"): + with open(path) as f: + loc_sigs, loc_class_sigs = parse_all_signatures(f.readlines()) + all_sigs += loc_sigs + all_class_sigs += loc_class_sigs + sigs = dict(find_unique_signatures(all_sigs)) + class_sigs = dict(find_unique_signatures(all_class_sigs)) + return ExternalSignatureGenerator(sigs, class_sigs) - def get_method_sig( - self, func: object, module_name: str, class_name: str, name: str, self_var: str + def get_function_sig( + self, default_sig: FunctionSig, ctx: FunctionContext ) -> list[FunctionSig] | None: + # method: if ( - name in ("__new__", "__init__") - and name not in self.func_sigs - and class_name in self.class_sigs + ctx.class_info + and ctx.name in ("__new__", "__init__") + and ctx.name not in self.func_sigs + and ctx.class_info.name in self.class_sigs ): return [ FunctionSig( - name=name, - args=infer_arg_sig_from_anon_docstring(self.class_sigs[class_name]), - ret_type="None" if name == "__init__" else "Any", + name=ctx.name, + args=infer_arg_sig_from_anon_docstring(self.class_sigs[ctx.class_info.name]), + ret_type=infer_method_ret_type(ctx.name), ) ] - return self.get_function_sig(func, module_name, name) + + # function: + if ctx.name not in self.func_sigs: + return None + + inferred = [ + FunctionSig( + name=ctx.name, + args=infer_arg_sig_from_anon_docstring(self.func_sigs[ctx.name]), + ret_type=None, + ) + ] + if ctx.class_info: + return self.remove_self_type(inferred, ctx.class_info.self_var) + else: + return inferred + + def get_property_type(self, default_type: str | None, ctx: FunctionContext) -> str | None: + return None class DocstringSignatureGenerator(SignatureGenerator): def get_function_sig( - self, func: object, module_name: str, name: str + self, default_sig: FunctionSig, ctx: FunctionContext ) -> list[FunctionSig] | None: - docstr = getattr(func, "__doc__", None) - inferred = infer_sig_from_docstring(docstr, name) + inferred = infer_sig_from_docstring(ctx.docstring, ctx.name) if inferred: - assert docstr is not None - if is_pybind11_overloaded_function_docstring(docstr, name): + assert ctx.docstring is not None + if is_pybind11_overloaded_function_docstring(ctx.docstring, ctx.name): # Remove pybind11 umbrella (*args, **kwargs) for overloaded functions del inferred[-1] - return inferred - def get_method_sig( - self, func: object, module_name: str, class_name: str, name: str, self_var: str - ) -> list[FunctionSig] | None: - return self.get_function_sig(func, module_name, name) + if ctx.class_info: + if not inferred and ctx.name == "__init__": + # look for class-level constructor signatures of the form () + inferred = infer_sig_from_docstring(ctx.class_info.docstring, ctx.class_info.name) + if inferred: + inferred = [sig._replace(name="__init__") for sig in inferred] + return self.remove_self_type(inferred, ctx.class_info.self_var) + else: + return inferred + def get_property_type(self, default_type: str | None, ctx: FunctionContext) -> str | None: + """Infer property type from docstring or docstring signature.""" + if ctx.docstring is not None: + inferred = infer_ret_type_sig_from_anon_docstring(ctx.docstring) + if inferred: + return inferred + inferred = infer_ret_type_sig_from_docstring(ctx.docstring, ctx.name) + if inferred: + return inferred + inferred = infer_prop_type_from_docstring(ctx.docstring) + return inferred + else: + return None -class FallbackSignatureGenerator(SignatureGenerator): - def get_function_sig( - self, func: object, module_name: str, name: str - ) -> list[FunctionSig] | None: - return [ - FunctionSig( - name=name, - args=infer_arg_sig_from_anon_docstring("(*args, **kwargs)"), - ret_type="Any", - ) - ] - def get_method_sig( - self, func: object, module_name: str, class_name: str, name: str, self_var: str - ) -> list[FunctionSig] | None: - return [ - FunctionSig( - name=name, - args=infer_method_sig(name, self_var), - ret_type="None" if name == "__init__" else "Any", - ) - ] +def is_pybind11_overloaded_function_docstring(docstring: str, name: str) -> bool: + return docstring.startswith(f"{name}(*args, **kwargs)\nOverloaded function.\n\n") def generate_stub_for_c_module( - module_name: str, target: str, sig_generators: Iterable[SignatureGenerator] + module_name: str, + target: str, + known_modules: list[str], + doc_dir: str = "", + *, + include_private: bool = False, + export_less: bool = False, + include_docstrings: bool = False, ) -> None: """Generate stub for C module. @@ -158,387 +163,708 @@ def generate_stub_for_c_module( If directory for target doesn't exist it will be created. Existing stub will be overwritten. """ - module = importlib.import_module(module_name) - assert is_c_module(module), f"{module_name} is not a C module" subdir = os.path.dirname(target) if subdir and not os.path.isdir(subdir): os.makedirs(subdir) - imports: list[str] = [] - functions: list[str] = [] - done = set() - items = sorted(module.__dict__.items(), key=lambda x: x[0]) - for name, obj in items: - if is_c_function(obj): - generate_c_function_stub( - module, name, obj, functions, imports=imports, sig_generators=sig_generators - ) - done.add(name) - types: list[str] = [] - for name, obj in items: - if name.startswith("__") and name.endswith("__"): - continue - if is_c_type(obj): - generate_c_type_stub( - module, name, obj, types, imports=imports, sig_generators=sig_generators - ) - done.add(name) - variables = [] - for name, obj in items: - if name.startswith("__") and name.endswith("__"): - continue - if name not in done and not inspect.ismodule(obj): - type_str = strip_or_import(get_type_fullname(type(obj)), module, imports) - variables.append(f"{name}: {type_str}") - output = sorted(set(imports)) - for line in variables: - output.append(line) - for line in types: - if line.startswith("class") and output and output[-1]: - output.append("") - output.append(line) - if output and functions: - output.append("") - for line in functions: - output.append(line) - output = add_typing_import(output) - with open(target, "w") as file: - for line in output: - file.write(f"{line}\n") - - -def add_typing_import(output: list[str]) -> list[str]: - """Add typing imports for collections/types that occur in the generated stub.""" - names = [] - for name in _DEFAULT_TYPING_IMPORTS: - if any(re.search(r"\b%s\b" % name, line) for line in output): - names.append(name) - if names: - return [f"from typing import {', '.join(names)}", ""] + output - else: - return output[:] + gen = InspectionStubGenerator( + module_name, + known_modules, + doc_dir, + include_private=include_private, + export_less=export_less, + include_docstrings=include_docstrings, + ) + gen.generate_module() + output = gen.output() -def is_c_function(obj: object) -> bool: - return inspect.isbuiltin(obj) or type(obj) is type(ord) + with open(target, "w") as file: + file.write(output) -def is_c_method(obj: object) -> bool: - return inspect.ismethoddescriptor(obj) or type(obj) in ( - type(str.index), - type(str.__add__), - type(str.__new__), - ) +class CFunctionStub: + """ + Class that mimics a C function in order to provide parseable docstrings. + """ + def __init__(self, name: str, doc: str, is_abstract: bool = False) -> None: + self.__name__ = name + self.__doc__ = doc + self.__abstractmethod__ = is_abstract -def is_c_classmethod(obj: object) -> bool: - return inspect.isbuiltin(obj) or type(obj).__name__ in ( - "classmethod", - "classmethod_descriptor", - ) + @classmethod + def _from_sig(cls, sig: FunctionSig, is_abstract: bool = False) -> CFunctionStub: + return CFunctionStub(sig.name, sig.format_sig()[:-4], is_abstract) + @classmethod + def _from_sigs(cls, sigs: list[FunctionSig], is_abstract: bool = False) -> CFunctionStub: + return CFunctionStub( + sigs[0].name, "\n".join(sig.format_sig()[:-4] for sig in sigs), is_abstract + ) + + def __get__(self) -> None: + """ + This exists to make this object look like a method descriptor and thus + return true for CStubGenerator.ismethod() + """ + pass -def is_c_property(obj: object) -> bool: - return inspect.isdatadescriptor(obj) or hasattr(obj, "fget") +class InspectionStubGenerator(BaseStubGenerator): + """Stub generator that does not parse code. -def is_c_property_readonly(prop: Any) -> bool: - return hasattr(prop, "fset") and prop.fset is None + Generation is performed by inspecting the module's contents, and thus works + for highly dynamic modules, pyc files, and C modules (via the CStubGenerator + subclass). + """ + def __init__( + self, + module_name: str, + known_modules: list[str], + doc_dir: str = "", + _all_: list[str] | None = None, + include_private: bool = False, + export_less: bool = False, + include_docstrings: bool = False, + module: ModuleType | None = None, + ) -> None: + self.doc_dir = doc_dir + if module is None: + self.module = importlib.import_module(module_name) + else: + self.module = module + self.is_c_module = is_c_module(self.module) + self.known_modules = known_modules + self.resort_members = self.is_c_module + super().__init__(_all_, include_private, export_less, include_docstrings) + self.module_name = module_name + if self.is_c_module: + # Add additional implicit imports. + # C-extensions are given more lattitude since they do not import the typing module. + self.known_imports.update( + { + "typing": [ + "Any", + "Callable", + "ClassVar", + "Dict", + "Iterable", + "Iterator", + "List", + "NamedTuple", + "Optional", + "Tuple", + "Union", + ] + } + ) -def is_c_type(obj: object) -> bool: - return inspect.isclass(obj) or type(obj) is type(int) + def get_default_function_sig(self, func: object, ctx: FunctionContext) -> FunctionSig: + argspec = None + if not self.is_c_module: + # Get the full argument specification of the function + try: + argspec = inspect.getfullargspec(func) + except TypeError: + # some callables cannot be inspected, e.g. functools.partial + pass + if argspec is None: + if ctx.class_info is not None: + # method: + return FunctionSig( + name=ctx.name, + args=infer_c_method_args(ctx.name, ctx.class_info.self_var), + ret_type=infer_method_ret_type(ctx.name), + ) + else: + # function: + return FunctionSig( + name=ctx.name, + args=[ArgSig(name="*args"), ArgSig(name="**kwargs")], + ret_type=None, + ) + # Extract the function arguments, defaults, and varargs + args = argspec.args + defaults = argspec.defaults + varargs = argspec.varargs + kwargs = argspec.varkw + annotations = argspec.annotations + + def get_annotation(key: str) -> str | None: + if key not in annotations: + return None + argtype = annotations[key] + if argtype is None: + return "None" + if not isinstance(argtype, str): + return self.get_type_fullname(argtype) + return argtype + + arglist: list[ArgSig] = [] + # Add the arguments to the signature + for i, arg in enumerate(args): + # Check if the argument has a default value + if defaults and i >= len(args) - len(defaults): + default_value = defaults[i - (len(args) - len(defaults))] + if arg in annotations: + argtype = annotations[arg] + else: + argtype = self.get_type_annotation(default_value) + if argtype == "None": + # None is not a useful annotation, but we can infer that the arg + # is optional + incomplete = self.add_name("_typeshed.Incomplete") + argtype = f"{incomplete} | None" + arglist.append(ArgSig(arg, argtype, default=True)) + else: + arglist.append(ArgSig(arg, get_annotation(arg), default=False)) + + # Add *args if present + if varargs: + arglist.append(ArgSig(f"*{varargs}", get_annotation(varargs))) + + # Add **kwargs if present + if kwargs: + arglist.append(ArgSig(f"**{kwargs}", get_annotation(kwargs))) + + # add types for known special methods + if ctx.class_info is not None and all( + arg.type is None and arg.default is False for arg in arglist + ): + new_args = infer_method_arg_types( + ctx.name, ctx.class_info.self_var, [arg.name for arg in arglist if arg.name] + ) + if new_args is not None: + arglist = new_args -def is_pybind11_overloaded_function_docstring(docstr: str, name: str) -> bool: - return docstr.startswith(f"{name}(*args, **kwargs)\n" + "Overloaded function.\n\n") + ret_type = get_annotation("return") or infer_method_ret_type(ctx.name) + return FunctionSig(ctx.name, arglist, ret_type) + def get_sig_generators(self) -> list[SignatureGenerator]: + if not self.is_c_module: + return [] + else: + sig_generators: list[SignatureGenerator] = [DocstringSignatureGenerator()] + if self.doc_dir: + # Collect info from docs (if given). Always check these first. + sig_generators.insert(0, ExternalSignatureGenerator.from_doc_dir(self.doc_dir)) + return sig_generators -def generate_c_function_stub( - module: ModuleType, - name: str, - obj: object, - output: list[str], - imports: list[str], - sig_generators: Iterable[SignatureGenerator], - self_var: str | None = None, - class_name: str | None = None, -) -> None: - """Generate stub for a single function or method. + def strip_or_import(self, type_name: str) -> str: + """Strips unnecessary module names from typ. - The result (always a single line) will be appended to 'output'. - If necessary, any required names will be added to 'imports'. - The 'class_name' is used to find signature of __init__ or __new__ in - 'class_sigs'. - """ - inferred: list[FunctionSig] | None = None - if class_name: - # method: - assert self_var is not None, "self_var should be provided for methods" - for sig_gen in sig_generators: - inferred = sig_gen.get_method_sig(obj, module.__name__, class_name, name, self_var) - if inferred: - # add self/cls var, if not present - for sig in inferred: - if not sig.args or sig.args[0].name != self_var: - sig.args.insert(0, ArgSig(name=self_var)) - break - else: - # function: - for sig_gen in sig_generators: - inferred = sig_gen.get_function_sig(obj, module.__name__, name) - if inferred: - break + If typ represents a type that is inside module or is a type coming from builtins, remove + module declaration from it. Return stripped name of the type. - if not inferred: - raise ValueError( - "No signature was found. This should never happen " - "if FallbackSignatureGenerator is provided" + Arguments: + typ: name of the type + """ + local_modules = ["builtins", self.module_name] + parsed_type = parse_type_comment(type_name, 0, 0, None)[1] + assert parsed_type is not None, type_name + return self.print_annotation(parsed_type, self.known_modules, local_modules) + + def get_obj_module(self, obj: object) -> str | None: + """Return module name of the object.""" + return getattr(obj, "__module__", None) + + def is_defined_in_module(self, obj: object) -> bool: + """Check if object is considered defined in the current module.""" + module = self.get_obj_module(obj) + return module is None or module == self.module_name + + def generate_module(self) -> None: + all_items = self.get_members(self.module) + if self.resort_members: + all_items = sorted(all_items, key=lambda x: x[0]) + items = [] + for name, obj in all_items: + if inspect.ismodule(obj) and obj.__name__ in self.known_modules: + module_name = obj.__name__ + if module_name.startswith(self.module_name + "."): + # from {.rel_name} import {mod_name} as {name} + pkg_name, mod_name = module_name.rsplit(".", 1) + rel_module = pkg_name[len(self.module_name) :] or "." + self.import_tracker.add_import_from(rel_module, [(mod_name, name)]) + self.import_tracker.reexport(name) + else: + # import {module_name} as {name} + self.import_tracker.add_import(module_name, name) + self.import_tracker.reexport(name) + elif self.is_defined_in_module(obj) and not inspect.ismodule(obj): + # process this below + items.append((name, obj)) + else: + # from {obj_module} import {obj_name} + obj_module_name = self.get_obj_module(obj) + if obj_module_name: + self.import_tracker.add_import_from(obj_module_name, [(name, None)]) + if self.should_reexport(name, obj_module_name, name_is_alias=False): + self.import_tracker.reexport(name) + + self.set_defined_names({name for name, obj in all_items if not inspect.ismodule(obj)}) + + if self.resort_members: + functions: list[str] = [] + types: list[str] = [] + variables: list[str] = [] + else: + output: list[str] = [] + functions = types = variables = output + + for name, obj in items: + if self.is_function(obj): + self.generate_function_stub(name, obj, output=functions) + elif inspect.isclass(obj): + self.generate_class_stub(name, obj, output=types) + else: + self.generate_variable_stub(name, obj, output=variables) + + self._output = [] + + if self.resort_members: + for line in variables: + self._output.append(line + "\n") + for line in types: + if line.startswith("class") and self._output and self._output[-1]: + self._output.append("\n") + self._output.append(line + "\n") + if self._output and functions: + self._output.append("\n") + for line in functions: + self._output.append(line + "\n") + else: + for i, line in enumerate(output): + if ( + self._output + and line.startswith("class") + and ( + not self._output[-1].startswith("class") + or (len(output) > i + 1 and output[i + 1].startswith(" ")) + ) + ) or ( + self._output + and self._output[-1].startswith("def") + and not line.startswith("def") + ): + self._output.append("\n") + self._output.append(line + "\n") + self.check_undefined_names() + + def is_skipped_attribute(self, attr: str) -> bool: + return ( + attr + in ( + "__class__", + "__getattribute__", + "__str__", + "__repr__", + "__doc__", + "__dict__", + "__module__", + "__weakref__", + "__annotations__", + ) + or attr in self.IGNORED_DUNDERS + or is_pybind_skipped_attribute(attr) # For pickling + or keyword.iskeyword(attr) ) - is_classmethod = self_var == "cls" - is_overloaded = len(inferred) > 1 if inferred else False - if is_overloaded: - imports.append("from typing import overload") - if inferred: - for signature in inferred: - args: list[str] = [] - for arg in signature.args: - if arg.name == self_var: - arg_def = self_var - else: - arg_def = arg.name - if arg_def == "None": - arg_def = "_none" # None is not a valid argument name - - if arg.type: - arg_def += ": " + strip_or_import(arg.type, module, imports) - - if arg.default: - arg_def += " = ..." - - args.append(arg_def) - - if is_overloaded: - output.append("@overload") - if is_classmethod: - output.append("@classmethod") - output.append( - "def {function}({args}) -> {ret}: ...".format( - function=name, - args=", ".join(args), - ret=strip_or_import(signature.ret_type, module, imports), - ) + def get_members(self, obj: object) -> list[tuple[str, Any]]: + obj_dict: Mapping[str, Any] = getattr(obj, "__dict__") # noqa: B009 + results = [] + for name in obj_dict: + if self.is_skipped_attribute(name): + continue + # Try to get the value via getattr + try: + value = getattr(obj, name) + except AttributeError: + continue + else: + results.append((name, value)) + return results + + def get_type_annotation(self, obj: object) -> str: + """ + Given an instance, return a string representation of its type that is valid + to use as a type annotation. + """ + if obj is None or obj is type(None): + return "None" + elif inspect.isclass(obj): + return f"type[{self.get_type_fullname(obj)}]" + elif isinstance(obj, FunctionType): + return self.add_name("typing.Callable") + elif isinstance(obj, ModuleType): + return self.add_name("types.ModuleType", require=False) + else: + return self.get_type_fullname(type(obj)) + + def is_function(self, obj: object) -> bool: + if self.is_c_module: + return inspect.isbuiltin(obj) + else: + return inspect.isfunction(obj) + + def is_method(self, class_info: ClassInfo, name: str, obj: object) -> bool: + if self.is_c_module: + return inspect.ismethoddescriptor(obj) or type(obj) in ( + type(str.index), + type(str.__add__), + type(str.__new__), + ) + else: + # this is valid because it is only called on members of a class + return inspect.isfunction(obj) + + def is_classmethod(self, class_info: ClassInfo, name: str, obj: object) -> bool: + if self.is_c_module: + return inspect.isbuiltin(obj) or type(obj).__name__ in ( + "classmethod", + "classmethod_descriptor", ) + else: + return inspect.ismethod(obj) + + def is_staticmethod(self, class_info: ClassInfo | None, name: str, obj: object) -> bool: + if class_info is None: + return False + elif self.is_c_module: + raw_lookup: Mapping[str, Any] = getattr(class_info.cls, "__dict__") # noqa: B009 + raw_value = raw_lookup.get(name, obj) + return isinstance(raw_value, staticmethod) + else: + return isinstance(inspect.getattr_static(class_info.cls, name), staticmethod) + @staticmethod + def is_abstract_method(obj: object) -> bool: + return getattr(obj, "__abstractmethod__", False) -def strip_or_import(typ: str, module: ModuleType, imports: list[str]) -> str: - """Strips unnecessary module names from typ. + @staticmethod + def is_property(class_info: ClassInfo, name: str, obj: object) -> bool: + return inspect.isdatadescriptor(obj) or hasattr(obj, "fget") - If typ represents a type that is inside module or is a type coming from builtins, remove - module declaration from it. Return stripped name of the type. + @staticmethod + def is_property_readonly(prop: Any) -> bool: + return hasattr(prop, "fset") and prop.fset is None - Arguments: - typ: name of the type - module: in which this type is used - imports: list of import statements (may be modified during the call) - """ - stripped_type = typ - if any(c in typ for c in "[,"): - for subtyp in re.split(r"[\[,\]]", typ): - strip_or_import(subtyp.strip(), module, imports) - if module: - stripped_type = re.sub(r"(^|[\[, ]+)" + re.escape(module.__name__ + "."), r"\1", typ) - elif module and typ.startswith(module.__name__ + "."): - stripped_type = typ[len(module.__name__) + 1 :] - elif "." in typ: - arg_module = typ[: typ.rindex(".")] - if arg_module == "builtins": - stripped_type = typ[len("builtins") + 1 :] + def is_static_property(self, obj: object) -> bool: + """For c-modules, whether the property behaves like an attribute""" + if self.is_c_module: + # StaticProperty is from boost-python + return type(obj).__name__ in ("pybind11_static_property", "StaticProperty") else: - imports.append(f"import {arg_module}") - if stripped_type == "NoneType": - stripped_type = "None" - return stripped_type + return False + + def process_inferred_sigs(self, inferred: list[FunctionSig]) -> None: + for i, sig in enumerate(inferred): + for arg in sig.args: + if arg.type is not None: + arg.type = self.strip_or_import(arg.type) + if sig.ret_type is not None: + inferred[i] = sig._replace(ret_type=self.strip_or_import(sig.ret_type)) + + def generate_function_stub( + self, name: str, obj: object, *, output: list[str], class_info: ClassInfo | None = None + ) -> None: + """Generate stub for a single function or method. + + The result (always a single line) will be appended to 'output'. + If necessary, any required names will be added to 'imports'. + The 'class_name' is used to find signature of __init__ or __new__ in + 'class_sigs'. + """ + docstring: Any = getattr(obj, "__doc__", None) + if not isinstance(docstring, str): + docstring = None + + ctx = FunctionContext( + self.module_name, + name, + docstring=docstring, + is_abstract=self.is_abstract_method(obj), + class_info=class_info, + ) + if self.is_private_name(name, ctx.fullname) or self.is_not_in_all(name): + return + self.record_name(ctx.name) + default_sig = self.get_default_function_sig(obj, ctx) + inferred = self.get_signatures(default_sig, self.sig_generators, ctx) + self.process_inferred_sigs(inferred) -def is_static_property(obj: object) -> bool: - return type(obj).__name__ == "pybind11_static_property" + decorators = [] + if len(inferred) > 1: + decorators.append("@{}".format(self.add_name("typing.overload"))) + if ctx.is_abstract: + decorators.append("@{}".format(self.add_name("abc.abstractmethod"))) -def generate_c_property_stub( - name: str, - obj: object, - static_properties: list[str], - rw_properties: list[str], - ro_properties: list[str], - readonly: bool, - module: ModuleType | None = None, - imports: list[str] | None = None, -) -> None: - """Generate property stub using introspection of 'obj'. + if class_info is not None: + if self.is_staticmethod(class_info, name, obj): + decorators.append("@staticmethod") + else: + for sig in inferred: + if not sig.args or sig.args[0].name not in ("self", "cls"): + sig.args.insert(0, ArgSig(name=class_info.self_var)) + # a sig generator indicates @classmethod by specifying the cls arg. + if inferred[0].args and inferred[0].args[0].name == "cls": + decorators.append("@classmethod") + + if docstring: + docstring = self._indent_docstring(docstring) + output.extend(self.format_func_def(inferred, decorators=decorators, docstring=docstring)) + self._fix_iter(ctx, inferred, output) + + def _indent_docstring(self, docstring: str) -> str: + """Fix indentation of docstring extracted from pybind11 or other binding generators.""" + lines = docstring.splitlines(keepends=True) + indent = self._indent + " " + if len(lines) > 1: + if not all(line.startswith(indent) or not line.strip() for line in lines): + # if the docstring is not indented, then indent all but the first line + for i, line in enumerate(lines[1:]): + if line.strip(): + lines[i + 1] = indent + line + # if there's a trailing newline, add a final line to visually indent the quoted docstring + if lines[-1].endswith("\n"): + if len(lines) > 1: + lines.append(indent) + else: + lines[-1] = lines[-1][:-1] + return "".join(lines) + + def _fix_iter( + self, ctx: FunctionContext, inferred: list[FunctionSig], output: list[str] + ) -> None: + """Ensure that objects which implement old-style iteration via __getitem__ + are considered iterable. + """ + if ( + ctx.class_info + and ctx.class_info.cls is not None + and ctx.name == "__getitem__" + and "__iter__" not in ctx.class_info.cls.__dict__ + ): + item_type: str | None = None + for sig in inferred: + if sig.args and sig.args[-1].type == "int": + item_type = sig.ret_type + break + if item_type is None: + return + obj = CFunctionStub( + "__iter__", f"def __iter__(self) -> typing.Iterator[{item_type}]\n" + ) + self.generate_function_stub("__iter__", obj, output=output, class_info=ctx.class_info) + + def generate_property_stub( + self, + name: str, + raw_obj: object, + obj: object, + static_properties: list[str], + rw_properties: list[str], + ro_properties: list[str], + class_info: ClassInfo | None = None, + ) -> None: + """Generate property stub using introspection of 'obj'. + + Try to infer type from docstring, append resulting lines to 'output'. + + raw_obj : object before evaluation of descriptor (if any) + obj : object after evaluation of descriptor + """ - Try to infer type from docstring, append resulting lines to 'output'. - """ + docstring = getattr(raw_obj, "__doc__", None) + fget = getattr(raw_obj, "fget", None) + if fget: + alt_docstr = getattr(fget, "__doc__", None) + if alt_docstr and docstring: + docstring += "\n" + alt_docstr + elif alt_docstr: + docstring = alt_docstr + + ctx = FunctionContext( + self.module_name, name, docstring=docstring, is_abstract=False, class_info=class_info + ) - def infer_prop_type(docstr: str | None) -> str | None: - """Infer property type from docstring or docstring signature.""" - if docstr is not None: - inferred = infer_ret_type_sig_from_anon_docstring(docstr) - if not inferred: - inferred = infer_ret_type_sig_from_docstring(docstr, name) - if not inferred: - inferred = infer_prop_type_from_docstring(docstr) - return inferred - else: - return None + if self.is_private_name(name, ctx.fullname) or self.is_not_in_all(name): + return - # Ignore special properties/attributes. - if is_skipped_attribute(name): - return - - inferred = infer_prop_type(getattr(obj, "__doc__", None)) - if not inferred: - fget = getattr(obj, "fget", None) - inferred = infer_prop_type(getattr(fget, "__doc__", None)) - if not inferred: - inferred = "Any" - - if module is not None and imports is not None: - inferred = strip_or_import(inferred, module, imports) - - if is_static_property(obj): - trailing_comment = " # read-only" if readonly else "" - static_properties.append(f"{name}: ClassVar[{inferred}] = ...{trailing_comment}") - else: # regular property - if readonly: - ro_properties.append("@property") - ro_properties.append(f"def {name}(self) -> {inferred}: ...") + self.record_name(ctx.name) + static = self.is_static_property(raw_obj) + readonly = self.is_property_readonly(raw_obj) + if static: + ret_type: str | None = self.strip_or_import(self.get_type_annotation(obj)) else: - rw_properties.append(f"{name}: {inferred}") + default_sig = self.get_default_function_sig(raw_obj, ctx) + ret_type = default_sig.ret_type + inferred_type = self.get_property_type(ret_type, self.sig_generators, ctx) + if inferred_type is not None: + inferred_type = self.strip_or_import(inferred_type) -def generate_c_type_stub( - module: ModuleType, - class_name: str, - obj: type, - output: list[str], - imports: list[str], - sig_generators: Iterable[SignatureGenerator], -) -> None: - """Generate stub for a single class using runtime introspection. + if static: + classvar = self.add_name("typing.ClassVar") + trailing_comment = " # read-only" if readonly else "" + if inferred_type is None: + inferred_type = self.add_name("_typeshed.Incomplete") - The result lines will be appended to 'output'. If necessary, any - required names will be added to 'imports'. - """ - # typeshed gives obj.__dict__ the not quite correct type Dict[str, Any] - # (it could be a mappingproxy!), which makes mypyc mad, so obfuscate it. - obj_dict: Mapping[str, Any] = getattr(obj, "__dict__") # noqa: B009 - items = sorted(obj_dict.items(), key=lambda x: method_name_sort_key(x[0])) - methods: list[str] = [] - types: list[str] = [] - static_properties: list[str] = [] - rw_properties: list[str] = [] - ro_properties: list[str] = [] - done: set[str] = set() - for attr, value in items: - if is_c_method(value) or is_c_classmethod(value): - done.add(attr) - if not is_skipped_attribute(attr): + static_properties.append( + f"{self._indent}{name}: {classvar}[{inferred_type}] = ...{trailing_comment}" + ) + else: # regular property + if readonly: + ro_properties.append(f"{self._indent}@property") + sig = FunctionSig(name, [ArgSig("self")], inferred_type) + ro_properties.append(sig.format_sig(indent=self._indent)) + else: + if inferred_type is None: + inferred_type = self.add_name("_typeshed.Incomplete") + + rw_properties.append(f"{self._indent}{name}: {inferred_type}") + + def get_type_fullname(self, typ: type) -> str: + """Given a type, return a string representation""" + if typ is Any: + return "Any" + typename = getattr(typ, "__qualname__", typ.__name__) + module_name = self.get_obj_module(typ) + assert module_name is not None, typ + if module_name != "builtins": + typename = f"{module_name}.{typename}" + return typename + + def get_base_types(self, obj: type) -> list[str]: + all_bases = type.mro(obj) + if all_bases[-1] is object: + # TODO: Is this always object? + del all_bases[-1] + # remove pybind11_object. All classes generated by pybind11 have pybind11_object in their MRO, + # which only overrides a few functions in object type + if all_bases and all_bases[-1].__name__ == "pybind11_object": + del all_bases[-1] + # remove the class itself + all_bases = all_bases[1:] + # Remove base classes of other bases as redundant. + bases: list[type] = [] + for base in all_bases: + if not any(issubclass(b, base) for b in bases): + bases.append(base) + return [self.strip_or_import(self.get_type_fullname(base)) for base in bases] + + def generate_class_stub(self, class_name: str, cls: type, output: list[str]) -> None: + """Generate stub for a single class using runtime introspection. + + The result lines will be appended to 'output'. If necessary, any + required names will be added to 'imports'. + """ + raw_lookup: Mapping[str, Any] = getattr(cls, "__dict__") # noqa: B009 + items = self.get_members(cls) + if self.resort_members: + items = sorted(items, key=lambda x: method_name_sort_key(x[0])) + names = {x[0] for x in items} + methods: list[str] = [] + types: list[str] = [] + static_properties: list[str] = [] + rw_properties: list[str] = [] + ro_properties: list[str] = [] + attrs: list[tuple[str, Any]] = [] + + self.record_name(class_name) + self.indent() + + class_info = ClassInfo(class_name, "", getattr(cls, "__doc__", None), cls) + + for attr, value in items: + # use unevaluated descriptors when dealing with property inspection + raw_value = raw_lookup.get(attr, value) + if self.is_method(class_info, attr, value) or self.is_classmethod( + class_info, attr, value + ): if attr == "__new__": # TODO: We should support __new__. - if "__init__" in obj_dict: + if "__init__" in names: # Avoid duplicate functions if both are present. # But is there any case where .__new__() has a # better signature than __init__() ? continue attr = "__init__" - if is_c_classmethod(value): - self_var = "cls" + # FIXME: make this nicer + if self.is_staticmethod(class_info, attr, value): + class_info.self_var = "" + elif self.is_classmethod(class_info, attr, value): + class_info.self_var = "cls" else: - self_var = "self" - generate_c_function_stub( - module, + class_info.self_var = "self" + self.generate_function_stub(attr, value, output=methods, class_info=class_info) + elif self.is_property(class_info, attr, raw_value): + self.generate_property_stub( attr, + raw_value, value, - methods, - imports=imports, - self_var=self_var, - class_name=class_name, - sig_generators=sig_generators, - ) - elif is_c_property(value): - done.add(attr) - generate_c_property_stub( - attr, - value, - static_properties, - rw_properties, - ro_properties, - is_c_property_readonly(value), - module=module, - imports=imports, - ) - elif is_c_type(value): - generate_c_type_stub( - module, attr, value, types, imports=imports, sig_generators=sig_generators - ) - done.add(attr) - - for attr, value in items: - if is_skipped_attribute(attr): - continue - if attr not in done: - static_properties.append( - "{}: ClassVar[{}] = ...".format( - attr, strip_or_import(get_type_fullname(type(value)), module, imports) + static_properties, + rw_properties, + ro_properties, + class_info, ) - ) - all_bases = type.mro(obj) - if all_bases[-1] is object: - # TODO: Is this always object? - del all_bases[-1] - # remove pybind11_object. All classes generated by pybind11 have pybind11_object in their MRO, - # which only overrides a few functions in object type - if all_bases and all_bases[-1].__name__ == "pybind11_object": - del all_bases[-1] - # remove the class itself - all_bases = all_bases[1:] - # Remove base classes of other bases as redundant. - bases: list[type] = [] - for base in all_bases: - if not any(issubclass(b, base) for b in bases): - bases.append(base) - if bases: - bases_str = "(%s)" % ", ".join( - strip_or_import(get_type_fullname(base), module, imports) for base in bases - ) - else: - bases_str = "" - if types or static_properties or rw_properties or methods or ro_properties: - output.append(f"class {class_name}{bases_str}:") - for line in types: - if ( - output - and output[-1] - and not output[-1].startswith("class") - and line.startswith("class") - ): - output.append("") - output.append(" " + line) - for line in static_properties: - output.append(f" {line}") - for line in rw_properties: - output.append(f" {line}") - for line in methods: - output.append(f" {line}") - for line in ro_properties: - output.append(f" {line}") - else: - output.append(f"class {class_name}{bases_str}: ...") + elif inspect.isclass(value) and self.is_defined_in_module(value): + self.generate_class_stub(attr, value, types) + else: + attrs.append((attr, value)) + + for attr, value in attrs: + if attr == "__hash__" and value is None: + # special case for __hash__ + continue + prop_type_name = self.strip_or_import(self.get_type_annotation(value)) + classvar = self.add_name("typing.ClassVar") + static_properties.append(f"{self._indent}{attr}: {classvar}[{prop_type_name}] = ...") + + self.dedent() + + bases = self.get_base_types(cls) + if bases: + bases_str = "(%s)" % ", ".join(bases) + else: + bases_str = "" + if types or static_properties or rw_properties or methods or ro_properties: + output.append(f"{self._indent}class {class_name}{bases_str}:") + for line in types: + if ( + output + and output[-1] + and not output[-1].strip().startswith("class") + and line.strip().startswith("class") + ): + output.append("") + output.append(line) + for line in static_properties: + output.append(line) + for line in rw_properties: + output.append(line) + for line in methods: + output.append(line) + for line in ro_properties: + output.append(line) + else: + output.append(f"{self._indent}class {class_name}{bases_str}: ...") + def generate_variable_stub(self, name: str, obj: object, output: list[str]) -> None: + """Generate stub for a single variable using runtime introspection. -def get_type_fullname(typ: type) -> str: - return f"{typ.__module__}.{getattr(typ, '__qualname__', typ.__name__)}" + The result lines will be appended to 'output'. If necessary, any + required names will be added to 'imports'. + """ + if self.is_private_name(name, f"{self.module_name}.{name}") or self.is_not_in_all(name): + return + self.record_name(name) + type_str = self.strip_or_import(self.get_type_annotation(obj)) + output.append(f"{name}: {type_str}") def method_name_sort_key(name: str) -> tuple[int, str]: @@ -557,21 +883,9 @@ def is_pybind_skipped_attribute(attr: str) -> bool: return attr.startswith("__pybind11_module_local_") -def is_skipped_attribute(attr: str) -> bool: - return attr in ( - "__getattribute__", - "__str__", - "__repr__", - "__doc__", - "__dict__", - "__module__", - "__weakref__", - ) or is_pybind_skipped_attribute( # For pickling - attr - ) - - -def infer_method_sig(name: str, self_var: str | None = None) -> list[ArgSig]: +def infer_c_method_args( + name: str, self_var: str = "self", arg_names: list[str] | None = None +) -> list[ArgSig]: args: list[ArgSig] | None = None if name.startswith("__") and name.endswith("__"): name = name[2:-2] @@ -611,13 +925,9 @@ def infer_method_sig(name: str, self_var: str | None = None) -> list[ArgSig]: args = [] elif name == "setstate": args = [ArgSig(name="state")] + elif name in ("eq", "ne", "lt", "le", "gt", "ge"): + args = [ArgSig(name="other", type="object")] elif name in ( - "eq", - "ne", - "lt", - "le", - "gt", - "ge", "add", "radd", "sub", @@ -669,7 +979,15 @@ def infer_method_sig(name: str, self_var: str | None = None) -> list[ArgSig]: elif name == "reduce_ex": args = [ArgSig(name="protocol")] elif name == "exit": - args = [ArgSig(name="type"), ArgSig(name="value"), ArgSig(name="traceback")] + args = [ + ArgSig(name="type", type="type[BaseException] | None"), + ArgSig(name="value", type="BaseException | None"), + ArgSig(name="traceback", type="types.TracebackType | None"), + ] + if args is None: + args = infer_method_arg_types(name, self_var, arg_names) + else: + args = [ArgSig(name=self_var)] + args if args is None: args = [ArgSig(name="*args"), ArgSig(name="**kwargs")] - return [ArgSig(name=self_var or "self")] + args + return args diff --git a/mypy/stubinfo.py b/mypy/stubinfo.py index 15bd96d9f4b4..9d8dfbe43f37 100644 --- a/mypy/stubinfo.py +++ b/mypy/stubinfo.py @@ -9,19 +9,15 @@ def approved_stub_package_exists(prefix: str) -> bool: return is_legacy_bundled_package(prefix) or prefix in non_bundled_packages -def stub_package_name(prefix: str) -> str: +def stub_distribution_name(prefix: str) -> str: return legacy_bundled_packages.get(prefix) or non_bundled_packages[prefix] # Stubs for these third-party packages used to be shipped with mypy. # # Map package name to PyPI stub distribution name. -# -# Package name can have one or two components ('a' or 'a.b'). legacy_bundled_packages = { "aiofiles": "types-aiofiles", - "backports": "types-backports", - "backports_abc": "types-backports_abc", "bleach": "types-bleach", "boto": "types-boto", "cachetools": "types-cachetools", @@ -30,17 +26,14 @@ def stub_package_name(prefix: str) -> str: "croniter": "types-croniter", "dataclasses": "types-dataclasses", "dateparser": "types-dateparser", - "datetimerange": "types-DateTimeRange", "dateutil": "types-python-dateutil", "decorator": "types-decorator", "deprecated": "types-Deprecated", "docutils": "types-docutils", "first": "types-first", - "geoip2": "types-geoip2", "gflags": "types-python-gflags", "google.protobuf": "types-protobuf", "markdown": "types-Markdown", - "maxminddb": "types-maxminddb", "mock": "types-mock", "OpenSSL": "types-pyOpenSSL", "paramiko": "types-paramiko", @@ -76,8 +69,6 @@ def stub_package_name(prefix: str) -> str: # Package name can have one or two components ('a' or 'a.b'). # # Note that these packages are omitted for now: -# sqlalchemy: It's unclear which stub package to suggest. There's also -# a mypy plugin available. # pika: typeshed's stubs are on PyPI as types-pika-ts. # types-pika already exists on PyPI, and is more complete in many ways, # but is a non-typeshed stubs package. @@ -86,8 +77,6 @@ def stub_package_name(prefix: str) -> str: "PIL": "types-Pillow", "PyInstaller": "types-pyinstaller", "Xlib": "types-python-xlib", - "annoy": "types-annoy", - "appdirs": "types-appdirs", "aws_xray_sdk": "types-aws-xray-sdk", "babel": "types-babel", "backports.ssl_match_hostname": "types-backports.ssl_match_hostname", @@ -102,7 +91,6 @@ def stub_package_name(prefix: str) -> str: "consolemenu": "types-console-menu", "crontab": "types-python-crontab", "d3dshot": "types-D3DShot", - "dj_database_url": "types-dj-database-url", "dockerfile_parse": "types-dockerfile-parse", "docopt": "types-docopt", "editdistance": "types-editdistance", @@ -117,10 +105,9 @@ def stub_package_name(prefix: str) -> str: "flake8_typing_imports": "types-flake8-typing-imports", "flask_cors": "types-Flask-Cors", "flask_migrate": "types-Flask-Migrate", - "flask_sqlalchemy": "types-Flask-SQLAlchemy", "fpdf": "types-fpdf2", "gdb": "types-gdb", - "google.cloud": "types-google-cloud-ndb", + "google.cloud.ndb": "types-google-cloud-ndb", "hdbcli": "types-hdbcli", "html5lib": "types-html5lib", "httplib2": "types-httplib2", @@ -168,7 +155,6 @@ def stub_package_name(prefix: str) -> str: "tree_sitter": "types-tree-sitter", "tree_sitter_languages": "types-tree-sitter-languages", "ttkthemes": "types-ttkthemes", - "urllib3": "types-urllib3", "vobject": "types-vobject", "whatthepatch": "types-whatthepatch", "win32": "types-pywin32", @@ -178,6 +164,10 @@ def stub_package_name(prefix: str) -> str: "win32comext": "types-pywin32", "win32gui": "types-pywin32", "xmltodict": "types-xmltodict", - "xxhash": "types-xxhash", "zxcvbn": "types-zxcvbn", + # Stub packages that are not from typeshed + # Since these can be installed automatically via --install-types, we have a high trust bar + # for additions here + "pandas": "pandas-stubs", # https://github.com/pandas-dev/pandas-stubs + "lxml": "lxml-stubs", # https://github.com/lxml/lxml-stubs } diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 4a99c407f319..d78b71715159 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -10,7 +10,9 @@ import collections.abc import copy import enum +import functools import importlib +import importlib.machinery import inspect import os import pkgutil @@ -22,11 +24,12 @@ import typing import typing_extensions import warnings +from collections import defaultdict from contextlib import redirect_stderr, redirect_stdout from functools import singledispatch from pathlib import Path -from typing import Any, Generic, Iterator, TypeVar, Union, cast -from typing_extensions import get_origin +from typing import AbstractSet, Any, Generic, Iterator, TypeVar, Union +from typing_extensions import get_origin, is_typeddict import mypy.build import mypy.modulefinder @@ -53,6 +56,17 @@ def __repr__(self) -> str: T = TypeVar("T") MaybeMissing: typing_extensions.TypeAlias = Union[T, Missing] + +class Unrepresentable: + """Marker object for unrepresentable parameter defaults.""" + + def __repr__(self) -> str: + return "" + + +UNREPRESENTABLE: typing_extensions.Final = Unrepresentable() + + _formatter: typing_extensions.Final = FancyFormatter(sys.stdout, sys.stderr, False) @@ -100,7 +114,17 @@ def __init__( self.stub_object = stub_object self.runtime_object = runtime_object self.stub_desc = stub_desc or str(getattr(stub_object, "type", stub_object)) - self.runtime_desc = runtime_desc or _truncate(repr(runtime_object), 100) + + if runtime_desc is None: + runtime_sig = safe_inspect_signature(runtime_object) + if runtime_sig is None: + self.runtime_desc = _truncate(repr(runtime_object), 100) + else: + runtime_is_async = inspect.iscoroutinefunction(runtime_object) + description = describe_runtime_callable(runtime_sig, is_async=runtime_is_async) + self.runtime_desc = _truncate(description, 100) + else: + self.runtime_desc = runtime_desc def is_missing_stub(self) -> bool: """Whether or not the error is for something missing from the stub.""" @@ -129,10 +153,10 @@ def get_description(self, concise: bool = False) -> str: stub_file = stub_node.path or None stub_loc_str = "" - if stub_line: - stub_loc_str += f" at line {stub_line}" if stub_file: stub_loc_str += f" in file {Path(stub_file)}" + if stub_line: + stub_loc_str += f"{':' if stub_file else ' at line '}{stub_line}" runtime_line = None runtime_file = None @@ -147,10 +171,10 @@ def get_description(self, concise: bool = False) -> str: pass runtime_loc_str = "" - if runtime_line: - runtime_loc_str += f" at line {runtime_line}" if runtime_file: runtime_loc_str += f" in file {Path(runtime_file)}" + if runtime_line: + runtime_loc_str += f"{':' if runtime_file else ' at line '}{runtime_line}" output = [ _style("error: ", color="red", bold=True), @@ -209,7 +233,12 @@ def test_module(module_name: str) -> Iterator[Error]: except KeyboardInterrupt: raise except BaseException as e: - yield Error([module_name], f"failed to import, {type(e).__name__}: {e}", stub, MISSING) + note = "" + if isinstance(e, ModuleNotFoundError): + note = " Maybe install the runtime package or alter PYTHONPATH?" + yield Error( + [module_name], f"failed to import.{note} {type(e).__name__}: {e}", stub, MISSING + ) return with warnings.catch_warnings(): @@ -275,44 +304,30 @@ def _verify_exported_names( # desirable in at least the `names_in_runtime_not_stub` case stub_object=MISSING, runtime_object=MISSING, - stub_desc=( - f"Names exported in the stub but not at runtime: " f"{names_in_stub_not_runtime}" - ), + stub_desc=(f"Names exported in the stub but not at runtime: {names_in_stub_not_runtime}"), runtime_desc=( - f"Names exported at runtime but not in the stub: " f"{names_in_runtime_not_stub}" + f"Names exported at runtime but not in the stub: {names_in_runtime_not_stub}" ), ) -def _get_imported_symbol_names(runtime: types.ModuleType) -> frozenset[str] | None: - """Retrieve the names in the global namespace which are known to be imported. - - 1). Use inspect to retrieve the source code of the module - 2). Use symtable to parse the source and retrieve names that are known to be imported - from other modules. +@functools.lru_cache +def _module_symbol_table(runtime: types.ModuleType) -> symtable.SymbolTable | None: + """Retrieve the symbol table for the module (or None on failure). - If either of the above steps fails, return `None`. - - Note that if a set of names is returned, - it won't include names imported via `from foo import *` imports. + 1) Use inspect to retrieve the source code of the module + 2) Use symtable to parse the source (and use what symtable knows for its purposes) """ try: source = inspect.getsource(runtime) except (OSError, TypeError, SyntaxError): return None - if not source.strip(): - # The source code for the module was an empty file, - # no point in parsing it with symtable - return frozenset() - try: - module_symtable = symtable.symtable(source, runtime.__name__, "exec") + return symtable.symtable(source, runtime.__name__, "exec") except SyntaxError: return None - return frozenset(sym.get_name() for sym in module_symtable.get_symbols() if sym.is_imported()) - @verify.register(nodes.MypyFile) def verify_mypyfile( @@ -343,25 +358,37 @@ def verify_mypyfile( if not o.module_hidden and (not is_probably_private(m) or hasattr(runtime, m)) } - imported_symbols = _get_imported_symbol_names(runtime) - def _belongs_to_runtime(r: types.ModuleType, attr: str) -> bool: """Heuristics to determine whether a name originates from another module.""" obj = getattr(r, attr) if isinstance(obj, types.ModuleType): return False - if callable(obj): - # It's highly likely to be a class or a function if it's callable, - # so the __module__ attribute will give a good indication of which module it comes from + + symbol_table = _module_symbol_table(r) + if symbol_table is not None: try: - obj_mod = obj.__module__ - except Exception: + symbol = symbol_table.lookup(attr) + except KeyError: pass else: - if isinstance(obj_mod, str): - return bool(obj_mod == r.__name__) - if imported_symbols is not None: - return attr not in imported_symbols + if symbol.is_imported(): + # symtable says we got this from another module + return False + # But we can't just return True here, because symtable doesn't know about symbols + # that come from `from module import *` + if symbol.is_assigned(): + # symtable knows we assigned this symbol in the module + return True + + # The __module__ attribute is unreliable for anything except functions and classes, + # but it's our best guess at this point + try: + obj_mod = obj.__module__ + except Exception: + pass + else: + if isinstance(obj_mod, str): + return bool(obj_mod == r.__name__) return True runtime_public_contents = ( @@ -419,14 +446,29 @@ class SubClass(runtime): # type: ignore[misc] # Examples: ctypes.Array, ctypes._SimpleCData pass + # Runtime class might be annotated with `@final`: + try: + runtime_final = getattr(runtime, "__final__", False) + except Exception: + runtime_final = False + + if runtime_final and not stub.is_final: + yield Error( + object_path, + "has `__final__` attribute, but isn't marked with @final in the stub", + stub, + runtime, + stub_desc=repr(stub), + ) + def _verify_metaclass( - stub: nodes.TypeInfo, runtime: type[Any], object_path: list[str] + stub: nodes.TypeInfo, runtime: type[Any], object_path: list[str], *, is_runtime_typeddict: bool ) -> Iterator[Error]: # We exclude protocols, because of how complex their implementation is in different versions of - # python. Enums are also hard, ignoring. + # python. Enums are also hard, as are runtime TypedDicts; ignoring. # TODO: check that metaclasses are identical? - if not stub.is_protocol and not stub.is_enum: + if not stub.is_protocol and not stub.is_enum and not is_runtime_typeddict: runtime_metaclass = type(runtime) if runtime_metaclass is not type and stub.metaclass_type is None: # This means that runtime has a custom metaclass, but a stub does not. @@ -462,6 +504,19 @@ def _verify_metaclass( def verify_typeinfo( stub: nodes.TypeInfo, runtime: MaybeMissing[type[Any]], object_path: list[str] ) -> Iterator[Error]: + if stub.is_type_check_only: + # This type only exists in stubs, we only check that the runtime part + # is missing. Other checks are not required. + if not isinstance(runtime, Missing): + yield Error( + object_path, + 'is marked as "@type_check_only", but also exists at runtime', + stub, + runtime, + stub_desc=repr(stub), + ) + return + if isinstance(runtime, Missing): yield Error(object_path, "is not present at runtime", stub, runtime, stub_desc=repr(stub)) return @@ -470,18 +525,22 @@ def verify_typeinfo( return yield from _verify_final(stub, runtime, object_path) - yield from _verify_metaclass(stub, runtime, object_path) + is_runtime_typeddict = stub.typeddict_type is not None and is_typeddict(runtime) + yield from _verify_metaclass( + stub, runtime, object_path, is_runtime_typeddict=is_runtime_typeddict + ) # Check everything already defined on the stub class itself (i.e. not inherited) - to_check = set(stub.names) + # + # Filter out non-identifier names, as these are (hopefully always?) whacky/fictional things + # (like __mypy-replace or __mypy-post_init, etc.) that don't exist at runtime, + # and exist purely for internal mypy reasons + to_check = {name for name in stub.names if name.isidentifier()} # Check all public things on the runtime class to_check.update( - # cast to workaround mypyc complaints - m - for m in cast(Any, vars)(runtime) - if not is_probably_private(m) and m not in IGNORABLE_CLASS_DUNDERS + m for m in vars(runtime) if not is_probably_private(m) and m not in IGNORABLE_CLASS_DUNDERS ) - # Special-case the __init__ method for Protocols + # Special-case the __init__ method for Protocols and the __new__ method for TypedDicts # # TODO: On Python <3.11, __init__ methods on Protocol classes # are silently discarded and replaced. @@ -489,6 +548,8 @@ def verify_typeinfo( # Ideally, we'd figure out a good way of validating Protocol __init__ methods on 3.11+. if stub.is_protocol: to_check.discard("__init__") + if is_runtime_typeddict: + to_check.discard("__new__") for entry in sorted(to_check): mangled_entry = entry @@ -517,8 +578,21 @@ def verify_typeinfo( yield from verify(stub_to_verify, runtime_attr, object_path + [entry]) +def _static_lookup_runtime(object_path: list[str]) -> MaybeMissing[Any]: + static_runtime = importlib.import_module(object_path[0]) + for entry in object_path[1:]: + try: + static_runtime = inspect.getattr_static(static_runtime, entry) + except AttributeError: + # This can happen with mangled names, ignore for now. + # TODO: pass more information about ancestors of nodes/objects to verify, so we don't + # have to do this hacky lookup. Would be useful in several places. + return MISSING + return static_runtime + + def _verify_static_class_methods( - stub: nodes.FuncBase, runtime: Any, object_path: list[str] + stub: nodes.FuncBase, runtime: Any, static_runtime: MaybeMissing[Any], object_path: list[str] ) -> Iterator[str]: if stub.name in ("__new__", "__init_subclass__", "__class_getitem__"): # Special cased by Python, so don't bother checking @@ -533,16 +607,8 @@ def _verify_static_class_methods( yield "stub is a classmethod but runtime is not" return - # Look the object up statically, to avoid binding by the descriptor protocol - static_runtime = importlib.import_module(object_path[0]) - for entry in object_path[1:]: - try: - static_runtime = inspect.getattr_static(static_runtime, entry) - except AttributeError: - # This can happen with mangled names, ignore for now. - # TODO: pass more information about ancestors of nodes/objects to verify, so we don't - # have to do this hacky lookup. Would be useful in a couple other places too. - return + if static_runtime is MISSING: + return if isinstance(static_runtime, classmethod) and not stub.is_class: yield "runtime is a classmethod but stub is not" @@ -568,6 +634,10 @@ def strip_prefix(s: str, prefix: str) -> str: if strip_prefix(stub_arg.variable.name, "__") == runtime_arg.name: return + nonspecific_names = {"object", "args"} + if runtime_arg.name in nonspecific_names: + return + def names_approx_match(a: str, b: str) -> bool: a = a.strip("_") b = b.strip("_") @@ -610,7 +680,7 @@ def _verify_arg_default_value( runtime_type is not None and stub_type is not None # Avoid false positives for marker objects - and type(runtime_arg.default) != object + and type(runtime_arg.default) is not object # And ellipsis and runtime_arg.default is not ... and not is_subtype_helper(runtime_type, stub_type) @@ -625,6 +695,7 @@ def _verify_arg_default_value( if ( stub_default is not UNKNOWN and stub_default is not ... + and runtime_arg.default is not UNREPRESENTABLE and ( stub_default != runtime_arg.default # We want the types to match exactly, e.g. in case the stub has @@ -646,7 +717,7 @@ def _verify_arg_default_value( def maybe_strip_cls(name: str, args: list[nodes.Argument]) -> list[nodes.Argument]: - if name in ("__init_subclass__", "__class_getitem__"): + if args and name in ("__init_subclass__", "__class_getitem__"): # These are implicitly classmethods. If the stub chooses not to have @classmethod, we # should remove the cls argument if args[0].variable.name == "cls": @@ -760,7 +831,10 @@ def from_overloadedfuncdef(stub: nodes.OverloadedFuncDef) -> Signature[nodes.Arg # argument. To accomplish this, we just make up a fake index-based name. name = ( f"__{index}" - if arg.variable.name.startswith("__") or assume_positional_only + if arg.variable.name.startswith("__") + or arg.pos_only + or assume_positional_only + or arg.variable.name.strip("_") == "self" else arg.variable.name ) all_args.setdefault(name, []).append((arg, index)) @@ -804,6 +878,7 @@ def get_kind(arg_name: str) -> nodes.ArgKind: type_annotation=None, initializer=None, kind=get_kind(arg_name), + pos_only=all(arg.pos_only for arg, _ in all_args[arg_name]), ) if arg.kind.is_positional(): sig.pos.append(arg) @@ -829,7 +904,7 @@ def _verify_signature( runtime_arg.kind == inspect.Parameter.POSITIONAL_ONLY and not stub_arg.pos_only and not stub_arg.variable.name.startswith("__") - and not stub_arg.variable.name.strip("_") == "self" + and stub_arg.variable.name.strip("_") != "self" and not is_dunder(function_name, exclude_special=True) # noisy for dunder methods ): yield ( @@ -839,6 +914,7 @@ def _verify_signature( if ( runtime_arg.kind != inspect.Parameter.POSITIONAL_ONLY and (stub_arg.pos_only or stub_arg.variable.name.startswith("__")) + and stub_arg.variable.name.strip("_") != "self" and not is_dunder(function_name, exclude_special=True) # noisy for dunder methods ): yield ( @@ -857,7 +933,10 @@ def _verify_signature( # If the variable is in runtime.kwonly, it's just mislabelled as not a # keyword-only argument if stub_arg.variable.name not in runtime.kwonly: - yield f'runtime does not have argument "{stub_arg.variable.name}"' + msg = f'runtime does not have argument "{stub_arg.variable.name}"' + if runtime.varkw is not None: + msg += ". Maybe you forgot to make it keyword-only in the stub?" + yield msg else: yield f'stub argument "{stub_arg.variable.name}" is not keyword-only' if stub.varpos is not None: @@ -865,7 +944,8 @@ def _verify_signature( elif len(stub.pos) < len(runtime.pos): for runtime_arg in runtime.pos[len(stub.pos) :]: if runtime_arg.name not in stub.kwonly: - yield f'stub does not have argument "{runtime_arg.name}"' + if not _is_private_parameter(runtime_arg): + yield f'stub does not have argument "{runtime_arg.name}"' else: yield f'runtime argument "{runtime_arg.name}" is not keyword-only' @@ -905,7 +985,8 @@ def _verify_signature( ): yield f'stub argument "{arg}" is not keyword-only' else: - yield f'stub does not have argument "{arg}"' + if not _is_private_parameter(runtime.kwonly[arg]): + yield f'stub does not have argument "{arg}"' # Checks involving **kwargs if stub.varkw is None and runtime.varkw is not None: @@ -920,6 +1001,14 @@ def _verify_signature( yield f'runtime does not have **kwargs argument "{stub.varkw.variable.name}"' +def _is_private_parameter(arg: inspect.Parameter) -> bool: + return ( + arg.name.startswith("_") + and not arg.name.startswith("__") + and arg.default is not inspect.Parameter.empty + ) + + @verify.register(nodes.FuncItem) def verify_funcitem( stub: nodes.FuncItem, runtime: MaybeMissing[Any], object_path: list[str] @@ -933,11 +1022,16 @@ def verify_funcitem( if not callable(runtime): return + # Look the object up statically, to avoid binding by the descriptor protocol + static_runtime = _static_lookup_runtime(object_path) + if isinstance(stub, nodes.FuncDef): for error_text in _verify_abstract_status(stub, runtime): yield Error(object_path, error_text, stub, runtime) + for error_text in _verify_final_method(stub, runtime, static_runtime): + yield Error(object_path, error_text, stub, runtime) - for message in _verify_static_class_methods(stub, runtime, object_path): + for message in _verify_static_class_methods(stub, runtime, static_runtime, object_path): yield Error(object_path, "is inconsistent, " + message, stub, runtime) signature = safe_inspect_signature(runtime) @@ -946,7 +1040,7 @@ def verify_funcitem( if signature: stub_sig = Signature.from_funcitem(stub) runtime_sig = Signature.from_inspect_signature(signature) - runtime_sig_desc = f'{"async " if runtime_is_coroutine else ""}def {signature}' + runtime_sig_desc = describe_runtime_callable(signature, is_async=runtime_is_coroutine) stub_desc = str(stub_sig) else: runtime_sig_desc, stub_desc = None, None @@ -1014,6 +1108,13 @@ def verify_var( runtime_type = get_mypy_type_of_runtime_value(runtime.value) if runtime_type is not None and is_subtype_helper(runtime_type, stub.type): should_error = False + # We always allow setting the stub value to ... + proper_type = mypy.types.get_proper_type(stub.type) + if ( + isinstance(proper_type, mypy.types.Instance) + and proper_type.type.fullname == "builtins.ellipsis" + ): + should_error = False if should_error: yield Error( @@ -1025,6 +1126,7 @@ def verify_var( def verify_overloadedfuncdef( stub: nodes.OverloadedFuncDef, runtime: MaybeMissing[Any], object_path: list[str] ) -> Iterator[Error]: + # TODO: support `@type_check_only` decorator if isinstance(runtime, Missing): yield Error(object_path, "is not present at runtime", stub, runtime) return @@ -1040,9 +1142,26 @@ def verify_overloadedfuncdef( if not callable(runtime): return - for message in _verify_static_class_methods(stub, runtime, object_path): + # mypy doesn't allow overloads where one overload is abstract but another isn't, + # so it should be okay to just check whether the first overload is abstract or not. + # + # TODO: Mypy *does* allow properties where e.g. the getter is abstract but the setter is not; + # and any property with a setter is represented as an OverloadedFuncDef internally; + # not sure exactly what (if anything) we should do about that. + first_part = stub.items[0] + if isinstance(first_part, nodes.Decorator) and first_part.is_overload: + for msg in _verify_abstract_status(first_part.func, runtime): + yield Error(object_path, msg, stub, runtime) + + # Look the object up statically, to avoid binding by the descriptor protocol + static_runtime = _static_lookup_runtime(object_path) + + for message in _verify_static_class_methods(stub, runtime, static_runtime, object_path): yield Error(object_path, "is inconsistent, " + message, stub, runtime) + # TODO: Should call _verify_final_method here, + # but overloaded final methods in stubs cause a stubtest crash: see #14950 + signature = safe_inspect_signature(runtime) if not signature: return @@ -1062,7 +1181,7 @@ def verify_overloadedfuncdef( "is inconsistent, " + message, stub, runtime, - stub_desc=str(stub.type) + f"\nInferred signature: {stub_sig}", + stub_desc=(str(stub.type)) + f"\nInferred signature: {stub_sig}", runtime_desc="def " + str(signature), ) @@ -1103,6 +1222,7 @@ def verify_paramspecexpr( def _verify_readonly_property(stub: nodes.Decorator, runtime: Any) -> Iterator[str]: assert stub.func.is_property if isinstance(runtime, property): + yield from _verify_final_method(stub.func, runtime.fget, MISSING) return if inspect.isdatadescriptor(runtime): # It's enough like a property... @@ -1131,6 +1251,17 @@ def _verify_abstract_status(stub: nodes.FuncDef, runtime: Any) -> Iterator[str]: yield f"is inconsistent, runtime {item_type} is abstract but stub is not" +def _verify_final_method( + stub: nodes.FuncDef, runtime: Any, static_runtime: MaybeMissing[Any] +) -> Iterator[str]: + if stub.is_final: + return + if getattr(runtime, "__final__", False) or ( + static_runtime is not MISSING and getattr(static_runtime, "__final__", False) + ): + yield "is decorated with @final at runtime, but not in the stub" + + def _resolve_funcitem_from_decorator(dec: nodes.OverloadPart) -> nodes.FuncItem | None: """Returns a FuncItem that corresponds to the output of the decorator. @@ -1145,6 +1276,12 @@ def _resolve_funcitem_from_decorator(dec: nodes.OverloadPart) -> nodes.FuncItem def apply_decorator_to_funcitem( decorator: nodes.Expression, func: nodes.FuncItem ) -> nodes.FuncItem | None: + if ( + isinstance(decorator, nodes.CallExpr) + and isinstance(decorator.callee, nodes.RefExpr) + and decorator.callee.fullname in mypy.types.DEPRECATED_TYPE_NAMES + ): + return func if not isinstance(decorator, nodes.RefExpr): return None if not decorator.fullname: @@ -1153,6 +1290,7 @@ def apply_decorator_to_funcitem( if ( decorator.fullname in ("builtins.staticmethod", "abc.abstractmethod") or decorator.fullname in mypy.types.OVERLOAD_NAMES + or decorator.fullname in mypy.types.FINAL_DECORATOR_NAMES ): return func if decorator.fullname == "builtins.classmethod": @@ -1183,6 +1321,19 @@ def apply_decorator_to_funcitem( def verify_decorator( stub: nodes.Decorator, runtime: MaybeMissing[Any], object_path: list[str] ) -> Iterator[Error]: + if stub.func.is_type_check_only: + # This function only exists in stubs, we only check that the runtime part + # is missing. Other checks are not required. + if not isinstance(runtime, Missing): + yield Error( + object_path, + 'is marked as "@type_check_only", but also exists at runtime', + stub, + runtime, + stub_desc=repr(stub), + ) + return + if isinstance(runtime, Missing): yield Error(object_path, "is not present at runtime", stub, runtime) return @@ -1304,11 +1455,12 @@ def verify_typealias( "__annotations__", "__text_signature__", "__weakref__", - "__del__", # Only ever called when an object is being deleted, who cares? "__hash__", "__getattr__", # resulting behaviour might be typed explicitly "__setattr__", # defining this on a class can cause worse type checking "__vectorcalloffset__", # undocumented implementation detail of the vectorcall protocol + "__firstlineno__", + "__static_attributes__", # isinstance/issubclass hooks that type-checkers don't usually care about "__instancecheck__", "__subclasshook__", @@ -1337,12 +1489,16 @@ def verify_typealias( "__dataclass_fields__", # Generated by dataclasses "__dataclass_params__", # Generated by dataclasses "__doc__", # mypy's semanal for namedtuples assumes this is str, not Optional[str] + # Added to all protocol classes on 3.12+ (or if using typing_extensions.Protocol) + "__protocol_attrs__", + "__callable_proto_members_only__", + "__non_callable_proto_members__", # typing implementation details, consider removing some of these: "__parameters__", "__origin__", "__args__", "__orig_bases__", - "__final__", + "__final__", # Has a specialized check # Consider removing __slots__? "__slots__", } @@ -1367,7 +1523,29 @@ def is_read_only_property(runtime: object) -> bool: def safe_inspect_signature(runtime: Any) -> inspect.Signature | None: try: - return inspect.signature(runtime) + try: + return inspect.signature(runtime) + except ValueError: + if ( + hasattr(runtime, "__text_signature__") + and "" in runtime.__text_signature__ + ): + # Try to fix up the signature. Workaround for + # https://github.com/python/cpython/issues/87233 + sig = runtime.__text_signature__.replace("", "...") + sig = inspect._signature_fromstr(inspect.Signature, runtime, sig) # type: ignore[attr-defined] + assert isinstance(sig, inspect.Signature) + new_params = [ + ( + parameter.replace(default=UNREPRESENTABLE) + if parameter.default is ... + else parameter + ) + for parameter in sig.parameters.values() + ] + return sig.replace(parameters=new_params) + else: + raise except Exception: # inspect.signature throws ValueError all the time # catch RuntimeError because of https://bugs.python.org/issue39504 @@ -1376,6 +1554,10 @@ def safe_inspect_signature(runtime: Any) -> inspect.Signature | None: return None +def describe_runtime_callable(signature: inspect.Signature, *, is_async: bool) -> str: + return f'{"async " if is_async else ""}def {signature}' + + def is_subtype_helper(left: mypy.types.Type, right: mypy.types.Type) -> bool: """Checks whether ``left`` is a subtype of ``right``.""" left = mypy.types.get_proper_type(left) @@ -1483,10 +1665,10 @@ def anytype() -> mypy.types.AnyType: fallback = mypy.types.Instance(type_info, [anytype() for _ in type_info.type_vars]) value: bool | int | str - if isinstance(runtime, bytes): - value = bytes_to_human_readable_repr(runtime) - elif isinstance(runtime, enum.Enum): + if isinstance(runtime, enum.Enum) and isinstance(runtime.name, str): value = runtime.name + elif isinstance(runtime, bytes): + value = bytes_to_human_readable_repr(runtime) elif isinstance(runtime, (bool, int, str)): value = runtime else: @@ -1571,7 +1753,7 @@ def get_stub(module: str) -> nodes.MypyFile | None: def get_typeshed_stdlib_modules( custom_typeshed_dir: str | None, version_info: tuple[int, int] | None = None -) -> list[str]: +) -> set[str]: """Returns a list of stdlib modules in typeshed (for current Python version).""" stdlib_py_versions = mypy.modulefinder.load_stdlib_py_versions(custom_typeshed_dir) if version_info is None: @@ -1593,14 +1775,91 @@ def exists_in_version(module: str) -> bool: typeshed_dir = Path(mypy.build.default_data_dir()) / "typeshed" stdlib_dir = typeshed_dir / "stdlib" - modules = [] + modules: set[str] = set() for path in stdlib_dir.rglob("*.pyi"): if path.stem == "__init__": path = path.parent module = ".".join(path.relative_to(stdlib_dir).parts[:-1] + (path.stem,)) if exists_in_version(module): - modules.append(module) - return sorted(modules) + modules.add(module) + return modules + + +def get_importable_stdlib_modules() -> set[str]: + """Return all importable stdlib modules at runtime.""" + all_stdlib_modules: AbstractSet[str] + if sys.version_info >= (3, 10): + all_stdlib_modules = sys.stdlib_module_names + else: + all_stdlib_modules = set(sys.builtin_module_names) + modules_by_finder: defaultdict[importlib.machinery.FileFinder, set[str]] = defaultdict(set) + for m in pkgutil.iter_modules(): + if isinstance(m.module_finder, importlib.machinery.FileFinder): + modules_by_finder[m.module_finder].add(m.name) + for finder, module_group in modules_by_finder.items(): + if ( + "site-packages" not in Path(finder.path).parts + # if "_queue" is present, it's most likely the module finder + # for stdlib extension modules; + # if "queue" is present, it's most likely the module finder + # for pure-Python stdlib modules. + # In either case, we'll want to add all the modules that the finder has to offer us. + # This is a bit hacky, but seems to work well in a cross-platform way. + and {"_queue", "queue"} & module_group + ): + all_stdlib_modules.update(module_group) + + importable_stdlib_modules: set[str] = set() + for module_name in all_stdlib_modules: + if module_name in ANNOYING_STDLIB_MODULES: + continue + + try: + runtime = silent_import_module(module_name) + except ImportError: + continue + else: + importable_stdlib_modules.add(module_name) + + try: + # some stdlib modules (e.g. `nt`) don't have __path__ set... + runtime_path = runtime.__path__ + runtime_name = runtime.__name__ + except AttributeError: + continue + + for submodule in pkgutil.walk_packages(runtime_path, runtime_name + "."): + submodule_name = submodule.name + + # There are many annoying *.__main__ stdlib modules, + # and including stubs for them isn't really that useful anyway: + # tkinter.__main__ opens a tkinter windows; unittest.__main__ raises SystemExit; etc. + # + # The idlelib.* submodules are similarly annoying in opening random tkinter windows, + # and we're unlikely to ever add stubs for idlelib in typeshed + # (see discussion in https://github.com/python/typeshed/pull/9193) + # + # test.* modules do weird things like raising exceptions in __del__ methods, + # leading to unraisable exceptions being logged to the terminal + # as a warning at the end of the stubtest run + if submodule_name.endswith(".__main__") or submodule_name.startswith( + ("idlelib.", "test.") + ): + continue + + try: + silent_import_module(submodule_name) + except KeyboardInterrupt: + raise + # importing multiprocessing.popen_forkserver on Windows raises AttributeError... + # some submodules also appear to raise SystemExit as well on some Python versions + # (not sure exactly which) + except BaseException: + continue + else: + importable_stdlib_modules.add(submodule_name) + + return importable_stdlib_modules def get_allowlist_entries(allowlist_file: str) -> Iterator[str]: @@ -1611,7 +1870,7 @@ def strip_comments(s: str) -> str: return s.strip() with open(allowlist_file) as f: - for line in f.readlines(): + for line in f: entry = strip_comments(line) if entry: yield entry @@ -1625,12 +1884,16 @@ class _Arguments: allowlist: list[str] generate_allowlist: bool ignore_unused_allowlist: bool - mypy_config_file: str - custom_typeshed_dir: str + mypy_config_file: str | None + custom_typeshed_dir: str | None check_typeshed: bool version: str +# typeshed added a stub for __main__, but that causes stubtest to check itself +ANNOYING_STDLIB_MODULES: typing_extensions.Final = frozenset({"antigravity", "this", "__main__"}) + + def test_stubs(args: _Arguments, use_builtins_fixtures: bool = False) -> int: """This is stubtest! It's time to test the stubs!""" # Load the allowlist. This is a series of strings corresponding to Error.object_desc @@ -1653,10 +1916,9 @@ def test_stubs(args: _Arguments, use_builtins_fixtures: bool = False) -> int: "cannot pass both --check-typeshed and a list of modules", ) return 1 - modules = get_typeshed_stdlib_modules(args.custom_typeshed_dir) - # typeshed added a stub for __main__, but that causes stubtest to check itself - annoying_modules = {"antigravity", "this", "__main__"} - modules = [m for m in modules if m not in annoying_modules] + typeshed_modules = get_typeshed_stdlib_modules(args.custom_typeshed_dir) + runtime_modules = get_importable_stdlib_modules() + modules = sorted((typeshed_modules | runtime_modules) - ANNOYING_STDLIB_MODULES) if not modules: print(_style("error:", color="red", bold=True), "no modules to check") @@ -1666,7 +1928,7 @@ def test_stubs(args: _Arguments, use_builtins_fixtures: bool = False) -> int: options.incremental = False options.custom_typeshed_dir = args.custom_typeshed_dir if options.custom_typeshed_dir: - options.abs_custom_typeshed_dir = os.path.abspath(args.custom_typeshed_dir) + options.abs_custom_typeshed_dir = os.path.abspath(options.custom_typeshed_dir) options.config_file = args.mypy_config_file options.use_builtins_fixtures = use_builtins_fixtures diff --git a/mypy/stubutil.py b/mypy/stubutil.py index e15766b66cb3..8e41d6862531 100644 --- a/mypy/stubutil.py +++ b/mypy/stubutil.py @@ -5,19 +5,55 @@ import os.path import re import sys +from abc import abstractmethod +from collections import defaultdict from contextlib import contextmanager -from typing import Iterator +from typing import Final, Iterable, Iterator, Mapping from typing_extensions import overload +from mypy_extensions import mypyc_attr + +import mypy.options from mypy.modulefinder import ModuleNotFoundReason from mypy.moduleinspect import InspectError, ModuleInspect +from mypy.stubdoc import ArgSig, FunctionSig +from mypy.types import ( + AnyType, + NoneType, + RawExpressionType, + Type, + TypeList, + TypeStrVisitor, + UnboundType, + UnionType, +) # Modules that may fail when imported, or that may have side effects (fully qualified). NOT_IMPORTABLE_MODULES = () +# Typing constructs to be replaced by their builtin equivalents. +TYPING_BUILTIN_REPLACEMENTS: Final = { + # From typing + "typing.Text": "builtins.str", + "typing.Tuple": "builtins.tuple", + "typing.List": "builtins.list", + "typing.Dict": "builtins.dict", + "typing.Set": "builtins.set", + "typing.FrozenSet": "builtins.frozenset", + "typing.Type": "builtins.type", + # From typing_extensions + "typing_extensions.Text": "builtins.str", + "typing_extensions.Tuple": "builtins.tuple", + "typing_extensions.List": "builtins.list", + "typing_extensions.Dict": "builtins.dict", + "typing_extensions.Set": "builtins.set", + "typing_extensions.FrozenSet": "builtins.frozenset", + "typing_extensions.Type": "builtins.type", +} + class CantImport(Exception): - def __init__(self, module: str, message: str): + def __init__(self, module: str, message: str) -> None: self.module = module self.message = message @@ -70,8 +106,9 @@ def find_module_path_and_all_py3( ) -> tuple[str | None, list[str] | None] | None: """Find module and determine __all__ for a Python 3 module. - Return None if the module is a C module. Return (module_path, __all__) if - it is a Python module. Raise CantImport if import failed. + Return None if the module is a C or pyc-only module. + Return (module_path, __all__) if it is a Python module. + Raise CantImport if import failed. """ if module in NOT_IMPORTABLE_MODULES: raise CantImport(module, "") @@ -132,13 +169,11 @@ def fail_missing(mod: str, reason: ModuleNotFoundReason) -> None: @overload -def remove_misplaced_type_comments(source: bytes) -> bytes: - ... +def remove_misplaced_type_comments(source: bytes) -> bytes: ... @overload -def remove_misplaced_type_comments(source: str) -> str: - ... +def remove_misplaced_type_comments(source: str) -> str: ... def remove_misplaced_type_comments(source: str | bytes) -> str | bytes: @@ -182,3 +217,626 @@ def common_dir_prefix(paths: list[str]) -> str: cur = path break return cur or "." + + +class AnnotationPrinter(TypeStrVisitor): + """Visitor used to print existing annotations in a file. + + The main difference from TypeStrVisitor is a better treatment of + unbound types. + + Notes: + * This visitor doesn't add imports necessary for annotations, this is done separately + by ImportTracker. + * It can print all kinds of types, but the generated strings may not be valid (notably + callable types) since it prints the same string that reveal_type() does. + * For Instance types it prints the fully qualified names. + """ + + # TODO: Generate valid string representation for callable types. + # TODO: Use short names for Instances. + def __init__( + self, + stubgen: BaseStubGenerator, + known_modules: list[str] | None = None, + local_modules: list[str] | None = None, + ) -> None: + super().__init__(options=mypy.options.Options()) + self.stubgen = stubgen + self.known_modules = known_modules + self.local_modules = local_modules or ["builtins"] + + def visit_any(self, t: AnyType) -> str: + s = super().visit_any(t) + self.stubgen.import_tracker.require_name(s) + return s + + def visit_unbound_type(self, t: UnboundType) -> str: + s = t.name + fullname = self.stubgen.resolve_name(s) + if fullname == "typing.Union": + return " | ".join([item.accept(self) for item in t.args]) + if fullname == "typing.Optional": + return f"{t.args[0].accept(self)} | None" + if fullname in TYPING_BUILTIN_REPLACEMENTS: + s = self.stubgen.add_name(TYPING_BUILTIN_REPLACEMENTS[fullname], require=True) + if self.known_modules is not None and "." in s: + # see if this object is from any of the modules that we're currently processing. + # reverse sort so that subpackages come before parents: e.g. "foo.bar" before "foo". + for module_name in self.local_modules + sorted(self.known_modules, reverse=True): + if s.startswith(module_name + "."): + if module_name in self.local_modules: + s = s[len(module_name) + 1 :] + arg_module = module_name + break + else: + arg_module = s[: s.rindex(".")] + if arg_module not in self.local_modules: + self.stubgen.import_tracker.add_import(arg_module, require=True) + elif s == "NoneType": + # when called without analysis all types are unbound, so this won't hit + # visit_none_type(). + s = "None" + else: + self.stubgen.import_tracker.require_name(s) + if t.args: + s += f"[{self.args_str(t.args)}]" + elif t.empty_tuple_index: + s += "[()]" + return s + + def visit_none_type(self, t: NoneType) -> str: + return "None" + + def visit_type_list(self, t: TypeList) -> str: + return f"[{self.list_str(t.items)}]" + + def visit_union_type(self, t: UnionType) -> str: + return " | ".join([item.accept(self) for item in t.items]) + + def args_str(self, args: Iterable[Type]) -> str: + """Convert an array of arguments to strings and join the results with commas. + + The main difference from list_str is the preservation of quotes for string + arguments + """ + res = [] + for arg in args: + arg_str = arg.accept(self) + if isinstance(arg, RawExpressionType): + res.append(repr(arg.literal_value)) + else: + res.append(arg_str) + return ", ".join(res) + + +class ClassInfo: + def __init__( + self, name: str, self_var: str, docstring: str | None = None, cls: type | None = None + ) -> None: + self.name = name + self.self_var = self_var + self.docstring = docstring + self.cls = cls + + +class FunctionContext: + def __init__( + self, + module_name: str, + name: str, + docstring: str | None = None, + is_abstract: bool = False, + class_info: ClassInfo | None = None, + ) -> None: + self.module_name = module_name + self.name = name + self.docstring = docstring + self.is_abstract = is_abstract + self.class_info = class_info + self._fullname: str | None = None + + @property + def fullname(self) -> str: + if self._fullname is None: + if self.class_info: + self._fullname = f"{self.module_name}.{self.class_info.name}.{self.name}" + else: + self._fullname = f"{self.module_name}.{self.name}" + return self._fullname + + +def infer_method_ret_type(name: str) -> str | None: + """Infer return types for known special methods""" + if name.startswith("__") and name.endswith("__"): + name = name[2:-2] + if name in ("float", "bool", "bytes", "int", "complex", "str"): + return name + # Note: __eq__ and co may return arbitrary types, but bool is good enough for stubgen. + elif name in ("eq", "ne", "lt", "le", "gt", "ge", "contains"): + return "bool" + elif name in ("len", "length_hint", "index", "hash", "sizeof", "trunc", "floor", "ceil"): + return "int" + elif name in ("format", "repr"): + return "str" + elif name in ("init", "setitem", "del", "delitem"): + return "None" + return None + + +def infer_method_arg_types( + name: str, self_var: str = "self", arg_names: list[str] | None = None +) -> list[ArgSig] | None: + """Infer argument types for known special methods""" + args: list[ArgSig] | None = None + if name.startswith("__") and name.endswith("__"): + if arg_names and len(arg_names) >= 1 and arg_names[0] == "self": + arg_names = arg_names[1:] + + name = name[2:-2] + if name == "exit": + if arg_names is None: + arg_names = ["type", "value", "traceback"] + if len(arg_names) == 3: + arg_types = [ + "type[BaseException] | None", + "BaseException | None", + "types.TracebackType | None", + ] + args = [ + ArgSig(name=arg_name, type=arg_type) + for arg_name, arg_type in zip(arg_names, arg_types) + ] + if args is not None: + return [ArgSig(name=self_var)] + args + return None + + +@mypyc_attr(allow_interpreted_subclasses=True) +class SignatureGenerator: + """Abstract base class for extracting a list of FunctionSigs for each function.""" + + def remove_self_type( + self, inferred: list[FunctionSig] | None, self_var: str + ) -> list[FunctionSig] | None: + """Remove type annotation from self/cls argument""" + if inferred: + for signature in inferred: + if signature.args: + if signature.args[0].name == self_var: + signature.args[0].type = None + return inferred + + @abstractmethod + def get_function_sig( + self, default_sig: FunctionSig, ctx: FunctionContext + ) -> list[FunctionSig] | None: + """Return a list of signatures for the given function. + + If no signature can be found, return None. If all of the registered SignatureGenerators + for the stub generator return None, then the default_sig will be used. + """ + pass + + @abstractmethod + def get_property_type(self, default_type: str | None, ctx: FunctionContext) -> str | None: + """Return the type of the given property""" + pass + + +class ImportTracker: + """Record necessary imports during stub generation.""" + + def __init__(self) -> None: + # module_for['foo'] has the module name where 'foo' was imported from, or None if + # 'foo' is a module imported directly; + # direct_imports['foo'] is the module path used when the name 'foo' was added to the + # namespace. + # reverse_alias['foo'] is the name that 'foo' had originally when imported with an + # alias; examples + # 'from pkg import mod' ==> module_for['mod'] == 'pkg' + # 'from pkg import mod as m' ==> module_for['m'] == 'pkg' + # ==> reverse_alias['m'] == 'mod' + # 'import pkg.mod as m' ==> module_for['m'] == None + # ==> reverse_alias['m'] == 'pkg.mod' + # 'import pkg.mod' ==> module_for['pkg'] == None + # ==> module_for['pkg.mod'] == None + # ==> direct_imports['pkg'] == 'pkg.mod' + # ==> direct_imports['pkg.mod'] == 'pkg.mod' + self.module_for: dict[str, str | None] = {} + self.direct_imports: dict[str, str] = {} + self.reverse_alias: dict[str, str] = {} + + # required_names is the set of names that are actually used in a type annotation + self.required_names: set[str] = set() + + # Names that should be reexported if they come from another module + self.reexports: set[str] = set() + + def add_import_from( + self, module: str, names: list[tuple[str, str | None]], require: bool = False + ) -> None: + for name, alias in names: + if alias: + # 'from {module} import {name} as {alias}' + self.module_for[alias] = module + self.reverse_alias[alias] = name + else: + # 'from {module} import {name}' + self.module_for[name] = module + self.reverse_alias.pop(name, None) + if require: + self.require_name(alias or name) + self.direct_imports.pop(alias or name, None) + + def add_import(self, module: str, alias: str | None = None, require: bool = False) -> None: + if alias: + # 'import {module} as {alias}' + assert "." not in alias # invalid syntax + self.module_for[alias] = None + self.reverse_alias[alias] = module + if require: + self.required_names.add(alias) + else: + # 'import {module}' + name = module + if require: + self.required_names.add(name) + # add module and its parent packages + while name: + self.module_for[name] = None + self.direct_imports[name] = module + self.reverse_alias.pop(name, None) + name = name.rpartition(".")[0] + + def require_name(self, name: str) -> None: + while name not in self.direct_imports and "." in name: + name = name.rsplit(".", 1)[0] + self.required_names.add(name) + + def reexport(self, name: str) -> None: + """Mark a given non qualified name as needed in __all__. + + This means that in case it comes from a module, it should be + imported with an alias even if the alias is the same as the name. + """ + self.require_name(name) + self.reexports.add(name) + + def import_lines(self) -> list[str]: + """The list of required import lines (as strings with python code). + + In order for a module be included in this output, an identifier must be both + 'required' via require_name() and 'imported' via add_import_from() + or add_import() + """ + result = [] + + # To summarize multiple names imported from a same module, we collect those + # in the `module_map` dictionary, mapping a module path to the list of names that should + # be imported from it. the names can also be alias in the form 'original as alias' + module_map: Mapping[str, list[str]] = defaultdict(list) + + for name in sorted( + self.required_names, + key=lambda n: (self.reverse_alias[n], n) if n in self.reverse_alias else (n, ""), + ): + # If we haven't seen this name in an import statement, ignore it + if name not in self.module_for: + continue + + m = self.module_for[name] + if m is not None: + # This name was found in a from ... import ... + # Collect the name in the module_map + if name in self.reverse_alias: + name = f"{self.reverse_alias[name]} as {name}" + elif name in self.reexports: + name = f"{name} as {name}" + module_map[m].append(name) + else: + # This name was found in an import ... + # We can already generate the import line + if name in self.reverse_alias: + source = self.reverse_alias[name] + result.append(f"import {source} as {name}\n") + elif name in self.reexports: + assert "." not in name # Because reexports only has nonqualified names + result.append(f"import {name} as {name}\n") + else: + result.append(f"import {name}\n") + + # Now generate all the from ... import ... lines collected in module_map + for module, names in sorted(module_map.items()): + result.append(f"from {module} import {', '.join(sorted(names))}\n") + return result + + +@mypyc_attr(allow_interpreted_subclasses=True) +class BaseStubGenerator: + # These names should be omitted from generated stubs. + IGNORED_DUNDERS: Final = { + "__all__", + "__author__", + "__about__", + "__copyright__", + "__email__", + "__license__", + "__summary__", + "__title__", + "__uri__", + "__str__", + "__repr__", + "__getstate__", + "__setstate__", + "__slots__", + "__builtins__", + "__cached__", + "__file__", + "__name__", + "__package__", + "__path__", + "__spec__", + "__loader__", + } + TYPING_MODULE_NAMES: Final = ("typing", "typing_extensions") + # Special-cased names that are implicitly exported from the stub (from m import y as y). + EXTRA_EXPORTED: Final = { + "pyasn1_modules.rfc2437.univ", + "pyasn1_modules.rfc2459.char", + "pyasn1_modules.rfc2459.univ", + } + + def __init__( + self, + _all_: list[str] | None = None, + include_private: bool = False, + export_less: bool = False, + include_docstrings: bool = False, + ) -> None: + # Best known value of __all__. + self._all_ = _all_ + self._include_private = include_private + self._include_docstrings = include_docstrings + # Disable implicit exports of package-internal imports? + self.export_less = export_less + self._import_lines: list[str] = [] + self._output: list[str] = [] + # Current indent level (indent is hardcoded to 4 spaces). + self._indent = "" + self._toplevel_names: list[str] = [] + self.import_tracker = ImportTracker() + # Top-level members + self.defined_names: set[str] = set() + self.sig_generators = self.get_sig_generators() + # populated by visit_mypy_file + self.module_name: str = "" + # These are "soft" imports for objects which might appear in annotations but not have + # a corresponding import statement. + self.known_imports = { + "_typeshed": ["Incomplete"], + "typing": ["Any", "TypeVar", "NamedTuple", "TypedDict"], + "collections.abc": ["Generator"], + "typing_extensions": ["ParamSpec", "TypeVarTuple"], + } + + def get_sig_generators(self) -> list[SignatureGenerator]: + return [] + + def resolve_name(self, name: str) -> str: + """Return the full name resolving imports and import aliases.""" + if "." not in name: + real_module = self.import_tracker.module_for.get(name) + real_short = self.import_tracker.reverse_alias.get(name, name) + if real_module is None and real_short not in self.defined_names: + real_module = "builtins" # not imported and not defined, must be a builtin + else: + name_module, real_short = name.split(".", 1) + real_module = self.import_tracker.reverse_alias.get(name_module, name_module) + resolved_name = real_short if real_module is None else f"{real_module}.{real_short}" + return resolved_name + + def add_name(self, fullname: str, require: bool = True) -> str: + """Add a name to be imported and return the name reference. + + The import will be internal to the stub (i.e don't reexport). + """ + module, name = fullname.rsplit(".", 1) + alias = "_" + name if name in self.defined_names else None + while alias in self.defined_names: + alias = "_" + alias + if module != "builtins" or alias: # don't import from builtins unless needed + self.import_tracker.add_import_from(module, [(name, alias)], require=require) + return alias or name + + def add_import_line(self, line: str) -> None: + """Add a line of text to the import section, unless it's already there.""" + if line not in self._import_lines: + self._import_lines.append(line) + + def get_imports(self) -> str: + """Return the import statements for the stub.""" + imports = "" + if self._import_lines: + imports += "".join(self._import_lines) + imports += "".join(self.import_tracker.import_lines()) + return imports + + def output(self) -> str: + """Return the text for the stub.""" + pieces: list[str] = [] + if imports := self.get_imports(): + pieces.append(imports) + if dunder_all := self.get_dunder_all(): + pieces.append(dunder_all) + if self._output: + pieces.append("".join(self._output)) + return "\n".join(pieces) + + def get_dunder_all(self) -> str: + """Return the __all__ list for the stub.""" + if self._all_: + # Note we emit all names in the runtime __all__ here, even if they + # don't actually exist. If that happens, the runtime has a bug, and + # it's not obvious what the correct behavior should be. We choose + # to reflect the runtime __all__ as closely as possible. + return f"__all__ = {self._all_!r}\n" + return "" + + def add(self, string: str) -> None: + """Add text to generated stub.""" + self._output.append(string) + + def is_top_level(self) -> bool: + """Are we processing the top level of a file?""" + return self._indent == "" + + def indent(self) -> None: + """Add one level of indentation.""" + self._indent += " " + + def dedent(self) -> None: + """Remove one level of indentation.""" + self._indent = self._indent[:-4] + + def record_name(self, name: str) -> None: + """Mark a name as defined. + + This only does anything if at the top level of a module. + """ + if self.is_top_level(): + self._toplevel_names.append(name) + + def is_recorded_name(self, name: str) -> bool: + """Has this name been recorded previously?""" + return self.is_top_level() and name in self._toplevel_names + + def set_defined_names(self, defined_names: set[str]) -> None: + self.defined_names = defined_names + # Names in __all__ are required + for name in self._all_ or (): + self.import_tracker.reexport(name) + + for pkg, imports in self.known_imports.items(): + for t in imports: + # require=False means that the import won't be added unless require_name() is called + # for the object during generation. + self.add_name(f"{pkg}.{t}", require=False) + + def check_undefined_names(self) -> None: + undefined_names = [name for name in self._all_ or [] if name not in self._toplevel_names] + if undefined_names: + if self._output: + self.add("\n") + self.add("# Names in __all__ with no definition:\n") + for name in sorted(undefined_names): + self.add(f"# {name}\n") + + def get_signatures( + self, + default_signature: FunctionSig, + sig_generators: list[SignatureGenerator], + func_ctx: FunctionContext, + ) -> list[FunctionSig]: + for sig_gen in sig_generators: + inferred = sig_gen.get_function_sig(default_signature, func_ctx) + if inferred: + return inferred + + return [default_signature] + + def get_property_type( + self, + default_type: str | None, + sig_generators: list[SignatureGenerator], + func_ctx: FunctionContext, + ) -> str | None: + for sig_gen in sig_generators: + inferred = sig_gen.get_property_type(default_type, func_ctx) + if inferred: + return inferred + + return default_type + + def format_func_def( + self, + sigs: list[FunctionSig], + is_coroutine: bool = False, + decorators: list[str] | None = None, + docstring: str | None = None, + ) -> list[str]: + lines: list[str] = [] + if decorators is None: + decorators = [] + + for signature in sigs: + # dump decorators, just before "def ..." + for deco in decorators: + lines.append(f"{self._indent}{deco}") + + lines.append( + signature.format_sig( + indent=self._indent, + is_async=is_coroutine, + docstring=docstring if self._include_docstrings else None, + ) + ) + return lines + + def print_annotation( + self, + t: Type, + known_modules: list[str] | None = None, + local_modules: list[str] | None = None, + ) -> str: + printer = AnnotationPrinter(self, known_modules, local_modules) + return t.accept(printer) + + def is_not_in_all(self, name: str) -> bool: + if self.is_private_name(name): + return False + if self._all_: + return self.is_top_level() and name not in self._all_ + return False + + def is_private_name(self, name: str, fullname: str | None = None) -> bool: + if self._include_private: + return False + if fullname in self.EXTRA_EXPORTED: + return False + if name == "_": + return False + if not name.startswith("_"): + return False + if self._all_ and name in self._all_: + return False + if name.startswith("__") and name.endswith("__"): + return name in self.IGNORED_DUNDERS + return True + + def should_reexport(self, name: str, full_module: str, name_is_alias: bool) -> bool: + if ( + not name_is_alias + and self.module_name + and (self.module_name + "." + name) in self.EXTRA_EXPORTED + ): + # Special case certain names that should be exported, against our general rules. + return True + if name_is_alias: + return False + if self.export_less: + return False + if not self.module_name: + return False + is_private = self.is_private_name(name, full_module + "." + name) + if is_private: + return False + top_level = full_module.split(".")[0] + self_top_level = self.module_name.split(".", 1)[0] + if top_level not in (self_top_level, "_" + self_top_level): + # Export imports from the same package, since we can't reliably tell whether they + # are part of the public API. + return False + if self._all_: + return name in self._all_ + return True diff --git a/mypy/subtypes.py b/mypy/subtypes.py index c3d5517d43dd..a5523fbe0d45 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1,14 +1,14 @@ from __future__ import annotations from contextlib import contextmanager -from typing import Any, Callable, Iterator, List, TypeVar, cast -from typing_extensions import Final, TypeAlias as _TypeAlias +from typing import Any, Callable, Final, Iterator, List, TypeVar, cast +from typing_extensions import TypeAlias as _TypeAlias import mypy.applytype import mypy.constraints import mypy.typeops from mypy.erasetype import erase_type -from mypy.expandtype import expand_self_type, expand_type_by_instance +from mypy.expandtype import expand_self_type, expand_type, expand_type_by_instance from mypy.maptype import map_instance_to_supertype # Circular import; done in the function instead. @@ -18,6 +18,8 @@ ARG_STAR2, CONTRAVARIANT, COVARIANT, + INVARIANT, + VARIANCE_NOT_READY, Decorator, FuncBase, OverloadedFuncDef, @@ -58,13 +60,14 @@ UninhabitedType, UnionType, UnpackType, - _flattened, + find_unpack_in_list, get_proper_type, is_named_instance, + split_with_prefix_and_suffix, ) +from mypy.types_utils import flatten_types from mypy.typestate import SubtypeKind, type_state -from mypy.typevars import fill_typevars_with_any -from mypy.typevartuples import extract_unpack, fully_split_with_mapped_and_template +from mypy.typevars import fill_typevars, fill_typevars_with_any # Flags for detected protocol members IS_SETTABLE: Final = 1 @@ -256,6 +259,18 @@ def is_same_type( This means types may have different representation (e.g. an alias, or a non-simplified union) but are semantically exchangeable in all contexts. """ + # First, use fast path for some common types. This is performance-critical. + if ( + type(a) is Instance + and type(b) is Instance + and a.type == b.type + and len(a.args) == len(b.args) + and a.last_known_value is b.last_known_value + ): + return all(is_same_type(x, y) for x, y in zip(a.args, b.args)) + elif isinstance(a, TypeVarType) and isinstance(b, TypeVarType) and a.id == b.id: + return True + # Note that using ignore_promotions=True (default) makes types like int and int64 # considered not the same type (which is the case at runtime). # Also Union[bool, int] (if it wasn't simplified before) will be different @@ -278,10 +293,12 @@ def _is_subtype( left = get_proper_type(left) right = get_proper_type(right) - if not proper_subtype and ( - isinstance(right, AnyType) - or isinstance(right, UnboundType) - or isinstance(right, ErasedType) + # Note: Unpack type should not be a subtype of Any, since it may represent + # multiple types. This should always go through the visitor, to check arity. + if ( + not proper_subtype + and isinstance(right, (AnyType, UnboundType, ErasedType)) + and not isinstance(left, UnpackType) ): # TODO: should we consider all types proper subtypes of UnboundType and/or # ErasedType as we do for non-proper subtyping. @@ -339,7 +356,16 @@ def _is_subtype( def check_type_parameter( left: Type, right: Type, variance: int, proper_subtype: bool, subtype_context: SubtypeContext ) -> bool: - if variance == COVARIANT: + # It is safe to consider empty collection literals and similar as covariant, since + # such type can't be stored in a variable, see checker.is_valid_inferred_type(). + if variance == INVARIANT: + p_left = get_proper_type(left) + if isinstance(p_left, UninhabitedType) and p_left.ambiguous: + variance = COVARIANT + # If variance hasn't been inferred yet, we are lenient and default to + # covariance. This shouldn't happen often, but it's very difficult to + # avoid these cases altogether. + if variance == COVARIANT or variance == VARIANCE_NOT_READY: if proper_subtype: return is_proper_subtype(left, right, subtype_context=subtype_context) else: @@ -388,8 +414,7 @@ def _is_subtype(self, left: Type, right: Type) -> bool: return is_proper_subtype(left, right, subtype_context=self.subtype_context) return is_subtype(left, right, subtype_context=self.subtype_context) - # visit_x(left) means: is left (which is an instance of X) a subtype of - # right? + # visit_x(left) means: is left (which is an instance of X) a subtype of right? def visit_unbound_type(self, left: UnboundType) -> bool: # This can be called if there is a bad type annotation. The result probably @@ -440,11 +465,46 @@ def visit_instance(self, left: Instance) -> bool: # dynamic base classes correctly, see #5456. return not isinstance(self.right, NoneType) right = self.right - if isinstance(right, TupleType) and mypy.typeops.tuple_fallback(right).type.is_enum: + if isinstance(right, TupleType) and right.partial_fallback.type.is_enum: return self._is_subtype(left, mypy.typeops.tuple_fallback(right)) + if isinstance(right, TupleType): + if len(right.items) == 1: + # Non-normalized Tuple type (may be left after semantic analysis + # because semanal_typearg visitor is not a type translator). + item = right.items[0] + if isinstance(item, UnpackType): + unpacked = get_proper_type(item.type) + if isinstance(unpacked, Instance): + return self._is_subtype(left, unpacked) + if left.type.has_base(right.partial_fallback.type.fullname): + if not self.proper_subtype: + # Special case to consider Foo[*tuple[Any, ...]] (i.e. bare Foo) a + # subtype of Foo[], when Foo is user defined variadic tuple type. + mapped = map_instance_to_supertype(left, right.partial_fallback.type) + for arg in map(get_proper_type, mapped.args): + if isinstance(arg, UnpackType): + unpacked = get_proper_type(arg.type) + if not isinstance(unpacked, Instance): + break + assert unpacked.type.fullname == "builtins.tuple" + if not isinstance(get_proper_type(unpacked.args[0]), AnyType): + break + elif not isinstance(arg, AnyType): + break + else: + return True + return False + if isinstance(right, TypeVarTupleType): + # tuple[Any, ...] is like Any in the world of tuples (see special case above). + if left.type.has_base("builtins.tuple"): + mapped = map_instance_to_supertype(left, right.tuple_fallback.type) + if isinstance(get_proper_type(mapped.args[0]), AnyType): + return not self.proper_subtype if isinstance(right, Instance): if type_state.is_cached_subtype_check(self._subtype_kind, left, right): return True + if type_state.is_cached_negative_subtype_check(self._subtype_kind, left, right): + return False if not self.subtype_context.ignore_promotions: for base in left.type.mro: if base._promote and any( @@ -479,111 +539,52 @@ def visit_instance(self, left: Instance) -> bool: t = erased nominal = True if right.type.has_type_var_tuple_type: - assert left.type.type_var_tuple_prefix is not None - assert left.type.type_var_tuple_suffix is not None + # For variadic instances we simply find the correct type argument mappings, + # all the heavy lifting is done by the tuple subtyping. assert right.type.type_var_tuple_prefix is not None assert right.type.type_var_tuple_suffix is not None - split_result = fully_split_with_mapped_and_template( - left.args, - left.type.type_var_tuple_prefix, - left.type.type_var_tuple_suffix, - right.args, - right.type.type_var_tuple_prefix, - right.type.type_var_tuple_suffix, + prefix = right.type.type_var_tuple_prefix + suffix = right.type.type_var_tuple_suffix + tvt = right.type.defn.type_vars[prefix] + assert isinstance(tvt, TypeVarTupleType) + fallback = tvt.tuple_fallback + left_prefix, left_middle, left_suffix = split_with_prefix_and_suffix( + t.args, prefix, suffix ) - if split_result is None: - return False - - ( - left_prefix, - left_mprefix, - left_middle, - left_msuffix, - left_suffix, - right_prefix, - right_mprefix, - right_middle, - right_msuffix, - right_suffix, - ) = split_result - - left_unpacked = extract_unpack(left_middle) - right_unpacked = extract_unpack(right_middle) - - # Helper for case 2 below so we can treat them the same. - def check_mixed( - unpacked_type: ProperType, compare_to: tuple[Type, ...] - ) -> bool: - if ( - isinstance(unpacked_type, Instance) - and unpacked_type.type.fullname == "builtins.tuple" - ): - return all(is_equivalent(l, unpacked_type.args[0]) for l in compare_to) - if isinstance(unpacked_type, TypeVarTupleType): - return False - if isinstance(unpacked_type, AnyType): - return True - if isinstance(unpacked_type, TupleType): - if len(unpacked_type.items) != len(compare_to): - return False - for t1, t2 in zip(unpacked_type.items, compare_to): - if not is_equivalent(t1, t2): - return False + right_prefix, right_middle, right_suffix = split_with_prefix_and_suffix( + right.args, prefix, suffix + ) + left_args = ( + left_prefix + (TupleType(list(left_middle), fallback),) + left_suffix + ) + right_args = ( + right_prefix + (TupleType(list(right_middle), fallback),) + right_suffix + ) + if not self.proper_subtype and t.args: + for arg in map(get_proper_type, t.args): + if isinstance(arg, UnpackType): + unpacked = get_proper_type(arg.type) + if not isinstance(unpacked, Instance): + break + assert unpacked.type.fullname == "builtins.tuple" + if not isinstance(get_proper_type(unpacked.args[0]), AnyType): + break + elif not isinstance(arg, AnyType): + break + else: return True + if len(left_args) != len(right_args): return False - - # Case 1: Both are unpacks, in this case we check what is being - # unpacked is the same. - if left_unpacked is not None and right_unpacked is not None: - if not is_equivalent(left_unpacked, right_unpacked): - return False - - # Case 2: Only one of the types is an unpack. The equivalence - # case is mostly the same but we check some additional - # things when unpacking on the right. - elif left_unpacked is not None and right_unpacked is None: - if not check_mixed(left_unpacked, right_middle): - return False - elif left_unpacked is None and right_unpacked is not None: - if not check_mixed(right_unpacked, left_middle): - return False - - # Case 3: Neither type is an unpack. In this case we just compare - # the items themselves. - else: - if len(left_middle) != len(right_middle): - return False - for left_t, right_t in zip(left_middle, right_middle): - if not is_equivalent(left_t, right_t): - return False - - assert len(left_mprefix) == len(right_mprefix) - assert len(left_msuffix) == len(right_msuffix) - - for left_item, right_item in zip( - left_mprefix + left_msuffix, right_mprefix + right_msuffix - ): - if not is_equivalent(left_item, right_item): - return False - - left_items = t.args[: right.type.type_var_tuple_prefix] - right_items = right.args[: right.type.type_var_tuple_prefix] - if right.type.type_var_tuple_suffix: - left_items += t.args[-right.type.type_var_tuple_suffix :] - right_items += right.args[-right.type.type_var_tuple_suffix :] - unpack_index = right.type.type_var_tuple_prefix - assert unpack_index is not None - type_params = zip( - left_prefix + left_suffix, - right_prefix + right_suffix, - right.type.defn.type_vars[:unpack_index] - + right.type.defn.type_vars[unpack_index + 1 :], - ) + type_params = zip(left_args, right_args, right.type.defn.type_vars) else: type_params = zip(t.args, right.args, right.type.defn.type_vars) if not self.subtype_context.ignore_type_params: + tried_infer = False for lefta, righta, tvar in type_params: if isinstance(tvar, TypeVarType): + if tvar.variance == VARIANCE_NOT_READY and not tried_infer: + infer_class_variances(right.type) + tried_infer = True if not check_type_parameter( lefta, righta, @@ -593,17 +594,24 @@ def check_mixed( ): nominal = False else: + # TODO: everywhere else ParamSpecs are handled as invariant. if not check_type_parameter( lefta, righta, COVARIANT, self.proper_subtype, self.subtype_context ): nominal = False if nominal: type_state.record_subtype_cache_entry(self._subtype_kind, left, right) + else: + type_state.record_negative_subtype_cache_entry(self._subtype_kind, left, right) return nominal if right.type.is_protocol and is_protocol_implementation( - left, right, proper_subtype=self.proper_subtype + left, right, proper_subtype=self.proper_subtype, options=self.options ): return True + # We record negative cache entry here, and not in the protocol check like we do for + # positive cache, to avoid accidentally adding a type that is not a structural + # subtype, but is a nominal subtype (involving type: ignore override). + type_state.record_negative_subtype_cache_entry(self._subtype_kind, left, right) return False if isinstance(right, TypeType): item = right.item @@ -644,7 +652,7 @@ def visit_param_spec(self, left: ParamSpecType) -> bool: and right.id == left.id and right.flavor == left.flavor ): - return True + return self._is_subtype(left.prefix, right.prefix) if isinstance(right, Parameters) and are_trivial_parameters(right): return True return self._is_subtype(left.upper_bound, self.right) @@ -652,23 +660,26 @@ def visit_param_spec(self, left: ParamSpecType) -> bool: def visit_type_var_tuple(self, left: TypeVarTupleType) -> bool: right = self.right if isinstance(right, TypeVarTupleType) and right.id == left.id: - return True + return left.min_len >= right.min_len return self._is_subtype(left.upper_bound, self.right) def visit_unpack_type(self, left: UnpackType) -> bool: + # TODO: Ideally we should not need this (since it is not a real type). + # Instead callers (upper level types) should handle it when it appears in type list. if isinstance(self.right, UnpackType): return self._is_subtype(left.type, self.right.type) + if isinstance(self.right, Instance) and self.right.type.fullname == "builtins.object": + return True return False def visit_parameters(self, left: Parameters) -> bool: - if isinstance(self.right, Parameters) or isinstance(self.right, CallableType): - right = self.right - if isinstance(right, CallableType): - right = right.with_unpacked_kwargs() + if isinstance(self.right, Parameters): return are_parameters_compatible( left, - right, + self.right, is_compat=self._is_subtype, + # TODO: this should pass the current value, but then couple tests fail. + is_proper_subtype=False, ignore_pos_arg_names=self.subtype_context.ignore_pos_arg_names, ) else: @@ -680,16 +691,34 @@ def visit_callable_type(self, left: CallableType) -> bool: if left.type_guard is not None and right.type_guard is not None: if not self._is_subtype(left.type_guard, right.type_guard): return False + elif left.type_is is not None and right.type_is is not None: + # For TypeIs we have to check both ways; it is unsafe to pass + # a TypeIs[Child] when a TypeIs[Parent] is expected, because + # if the narrower returns False, we assume that the narrowed value is + # *not* a Parent. + if not self._is_subtype(left.type_is, right.type_is) or not self._is_subtype( + right.type_is, left.type_is + ): + return False elif right.type_guard is not None and left.type_guard is None: # This means that one function has `TypeGuard` and other does not. # They are not compatible. See https://github.com/python/mypy/issues/11307 return False + elif right.type_is is not None and left.type_is is None: + # Similarly, if one function has `TypeIs` and the other does not, + # they are not compatible. + return False return is_callable_compatible( left, right, is_compat=self._is_subtype, + is_proper_subtype=self.proper_subtype, ignore_pos_arg_names=self.subtype_context.ignore_pos_arg_names, - strict_concatenate=self.options.strict_concatenate if self.options else True, + strict_concatenate=( + (self.options.extra_checks or self.options.strict_concatenate) + if self.options + else False + ), ) elif isinstance(right, Overloaded): return all(self._is_subtype(left, item) for item in right.items) @@ -716,14 +745,6 @@ def visit_callable_type(self, left: CallableType) -> bool: elif isinstance(right, TypeType): # This is unsound, we don't check the __init__ signature. return left.is_type_obj() and self._is_subtype(left.ret_type, right.item) - elif isinstance(right, Parameters): - # this doesn't check return types.... but is needed for is_equivalent - return are_parameters_compatible( - left.with_unpacked_kwargs(), - right, - is_compat=self._is_subtype, - ignore_pos_arg_names=self.subtype_context.ignore_pos_arg_names, - ) else: return False @@ -745,11 +766,30 @@ def visit_tuple_type(self, left: TupleType) -> bool: # TODO: We shouldn't need this special case. This is currently needed # for isinstance(x, tuple), though it's unclear why. return True - return all(self._is_subtype(li, iter_type) for li in left.items) - elif self._is_subtype(mypy.typeops.tuple_fallback(left), right): + for li in left.items: + if isinstance(li, UnpackType): + unpack = get_proper_type(li.type) + if isinstance(unpack, TypeVarTupleType): + unpack = get_proper_type(unpack.upper_bound) + assert ( + isinstance(unpack, Instance) + and unpack.type.fullname == "builtins.tuple" + ) + li = unpack.args[0] + if not self._is_subtype(li, iter_type): + return False + return True + elif self._is_subtype(left.partial_fallback, right) and self._is_subtype( + mypy.typeops.tuple_fallback(left), right + ): return True return False elif isinstance(right, TupleType): + # If right has a variadic unpack this needs special handling. If there is a TypeVarTuple + # unpack, item count must coincide. If the left has variadic unpack but right + # doesn't have one, we will fall through to False down the line. + if self.variadic_tuple_subtype(left, right): + return True if len(left.items) != len(right.items): return False if any(not self._is_subtype(l, r) for l, r in zip(left.items, right.items)): @@ -766,6 +806,79 @@ def visit_tuple_type(self, left: TupleType) -> bool: else: return False + def variadic_tuple_subtype(self, left: TupleType, right: TupleType) -> bool: + """Check subtyping between two potentially variadic tuples. + + Most non-trivial cases here are due to variadic unpacks like *tuple[X, ...], + we handle such unpacks as infinite unions Tuple[()] | Tuple[X] | Tuple[X, X] | ... + + Note: the cases where right is fixed or has *Ts unpack should be handled + by the caller. + """ + right_unpack_index = find_unpack_in_list(right.items) + if right_unpack_index is None: + # This case should be handled by the caller. + return False + right_unpack = right.items[right_unpack_index] + assert isinstance(right_unpack, UnpackType) + right_unpacked = get_proper_type(right_unpack.type) + if not isinstance(right_unpacked, Instance): + # This case should be handled by the caller. + return False + assert right_unpacked.type.fullname == "builtins.tuple" + right_item = right_unpacked.args[0] + right_prefix = right_unpack_index + right_suffix = len(right.items) - right_prefix - 1 + left_unpack_index = find_unpack_in_list(left.items) + if left_unpack_index is None: + # Simple case: left is fixed, simply find correct mapping to the right + # (effectively selecting item with matching length from an infinite union). + if len(left.items) < right_prefix + right_suffix: + return False + prefix, middle, suffix = split_with_prefix_and_suffix( + tuple(left.items), right_prefix, right_suffix + ) + if not all( + self._is_subtype(li, ri) for li, ri in zip(prefix, right.items[:right_prefix]) + ): + return False + if right_suffix and not all( + self._is_subtype(li, ri) for li, ri in zip(suffix, right.items[-right_suffix:]) + ): + return False + return all(self._is_subtype(li, right_item) for li in middle) + else: + if len(left.items) < len(right.items): + # There are some items on the left that will never have a matching length + # on the right. + return False + left_unpack = left.items[left_unpack_index] + assert isinstance(left_unpack, UnpackType) + left_unpacked = get_proper_type(left_unpack.type) + if not isinstance(left_unpacked, Instance): + # *Ts unpacks can't be split. + return False + assert left_unpacked.type.fullname == "builtins.tuple" + left_item = left_unpacked.args[0] + + # The most tricky case with two variadic unpacks we handle similar to union + # subtyping: *each* item on the left, must be a subtype of *some* item on the right. + # For this we first check the "asymptotic case", i.e. that both unpacks a subtypes, + # and then check subtyping for all finite overlaps. + if not self._is_subtype(left_item, right_item): + return False + left_prefix = left_unpack_index + left_suffix = len(left.items) - left_prefix - 1 + max_overlap = max(0, right_prefix - left_prefix, right_suffix - left_suffix) + for overlap in range(max_overlap + 1): + repr_items = left.items[:left_prefix] + [left_item] * overlap + if left_suffix: + repr_items += left.items[-left_suffix:] + left_repr = left.copy_modified(items=repr_items) + if not self._is_subtype(left_repr, right): + return False + return True + def visit_typeddict_type(self, left: TypedDictType) -> bool: right = self.right if isinstance(right, Instance): @@ -851,12 +964,17 @@ def visit_overloaded(self, left: Overloaded) -> bool: else: # If this one overlaps with the supertype in any way, but it wasn't # an exact match, then it's a potential error. - strict_concat = self.options.strict_concatenate if self.options else True + strict_concat = ( + (self.options.extra_checks or self.options.strict_concatenate) + if self.options + else False + ) if left_index not in matched_overloads and ( is_callable_compatible( left_item, right_item, is_compat=self._is_subtype, + is_proper_subtype=self.proper_subtype, ignore_return=True, ignore_pos_arg_names=self.subtype_context.ignore_pos_arg_names, strict_concatenate=strict_concat, @@ -865,6 +983,7 @@ def visit_overloaded(self, left: Overloaded) -> bool: right_item, left_item, is_compat=self._is_subtype, + is_proper_subtype=self.proper_subtype, ignore_return=True, ignore_pos_arg_names=self.subtype_context.ignore_pos_arg_names, strict_concatenate=strict_concat, @@ -908,15 +1027,11 @@ def visit_union_type(self, left: UnionType) -> bool: fast_check: set[ProperType] = set() - for item in _flattened(self.right.relevant_items()): + for item in flatten_types(self.right.relevant_items()): p_item = get_proper_type(item) - if isinstance(p_item, LiteralType): - fast_check.add(p_item) - elif isinstance(p_item, Instance): - if p_item.last_known_value is None: - fast_check.add(p_item) - else: - fast_check.add(p_item.last_known_value) + fast_check.add(p_item) + if isinstance(p_item, Instance) and p_item.last_known_value is not None: + fast_check.add(p_item.last_known_value) for item in left.relevant_items(): p_item = get_proper_type(item) @@ -994,6 +1109,7 @@ def is_protocol_implementation( proper_subtype: bool = False, class_obj: bool = False, skip: list[str] | None = None, + options: Options | None = None, ) -> bool: """Check whether 'left' implements the protocol 'right'. @@ -1027,7 +1143,7 @@ def f(self) -> A: ... if not members_right.issubset(members_left): return False assuming = right.type.assuming_proper if proper_subtype else right.type.assuming - for (l, r) in reversed(assuming): + for l, r in reversed(assuming): if l == left and r == right: return True with pop_on_exit(assuming, left, right): @@ -1039,23 +1155,8 @@ def f(self) -> A: ... # We always bind self to the subtype. (Similarly to nominal types). supertype = get_proper_type(find_member(member, right, left)) assert supertype is not None - if member == "__call__" and class_obj: - # Special case: class objects always have __call__ that is just the constructor. - # TODO: move this helper function to typeops.py? - import mypy.checkmember - - def named_type(fullname: str) -> Instance: - return Instance(left.type.mro[-1], []) - subtype: ProperType | None = mypy.checkmember.type_object_type( - left.type, named_type - ) - elif member == "__call__" and left.type.is_metaclass(): - # Special case: we want to avoid falling back to metaclass __call__ - # if constructor signature didn't match, this can cause many false negatives. - subtype = None - else: - subtype = get_proper_type(find_member(member, left, left, class_obj=class_obj)) + subtype = mypy.typeops.get_protocol_member(left, member, class_obj) # Useful for debugging: # print(member, 'of', left, 'has type', subtype) # print(member, 'of', right, 'has type', supertype) @@ -1074,7 +1175,9 @@ def named_type(fullname: str) -> Instance: # Nominal check currently ignores arg names # NOTE: If we ever change this, be sure to also change the call to # SubtypeVisitor.build_subtype_kind(...) down below. - is_compat = is_subtype(subtype, supertype, ignore_pos_arg_names=ignore_names) + is_compat = is_subtype( + subtype, supertype, ignore_pos_arg_names=ignore_names, options=options + ) else: is_compat = is_proper_subtype(subtype, supertype) if not is_compat: @@ -1086,7 +1189,7 @@ def named_type(fullname: str) -> Instance: superflags = get_member_flags(member, right) if IS_SETTABLE in superflags: # Check opposite direction for settable attributes. - if not is_subtype(supertype, subtype): + if not is_subtype(supertype, subtype, options=options): return False if not class_obj: if IS_SETTABLE not in superflags: @@ -1299,12 +1402,14 @@ def is_callable_compatible( right: CallableType, *, is_compat: Callable[[Type, Type], bool], + is_proper_subtype: bool, is_compat_return: Callable[[Type, Type], bool] | None = None, ignore_return: bool = False, ignore_pos_arg_names: bool = False, check_args_covariantly: bool = False, allow_partial_overlap: bool = False, strict_concatenate: bool = False, + no_unify_none: bool = False, ) -> bool: """Is the left compatible with the right, using the provided compatibility check? @@ -1395,8 +1500,8 @@ def g(x: int) -> int: ... whether or not we check the args covariantly. """ # Normalize both types before comparing them. - left = left.with_unpacked_kwargs() - right = right.with_unpacked_kwargs() + left = left.with_unpacked_kwargs().with_normalized_var_args() + right = right.with_unpacked_kwargs().with_normalized_var_args() if is_compat_return is None: is_compat_return = is_compat @@ -1421,7 +1526,9 @@ def g(x: int) -> int: ... # (below) treats type variables on the two sides as independent. if left.variables: # Apply generic type variables away in left via type inference. - unified = unify_generic_callable(left, right, ignore_return=ignore_return) + unified = unify_generic_callable( + left, right, ignore_return=ignore_return, no_unify_none=no_unify_none + ) if unified is None: return False left = unified @@ -1433,7 +1540,9 @@ def g(x: int) -> int: ... # So, we repeat the above checks in the opposite direction. This also # lets us preserve the 'symmetry' property of allow_partial_overlap. if allow_partial_overlap and right.variables: - unified = unify_generic_callable(right, left, ignore_return=ignore_return) + unified = unify_generic_callable( + right, left, ignore_return=ignore_return, no_unify_none=no_unify_none + ) if unified is not None: right = unified @@ -1453,8 +1562,8 @@ def g(x: int) -> int: ... left, right, is_compat=is_compat, + is_proper_subtype=is_proper_subtype, ignore_pos_arg_names=ignore_pos_arg_names, - check_args_covariantly=check_args_covariantly, allow_partial_overlap=allow_partial_overlap, strict_concatenate_check=strict_concatenate_check, ) @@ -1472,18 +1581,30 @@ def are_trivial_parameters(param: Parameters | NormalizedCallableType) -> bool: ) +def is_trivial_suffix(param: Parameters | NormalizedCallableType) -> bool: + param_star = param.var_arg() + param_star2 = param.kw_arg() + return ( + param.arg_kinds[-2:] == [ARG_STAR, ARG_STAR2] + and param_star is not None + and isinstance(get_proper_type(param_star.typ), AnyType) + and param_star2 is not None + and isinstance(get_proper_type(param_star2.typ), AnyType) + ) + + def are_parameters_compatible( left: Parameters | NormalizedCallableType, right: Parameters | NormalizedCallableType, *, is_compat: Callable[[Type, Type], bool], + is_proper_subtype: bool, ignore_pos_arg_names: bool = False, - check_args_covariantly: bool = False, allow_partial_overlap: bool = False, - strict_concatenate_check: bool = True, + strict_concatenate_check: bool = False, ) -> bool: """Helper function for is_callable_compatible, used for Parameter compatibility""" - if right.is_ellipsis_args: + if right.is_ellipsis_args and not is_proper_subtype: return True left_star = left.var_arg() @@ -1492,8 +1613,21 @@ def are_parameters_compatible( right_star2 = right.kw_arg() # Treat "def _(*a: Any, **kw: Any) -> X" similarly to "Callable[..., X]" - if are_trivial_parameters(right): + if are_trivial_parameters(right) and not is_proper_subtype: return True + trivial_suffix = is_trivial_suffix(right) and not is_proper_subtype + + if ( + right.arg_kinds == [ARG_STAR] + and isinstance(get_proper_type(right.arg_types[0]), AnyType) + and not is_proper_subtype + ): + # Similar to how (*Any, **Any) is considered a supertype of all callables, we consider + # (*Any) a supertype of all callables with positional arguments. This is needed in + # particular because we often refuse to try type inference if actual type is not + # a subtype of erased template type. + if all(k.is_positional() for k in left.arg_kinds) and ignore_pos_arg_names: + return True # Match up corresponding arguments and check them for compatibility. In # every pair (argL, argR) of corresponding arguments from L and R, argL must @@ -1524,7 +1658,7 @@ def _incompatible(left_arg: FormalArgument | None, right_arg: FormalArgument | N if right_arg is None: return False if left_arg is None: - return not allow_partial_overlap + return not allow_partial_overlap and not trivial_suffix return not is_compat(right_arg.typ, left_arg.typ) if _incompatible(left_star, right_star) or _incompatible(left_star2, right_star2): @@ -1532,7 +1666,7 @@ def _incompatible(left_arg: FormalArgument | None, right_arg: FormalArgument | N # Phase 1b: Check non-star args: for every arg right can accept, left must # also accept. The only exception is if we are allowing partial - # partial overlaps: in that case, we ignore optional args on the right. + # overlaps: in that case, we ignore optional args on the right. for right_arg in right.formal_arguments(): left_arg = mypy.typeops.callable_corresponding_argument(left, right_arg) if left_arg is None: @@ -1540,14 +1674,20 @@ def _incompatible(left_arg: FormalArgument | None, right_arg: FormalArgument | N continue return False if not are_args_compatible( - left_arg, right_arg, ignore_pos_arg_names, allow_partial_overlap, is_compat + left_arg, + right_arg, + is_compat, + ignore_pos_arg_names=ignore_pos_arg_names, + allow_partial_overlap=allow_partial_overlap, + allow_imprecise_kinds=right.imprecise_arg_kinds, ): return False # Phase 1c: Check var args. Right has an infinite series of optional positional # arguments. Get all further positional args of left, and make sure - # they're more general then the corresponding member in right. - if right_star is not None: + # they're more general than the corresponding member in right. + # TODO: are we handling UnpackType correctly here? + if right_star is not None and not trivial_suffix: # Synthesize an anonymous formal argument for the right right_by_position = right.try_synthesizing_arg_from_vararg(None) assert right_by_position is not None @@ -1564,17 +1704,17 @@ def _incompatible(left_arg: FormalArgument | None, right_arg: FormalArgument | N if not are_args_compatible( left_by_position, right_by_position, - ignore_pos_arg_names, - allow_partial_overlap, is_compat, + ignore_pos_arg_names=ignore_pos_arg_names, + allow_partial_overlap=allow_partial_overlap, ): return False i += 1 # Phase 1d: Check kw args. Right has an infinite series of optional named # arguments. Get all further named args of left, and make sure - # they're more general then the corresponding member in right. - if right_star2 is not None: + # they're more general than the corresponding member in right. + if right_star2 is not None and not trivial_suffix: right_names = {name for name in right.arg_names if name is not None} left_only_names = set() for name, kind in zip(left.arg_names, left.arg_kinds): @@ -1599,7 +1739,11 @@ def _incompatible(left_arg: FormalArgument | None, right_arg: FormalArgument | N continue if not are_args_compatible( - left_by_name, right_by_name, ignore_pos_arg_names, allow_partial_overlap, is_compat + left_by_name, + right_by_name, + is_compat, + ignore_pos_arg_names=ignore_pos_arg_names, + allow_partial_overlap=allow_partial_overlap, ): return False @@ -1623,6 +1767,7 @@ def _incompatible(left_arg: FormalArgument | None, right_arg: FormalArgument | N and right_by_name != right_by_pos and (right_by_pos.required or right_by_name.required) and strict_concatenate_check + and not right.imprecise_arg_kinds ): return False @@ -1637,10 +1782,16 @@ def _incompatible(left_arg: FormalArgument | None, right_arg: FormalArgument | N def are_args_compatible( left: FormalArgument, right: FormalArgument, + is_compat: Callable[[Type, Type], bool], + *, ignore_pos_arg_names: bool, allow_partial_overlap: bool, - is_compat: Callable[[Type, Type], bool], + allow_imprecise_kinds: bool = False, ) -> bool: + if left.required and right.required: + # If both arguments are required allow_partial_overlap has no effect. + allow_partial_overlap = False + def is_different(left_item: object | None, right_item: object | None) -> bool: """Checks if the left and right items are different. @@ -1663,12 +1814,12 @@ def is_different(left_item: object | None, right_item: object | None) -> bool: return False # If right is at a specific position, left must have the same: - if is_different(left.pos, right.pos): + if is_different(left.pos, right.pos) and not allow_imprecise_kinds: return False # If right's argument is optional, left's must also be # (unless we're relaxing the checks to allow potential - # rather then definite compatibility). + # rather than definite compatibility). if not allow_partial_overlap and not right.required and left.required: return False @@ -1693,6 +1844,8 @@ def unify_generic_callable( target: NormalizedCallableType, ignore_return: bool, return_constraint_direction: int | None = None, + *, + no_unify_none: bool = False, ) -> NormalizedCallableType | None: """Try to unify a generic callable type with another callable type. @@ -1704,18 +1857,25 @@ def unify_generic_callable( return_constraint_direction = mypy.constraints.SUBTYPE_OF constraints: list[mypy.constraints.Constraint] = [] - for arg_type, target_arg_type in zip(type.arg_types, target.arg_types): - c = mypy.constraints.infer_constraints( - arg_type, target_arg_type, mypy.constraints.SUPERTYPE_OF - ) - constraints.extend(c) + # There is some special logic for inference in callables, so better use them + # as wholes instead of picking separate arguments. + cs = mypy.constraints.infer_constraints( + type.copy_modified(ret_type=UninhabitedType()), + target.copy_modified(ret_type=UninhabitedType()), + mypy.constraints.SUBTYPE_OF, + skip_neg_op=True, + ) + constraints.extend(cs) if not ignore_return: c = mypy.constraints.infer_constraints( type.ret_type, target.ret_type, return_constraint_direction ) constraints.extend(c) - type_var_ids = [tvar.id for tvar in type.variables] - inferred_vars = mypy.solve.solve_constraints(type_var_ids, constraints) + if no_unify_none: + constraints = [ + c for c in constraints if not isinstance(get_proper_type(c.target), NoneType) + ] + inferred_vars, _ = mypy.solve.solve_constraints(type.variables, constraints) if None in inferred_vars: return None non_none_inferred_vars = cast(List[Type], inferred_vars) @@ -1730,7 +1890,7 @@ def report(*args: Any) -> None: # (probably also because solver needs subtyping). See also comment in # ExpandTypeVisitor.visit_erased_type(). applied = mypy.applytype.apply_generic_arguments( - type, non_none_inferred_vars, report, context=target, allow_erased_callables=True + type, non_none_inferred_vars, report, context=target ) if had_errors: return None @@ -1803,6 +1963,9 @@ def covers_at_runtime(item: Type, supertype: Type) -> bool: # Special case useful for selecting TypedDicts from unions using isinstance(x, dict). if supertype.type.fullname == "builtins.dict": return True + elif isinstance(item, TypeVarType): + if is_proper_subtype(item.upper_bound, supertype, ignore_promotions=True): + return True elif isinstance(item, Instance) and supertype.type.fullname == "builtins.int": # "int" covers all native int types if item.type.fullname in MYPYC_NATIVE_INT_NAMES: @@ -1823,3 +1986,72 @@ def is_more_precise(left: Type, right: Type, *, ignore_promotions: bool = False) if isinstance(right, AnyType): return True return is_proper_subtype(left, right, ignore_promotions=ignore_promotions) + + +def all_non_object_members(info: TypeInfo) -> set[str]: + members = set(info.names) + for base in info.mro[1:-1]: + members.update(base.names) + return members + + +def infer_variance(info: TypeInfo, i: int) -> bool: + """Infer the variance of the ith type variable of a generic class. + + Return True if successful. This can fail if some inferred types aren't ready. + """ + object_type = Instance(info.mro[-1], []) + + for variance in COVARIANT, CONTRAVARIANT, INVARIANT: + tv = info.defn.type_vars[i] + assert isinstance(tv, TypeVarType) + if tv.variance != VARIANCE_NOT_READY: + continue + tv.variance = variance + co = True + contra = True + tvar = info.defn.type_vars[i] + self_type = fill_typevars(info) + for member in all_non_object_members(info): + if member in ("__init__", "__new__"): + continue + node = info[member].node + if isinstance(node, Var) and node.type is None: + tv.variance = VARIANCE_NOT_READY + return False + if isinstance(self_type, TupleType): + self_type = mypy.typeops.tuple_fallback(self_type) + + flags = get_member_flags(member, self_type) + typ = find_member(member, self_type, self_type) + settable = IS_SETTABLE in flags + if typ: + typ2 = expand_type(typ, {tvar.id: object_type}) + if not is_subtype(typ, typ2): + co = False + if not is_subtype(typ2, typ): + contra = False + if settable: + co = False + if co: + v = COVARIANT + elif contra: + v = CONTRAVARIANT + else: + v = INVARIANT + if v == variance: + break + tv.variance = VARIANCE_NOT_READY + return True + + +def infer_class_variances(info: TypeInfo) -> bool: + if not info.defn.type_args: + return True + tvs = info.defn.type_vars + success = True + for i, tv in enumerate(tvs): + if isinstance(tv, TypeVarType) and tv.variance == VARIANCE_NOT_READY: + if not infer_variance(info, i): + success = False + return success diff --git a/mypy/suggestions.py b/mypy/suggestions.py index 9ac033ba3bdf..268f3032fc9b 100644 --- a/mypy/suggestions.py +++ b/mypy/suggestions.py @@ -54,6 +54,7 @@ TypeInfo, reverse_builtin_aliases, ) +from mypy.options import Options from mypy.plugin import FunctionContext, MethodContext, Plugin from mypy.server.update import FineGrainedBuildManager from mypy.state import state @@ -77,9 +78,8 @@ UninhabitedType, UnionType, get_proper_type, - is_optional, - remove_optional, ) +from mypy.types_utils import is_overlapping_none, remove_optional from mypy.util import split_target @@ -735,7 +735,7 @@ def format_signature(self, sig: PyAnnotateSignature) -> str: def format_type(self, cur_module: str | None, typ: Type) -> str: if self.use_fixme and isinstance(get_proper_type(typ), AnyType): return self.use_fixme - return typ.accept(TypeFormatter(cur_module, self.graph)) + return typ.accept(TypeFormatter(cur_module, self.graph, self.manager.options)) def score_type(self, t: Type, arg_pos: bool) -> int: """Generate a score for a type that we use to pick which type to use. @@ -752,7 +752,7 @@ def score_type(self, t: Type, arg_pos: bool) -> int: return 20 if any(has_any_type(x) for x in t.items): return 15 - if not is_optional(t): + if not is_overlapping_none(t): return 10 if isinstance(t, CallableType) and (has_any_type(t) or is_tricky_callable(t)): return 10 @@ -809,8 +809,8 @@ class TypeFormatter(TypeStrVisitor): """Visitor used to format types""" # TODO: Probably a lot - def __init__(self, module: str | None, graph: Graph) -> None: - super().__init__() + def __init__(self, module: str | None, graph: Graph, options: Options) -> None: + super().__init__(options=options) self.module = module self.graph = graph @@ -868,7 +868,7 @@ def visit_typeddict_type(self, t: TypedDictType) -> str: return t.fallback.accept(self) def visit_union_type(self, t: UnionType) -> str: - if len(t.items) == 2 and is_optional(t): + if len(t.items) == 2 and is_overlapping_none(t): return f"Optional[{remove_optional(t).accept(self)}]" else: return super().visit_union_type(t) diff --git a/mypy/test/data.py b/mypy/test/data.py index 535ebf304784..32f6354cc162 100644 --- a/mypy/test/data.py +++ b/mypy/test/data.py @@ -10,11 +10,14 @@ import sys import tempfile from abc import abstractmethod -from typing import Any, Iterator, NamedTuple, Pattern, Union -from typing_extensions import Final, TypeAlias as _TypeAlias +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Final, Iterator, NamedTuple, NoReturn, Pattern, Union +from typing_extensions import TypeAlias as _TypeAlias import pytest +from mypy import defaults from mypy.test.config import PREFIX, test_data_prefix, test_temp_dir root_dir = os.path.normpath(PREFIX) @@ -41,6 +44,14 @@ class DeleteFile(NamedTuple): FileOperation: _TypeAlias = Union[UpdateFile, DeleteFile] +def _file_arg_to_module(filename: str) -> str: + filename, _ = os.path.splitext(filename) + parts = filename.split("/") # not os.sep since it comes from test data + if parts[-1] == "__init__": + parts.pop() + return ".".join(parts) + + def parse_test_case(case: DataDrivenTestCase) -> None: """Parse and prepare a single case from suite with test case descriptions. @@ -54,7 +65,6 @@ def parse_test_case(case: DataDrivenTestCase) -> None: join = posixpath.join out_section_missing = case.suite.required_out_section - normalize_output = True files: list[tuple[str, str]] = [] # path and contents output_files: list[tuple[str, str | Pattern[str]]] = [] # output path and contents @@ -65,22 +75,34 @@ def parse_test_case(case: DataDrivenTestCase) -> None: rechecked_modules: dict[int, set[str]] = {} # from run number module names triggered: list[str] = [] # Active triggers (one line per incremental step) targets: dict[int, list[str]] = {} # Fine-grained targets (per fine-grained update) + test_modules: list[str] = [] # Modules which are deemed "test" (vs "fixture") + + def _case_fail(msg: str) -> NoReturn: + pytest.fail(f"{case.file}:{case.line}: {msg}", pytrace=False) # Process the parsed items. Each item has a header of form [id args], # optionally followed by lines of text. item = first_item = test_items[0] + test_modules.append("__main__") for item in test_items[1:]: - if item.id in {"file", "outfile", "outfile-re"}: + + def _item_fail(msg: str) -> NoReturn: + item_abs_line = case.line + item.line - 2 + pytest.fail(f"{case.file}:{item_abs_line}: {msg}", pytrace=False) + + if item.id in {"file", "fixture", "outfile", "outfile-re"}: # Record an extra file needed for the test case. assert item.arg is not None contents = expand_variables("\n".join(item.data)) - file_entry = (join(base_path, item.arg), contents) - if item.id == "file": - files.append(file_entry) + path = join(base_path, item.arg) + if item.id != "fixture": + test_modules.append(_file_arg_to_module(item.arg)) + if item.id in {"file", "fixture"}: + files.append((path, contents)) elif item.id == "outfile-re": - output_files.append((file_entry[0], re.compile(file_entry[1].rstrip(), re.S))) - else: - output_files.append(file_entry) + output_files.append((path, re.compile(contents.rstrip(), re.S))) + elif item.id == "outfile": + output_files.append((path, contents)) elif item.id == "builtins": # Use an alternative stub file for the builtins module. assert item.arg is not None @@ -93,6 +115,12 @@ def parse_test_case(case: DataDrivenTestCase) -> None: src_path = join(os.path.dirname(case.file), item.arg) with open(src_path, encoding="utf8") as f: files.append((join(base_path, "typing.pyi"), f.read())) + elif item.id == "_typeshed": + # Use an alternative stub file for the _typeshed module. + assert item.arg is not None + src_path = join(os.path.dirname(case.file), item.arg) + with open(src_path, encoding="utf8") as f: + files.append((join(base_path, "_typeshed.pyi"), f.read())) elif re.match(r"stale[0-9]*$", item.id): passnum = 1 if item.id == "stale" else int(item.id[len("stale") :]) assert passnum > 0 @@ -112,9 +140,11 @@ def parse_test_case(case: DataDrivenTestCase) -> None: # File/directory to delete during a multi-step test case assert item.arg is not None m = re.match(r"(.*)\.([0-9]+)$", item.arg) - assert m, f"Invalid delete section: {item.arg}" + if m is None: + _item_fail(f"Invalid delete section {item.arg!r}") num = int(m.group(2)) - assert num >= 2, f"Can't delete during step {num}" + if num < 2: + _item_fail(f"Can't delete during step {num}") full = join(base_path, m.group(1)) deleted_paths.setdefault(num, set()).add(full) elif re.match(r"out[0-9]*$", item.id): @@ -125,39 +155,34 @@ def parse_test_case(case: DataDrivenTestCase) -> None: version_check = True for arg in args: - if arg == "skip-path-normalization": - normalize_output = False if arg.startswith("version"): compare_op = arg[7:9] if compare_op not in {">=", "=="}: - raise ValueError( - "{}, line {}: Only >= and == version checks are currently supported".format( - case.file, item.line - ) - ) + _item_fail("Only >= and == version checks are currently supported") version_str = arg[9:] try: version = tuple(int(x) for x in version_str.split(".")) except ValueError: - raise ValueError( - '{}, line {}: "{}" is not a valid python version'.format( - case.file, item.line, version_str - ) - ) + _item_fail(f"{version_str!r} is not a valid python version") if compare_op == ">=": + if version <= defaults.PYTHON3_VERSION: + _item_fail( + f"{arg} always true since minimum runtime version is {defaults.PYTHON3_VERSION}" + ) version_check = sys.version_info >= version elif compare_op == "==": + if version < defaults.PYTHON3_VERSION: + _item_fail( + f"{arg} always false since minimum runtime version is {defaults.PYTHON3_VERSION}" + ) if not 1 < len(version) < 4: - raise ValueError( - "{}, line {}: Only minor or patch version checks " - 'are currently supported with "==": "{}"'.format( - case.file, item.line, version_str - ) + _item_fail( + f'Only minor or patch version checks are currently supported with "==": {version_str!r}' ) version_check = sys.version_info[: len(version)] == version if version_check: tmp_output = [expand_variables(line) for line in item.data] - if os.path.sep == "\\" and normalize_output: + if os.path.sep == "\\" and case.normalize_output: tmp_output = [fix_win_path(line) for line in tmp_output] if item.id == "out" or item.id == "out1": output = tmp_output @@ -169,12 +194,11 @@ def parse_test_case(case: DataDrivenTestCase) -> None: elif item.id == "triggered" and item.arg is None: triggered = item.data else: - raise ValueError( - f"Invalid section header {item.id} in {case.file} at line {item.line}" - ) + section_str = item.id + (f" {item.arg}" if item.arg else "") + _item_fail(f"Invalid section header [{section_str}] in case {case.name!r}") if out_section_missing: - raise ValueError(f"{case.file}, line {first_item.line}: Required output section not found") + _case_fail(f"Required output section not found in case {case.name!r}") for passnum in stale_modules.keys(): if passnum not in rechecked_modules: @@ -186,19 +210,24 @@ def parse_test_case(case: DataDrivenTestCase) -> None: and passnum in rechecked_modules and not stale_modules[passnum].issubset(rechecked_modules[passnum]) ): - raise ValueError( - ( - "Stale modules after pass {} must be a subset of rechecked modules ({}:{})" - ).format(passnum, case.file, first_item.line) - ) + _case_fail(f"Stale modules after pass {passnum} must be a subset of rechecked modules") + output_inline_start = len(output) input = first_item.data expand_errors(input, output, "main") for file_path, contents in files: expand_errors(contents.split("\n"), output, file_path) + seen_files = set() + for file, _ in files: + if file in seen_files: + _case_fail(f"Duplicated filename {file}. Did you include it multiple times?") + + seen_files.add(file) + case.input = input case.output = output + case.output_inline_start = output_inline_start case.output2 = output2 case.last_line = case.line + item.line + len(item.data) - 2 case.files = files @@ -207,8 +236,8 @@ def parse_test_case(case: DataDrivenTestCase) -> None: case.expected_rechecked_modules = rechecked_modules case.deleted_paths = deleted_paths case.triggered = triggered or [] - case.normalize_output = normalize_output case.expected_fine_grained_targets = targets + case.test_modules = test_modules class DataDrivenTestCase(pytest.Item): @@ -219,6 +248,7 @@ class DataDrivenTestCase(pytest.Item): input: list[str] output: list[str] # Output for the first pass + output_inline_start: int output2: dict[int, list[str]] # Output for runs 2+, indexed by run number # full path of test suite @@ -227,13 +257,15 @@ class DataDrivenTestCase(pytest.Item): # (file path, file content) tuples files: list[tuple[str, str]] + # Modules which is to be considered "test" rather than "fixture" + test_modules: list[str] expected_stale_modules: dict[int, set[str]] expected_rechecked_modules: dict[int, set[str]] expected_fine_grained_targets: dict[int, list[str]] # Whether or not we should normalize the output to standardize things like # forward vs backward slashes in file paths for Windows vs Linux. - normalize_output = True + normalize_output: bool # Extra attributes used by some tests. last_line: int @@ -245,10 +277,12 @@ def __init__( self, parent: DataSuiteCollector, suite: DataSuite, + *, file: str, name: str, writescache: bool, only_when: str, + normalize_output: bool, platform: str | None, skip: bool, xfail: bool, @@ -260,6 +294,7 @@ def __init__( self.file = file self.writescache = writescache self.only_when = only_when + self.normalize_output = normalize_output if (platform == "windows" and sys.platform != "win32") or ( platform == "posix" and sys.platform == "win32" ): @@ -333,27 +368,33 @@ def setup(self) -> None: self.steps = [steps.get(num, []) for num in range(2, max_step + 1)] def teardown(self) -> None: - assert self.old_cwd is not None and self.tmpdir is not None, "test was not properly set up" - os.chdir(self.old_cwd) - try: - self.tmpdir.cleanup() - except OSError: - pass + if self.old_cwd is not None: + os.chdir(self.old_cwd) + if self.tmpdir is not None: + try: + self.tmpdir.cleanup() + except OSError: + pass self.old_cwd = None self.tmpdir = None def reportinfo(self) -> tuple[str, int, str]: return self.file, self.line, self.name - def repr_failure(self, excinfo: Any, style: Any | None = None) -> str: - if excinfo.errisinstance(SystemExit): + def repr_failure( + self, excinfo: pytest.ExceptionInfo[BaseException], style: Any | None = None + ) -> str: + excrepr: object + if isinstance(excinfo.value, SystemExit): # We assume that before doing exit() (which raises SystemExit) we've printed # enough context about what happened so that a stack trace is not useful. # In particular, uncaught exceptions during semantic analysis or type checking # call exit() and they already print out a stack trace. excrepr = excinfo.exconly() + elif isinstance(excinfo.value, pytest.fail.Exception) and not excinfo.value.pytrace: + excrepr = excinfo.exconly() else: - self.parent._prunetraceback(excinfo) + excinfo.traceback = self.parent._traceback_filter(excinfo) excrepr = excinfo.getrepr(style="short") return f"data: {self.file}:{self.line}:\n{excrepr}" @@ -380,6 +421,7 @@ def module_from_path(path: str) -> str: return module +@dataclass class TestItem: """Parsed test caseitem. @@ -388,20 +430,18 @@ class TestItem: .. data .. """ - id = "" - arg: str | None = "" - - # Text data, array of 8-bit strings + id: str + arg: str | None + # Processed, collapsed text data data: list[str] + # Start line: 1-based, inclusive, relative to testcase + line: int + # End line: 1-based, exclusive, relative to testcase; not same as `line + len(test_item.data)` due to collapsing + end_line: int - file = "" - line = 0 # Line number in file - - def __init__(self, id: str, arg: str | None, data: list[str], line: int) -> None: - self.id = id - self.arg = arg - self.data = data - self.line = line + @property + def trimmed_newlines(self) -> int: # compensates for strip_list + return self.end_line - self.line - len(self.data) def parse_test_data(raw_data: str, name: str) -> list[TestItem]: @@ -423,7 +463,7 @@ def parse_test_data(raw_data: str, name: str) -> list[TestItem]: if id: data = collapse_line_continuation(data) data = strip_list(data) - ret.append(TestItem(id, arg, strip_list(data), i0 + 1)) + ret.append(TestItem(id, arg, data, i0 + 1, i)) i0 = i id = s[1:-1] @@ -444,7 +484,7 @@ def parse_test_data(raw_data: str, name: str) -> list[TestItem]: if id: data = collapse_line_continuation(data) data = strip_list(data) - ret.append(TestItem(id, arg, data, i0 + 1)) + ret.append(TestItem(id, arg, data, i0 + 1, i - 1)) return ret @@ -461,7 +501,7 @@ def strip_list(l: list[str]) -> list[str]: # Strip spaces at end of line r.append(re.sub(r"\s+$", "", s)) - while len(r) > 0 and r[-1] == "": + while r and r[-1] == "": r.pop() return r @@ -580,8 +620,15 @@ def pytest_addoption(parser: Any) -> None: ) +def pytest_configure(config: pytest.Config) -> None: + if config.getoption("--update-data") and config.getoption("--numprocesses", default=1) > 1: + raise pytest.UsageError( + "--update-data incompatible with parallelized tests; re-run with -n 1" + ) + + # This function name is special to pytest. See -# http://doc.pytest.org/en/latest/writing_plugins.html#collection-hooks +# https://doc.pytest.org/en/latest/how-to/writing_plugins.html#collection-hooks def pytest_pycollect_makeitem(collector: Any, name: str, obj: object) -> Any | None: """Called by pytest on each object in modules configured in conftest.py files. @@ -593,12 +640,21 @@ def pytest_pycollect_makeitem(collector: Any, name: str, obj: object) -> Any | N # Non-None result means this obj is a test case. # The collect method of the returned DataSuiteCollector instance will be called later, # with self.obj being obj. - return DataSuiteCollector.from_parent( # type: ignore[no-untyped-call] - parent=collector, name=name - ) + return DataSuiteCollector.from_parent(parent=collector, name=name) return None +_case_name_pattern = re.compile( + r"(?P[a-zA-Z_0-9]+)" + r"(?P-writescache)?" + r"(?P-only_when_cache|-only_when_nocache)?" + r"(?P-skip_path_normalization)?" + r"(-(?Pposix|windows))?" + r"(?P-skip)?" + r"(?P-xfail)?" +) + + def split_test_cases( parent: DataFileCollector, suite: DataSuite, file: str ) -> Iterator[DataDrivenTestCase]: @@ -609,40 +665,34 @@ def split_test_cases( """ with open(file, encoding="utf-8") as f: data = f.read() - # number of groups in the below regex - NUM_GROUPS = 7 - cases = re.split( - r"^\[case ([a-zA-Z_0-9]+)" - r"(-writescache)?" - r"(-only_when_cache|-only_when_nocache)?" - r"(-posix|-windows)?" - r"(-skip)?" - r"(-xfail)?" - r"\][ \t]*$\n", - data, - flags=re.DOTALL | re.MULTILINE, - ) - line_no = cases[0].count("\n") + 1 + cases = re.split(r"^\[case ([^]+)]+)\][ \t]*$\n", data, flags=re.DOTALL | re.MULTILINE) + cases_iter = iter(cases) + line_no = next(cases_iter).count("\n") + 1 test_names = set() - for i in range(1, len(cases), NUM_GROUPS): - name, writescache, only_when, platform_flag, skip, xfail, data = cases[i : i + NUM_GROUPS] + for case_id in cases_iter: + data = next(cases_iter) + + m = _case_name_pattern.fullmatch(case_id) + if not m: + raise RuntimeError(f"Invalid testcase id {case_id!r}") + name = m.group("name") if name in test_names: raise RuntimeError( 'Found a duplicate test name "{}" in {} on line {}'.format( name, parent.name, line_no ) ) - platform = platform_flag[1:] if platform_flag else None yield DataDrivenTestCase.from_parent( parent=parent, suite=suite, file=file, name=add_test_name_suffix(name, suite.test_name_suffix), - writescache=bool(writescache), - only_when=only_when, - platform=platform, - skip=bool(skip), - xfail=bool(xfail), + writescache=bool(m.group("writescache")), + only_when=m.group("only_when"), + platform=m.group("platform"), + skip=bool(m.group("skip")), + xfail=bool(m.group("xfail")), + normalize_output=not m.group("skip_path_normalization"), data=data, line=line_no, ) @@ -667,6 +717,12 @@ def collect(self) -> Iterator[DataFileCollector]: yield DataFileCollector.from_parent(parent=self, name=data_file) +class DataFileFix(NamedTuple): + lineno: int # 1-offset, inclusive + end_lineno: int # 1-offset, exclusive + lines: list[str] + + class DataFileCollector(pytest.Collector): """Represents a single `.test` data driven test file. @@ -675,6 +731,8 @@ class DataFileCollector(pytest.Collector): parent: DataSuiteCollector + _fixes: list[DataFileFix] + @classmethod # We have to fight with pytest here: def from_parent( cls, parent: DataSuiteCollector, *, name: str # type: ignore[override] @@ -690,6 +748,27 @@ def collect(self) -> Iterator[DataDrivenTestCase]: file=os.path.join(self.parent.obj.data_prefix, self.name), ) + def setup(self) -> None: + super().setup() + self._fixes = [] + + def teardown(self) -> None: + super().teardown() + self._apply_fixes() + + def enqueue_fix(self, fix: DataFileFix) -> None: + self._fixes.append(fix) + + def _apply_fixes(self) -> None: + if not self._fixes: + return + data_path = Path(self.parent.obj.data_prefix) / self.name + lines = data_path.read_text().split("\n") + # start from end to prevent line offsets from shifting as we update + for fix in sorted(self._fixes, reverse=True): + lines[fix.lineno - 1 : fix.end_lineno - 1] = fix.lines + data_path.write_text("\n".join(lines)) + def add_test_name_suffix(name: str, suffix: str) -> str: # Find magic suffix of form "-foobar" (used for things like "-skip"). diff --git a/mypy/test/helpers.py b/mypy/test/helpers.py index 145027404ff7..f26c3b042e8c 100644 --- a/mypy/test/helpers.py +++ b/mypy/test/helpers.py @@ -1,13 +1,14 @@ from __future__ import annotations import contextlib +import difflib import os import pathlib import re import shutil import sys import time -from typing import Any, Callable, Iterable, Iterator, Pattern +from typing import IO, Any, Callable, Iterable, Iterator, Pattern # Exporting Suite as alias to TestCase for backwards compatibility # TODO: avoid aliasing - import and subclass TestCase directly @@ -40,75 +41,91 @@ def run_mypy(args: list[str]) -> None: if status != 0: sys.stdout.write(outval) sys.stderr.write(errval) - pytest.fail(msg="Sample check failed", pytrace=False) + pytest.fail(reason="Sample check failed", pytrace=False) + + +def diff_ranges( + left: list[str], right: list[str] +) -> tuple[list[tuple[int, int]], list[tuple[int, int]]]: + seq = difflib.SequenceMatcher(None, left, right) + # note last triple is a dummy, so don't need to worry + blocks = seq.get_matching_blocks() + + i = 0 + j = 0 + left_ranges = [] + right_ranges = [] + for block in blocks: + # mismatched range + left_ranges.append((i, block.a)) + right_ranges.append((j, block.b)) + + i = block.a + block.size + j = block.b + block.size + + # matched range + left_ranges.append((block.a, i)) + right_ranges.append((block.b, j)) + return left_ranges, right_ranges + + +def render_diff_range( + ranges: list[tuple[int, int]], + content: list[str], + *, + colour: str | None = None, + output: IO[str] = sys.stderr, + indent: int = 2, +) -> None: + for i, line_range in enumerate(ranges): + is_matching = i % 2 == 1 + lines = content[line_range[0] : line_range[1]] + for j, line in enumerate(lines): + if ( + is_matching + # elide the middle of matching blocks + and j >= 3 + and j < len(lines) - 3 + ): + if j == 3: + output.write(" " * indent + "...\n") + continue + if not is_matching and colour: + output.write(colour) -def assert_string_arrays_equal(expected: list[str], actual: list[str], msg: str) -> None: - """Assert that two string arrays are equal. + output.write(" " * indent + line) - We consider "can't" and "cannot" equivalent, by replacing the - former with the latter before comparing. + if not is_matching: + if colour: + output.write("\033[0m") + output.write(" (diff)") - Display any differences in a human-readable form. - """ - __tracebackhide__ = True + output.write("\n") - actual = clean_up(actual) - actual = [line.replace("can't", "cannot") for line in actual] - expected = [line.replace("can't", "cannot") for line in expected] - if actual != expected: - num_skip_start = num_skipped_prefix_lines(expected, actual) - num_skip_end = num_skipped_suffix_lines(expected, actual) +def assert_string_arrays_equal( + expected: list[str], actual: list[str], msg: str, *, traceback: bool = False +) -> None: + """Assert that two string arrays are equal. + Display any differences in a human-readable form. + """ + actual = clean_up(actual) + if expected != actual: + expected_ranges, actual_ranges = diff_ranges(expected, actual) sys.stderr.write("Expected:\n") - - # If omit some lines at the beginning, indicate it by displaying a line - # with '...'. - if num_skip_start > 0: - sys.stderr.write(" ...\n") - - # Keep track of the first different line. - first_diff = -1 - - # Display only this many first characters of identical lines. - width = 75 - - for i in range(num_skip_start, len(expected) - num_skip_end): - if i >= len(actual) or expected[i] != actual[i]: - if first_diff < 0: - first_diff = i - sys.stderr.write(f" {expected[i]:<45} (diff)") - else: - e = expected[i] - sys.stderr.write(" " + e[:width]) - if len(e) > width: - sys.stderr.write("...") - sys.stderr.write("\n") - if num_skip_end > 0: - sys.stderr.write(" ...\n") - + red = "\033[31m" if sys.platform != "win32" else None + render_diff_range(expected_ranges, expected, colour=red) sys.stderr.write("Actual:\n") - - if num_skip_start > 0: - sys.stderr.write(" ...\n") - - for j in range(num_skip_start, len(actual) - num_skip_end): - if j >= len(expected) or expected[j] != actual[j]: - sys.stderr.write(f" {actual[j]:<45} (diff)") - else: - a = actual[j] - sys.stderr.write(" " + a[:width]) - if len(a) > width: - sys.stderr.write("...") - sys.stderr.write("\n") - if not actual: - sys.stderr.write(" (empty)\n") - if num_skip_end > 0: - sys.stderr.write(" ...\n") + green = "\033[32m" if sys.platform != "win32" else None + render_diff_range(actual_ranges, actual, colour=green) sys.stderr.write("\n") - + first_diff = next( + (i for i, (a, b) in enumerate(zip(expected, actual)) if a != b), + max(len(expected), len(actual)), + ) if 0 <= first_diff < len(actual) and ( len(expected[first_diff]) >= MIN_LINE_LENGTH_FOR_ALIGNMENT or len(actual[first_diff]) >= MIN_LINE_LENGTH_FOR_ALIGNMENT @@ -117,7 +134,11 @@ def assert_string_arrays_equal(expected: list[str], actual: list[str], msg: str) # long lines. show_align_message(expected[first_diff], actual[first_diff]) - raise AssertionError(msg) + sys.stderr.write( + "Update the test output using --update-data -n0 " + "(you can additionally use the -k selector to update only specific tests)\n" + ) + pytest.fail(msg, pytrace=traceback) def assert_module_equivalence(name: str, expected: Iterable[str], actual: Iterable[str]) -> None: @@ -126,7 +147,7 @@ def assert_module_equivalence(name: str, expected: Iterable[str], actual: Iterab assert_string_arrays_equal( expected_normalized, actual_normalized, - ("Actual modules ({}) do not match expected modules ({}) " 'for "[{} ...]"').format( + ('Actual modules ({}) do not match expected modules ({}) for "[{} ...]"').format( ", ".join(actual_normalized), ", ".join(expected_normalized), name ), ) @@ -137,45 +158,12 @@ def assert_target_equivalence(name: str, expected: list[str], actual: list[str]) assert_string_arrays_equal( expected, actual, - ("Actual targets ({}) do not match expected targets ({}) " 'for "[{} ...]"').format( + ('Actual targets ({}) do not match expected targets ({}) for "[{} ...]"').format( ", ".join(actual), ", ".join(expected), name ), ) -def update_testcase_output(testcase: DataDrivenTestCase, output: list[str]) -> None: - assert testcase.old_cwd is not None, "test was not properly set up" - testcase_path = os.path.join(testcase.old_cwd, testcase.file) - with open(testcase_path, encoding="utf8") as f: - data_lines = f.read().splitlines() - test = "\n".join(data_lines[testcase.line : testcase.last_line]) - - mapping: dict[str, list[str]] = {} - for old, new in zip(testcase.output, output): - PREFIX = "error:" - ind = old.find(PREFIX) - if ind != -1 and old[:ind] == new[:ind]: - old, new = old[ind + len(PREFIX) :], new[ind + len(PREFIX) :] - mapping.setdefault(old, []).append(new) - - for old in mapping: - if test.count(old) == len(mapping[old]): - betweens = test.split(old) - - # Interleave betweens and mapping[old] - from itertools import chain - - interleaved = [betweens[0]] + list( - chain.from_iterable(zip(mapping[old], betweens[1:])) - ) - test = "".join(interleaved) - - data_lines[testcase.line : testcase.last_line] = [test] - data = "\n".join(data_lines) - with open(testcase_path, "w", encoding="utf8") as f: - print(data, file=f) - - def show_align_message(s1: str, s2: str) -> None: """Align s1 and s2 so that the their first difference is highlighted. @@ -258,7 +246,7 @@ def local_sys_path_set() -> Iterator[None]: This can be used by test cases that do runtime imports, for example by the stubgen tests. """ - old_sys_path = sys.path[:] + old_sys_path = sys.path.copy() if not ("" in sys.path or "." in sys.path): sys.path.insert(0, "") try: @@ -267,22 +255,10 @@ def local_sys_path_set() -> Iterator[None]: sys.path = old_sys_path -def num_skipped_prefix_lines(a1: list[str], a2: list[str]) -> int: - num_eq = 0 - while num_eq < min(len(a1), len(a2)) and a1[num_eq] == a2[num_eq]: - num_eq += 1 - return max(0, num_eq - 4) - - -def num_skipped_suffix_lines(a1: list[str], a2: list[str]) -> int: - num_eq = 0 - while num_eq < min(len(a1), len(a2)) and a1[-num_eq - 1] == a2[-num_eq - 1]: - num_eq += 1 - return max(0, num_eq - 4) - - def testfile_pyversion(path: str) -> tuple[int, int]: - if path.endswith("python311.test"): + if path.endswith("python312.test"): + return 3, 12 + elif path.endswith("python311.test"): return 3, 11 elif path.endswith("python310.test"): return 3, 10 @@ -380,13 +356,13 @@ def parse_options( else: flag_list = [] options = Options() - # TODO: Enable strict optional in test cases by default (requires *many* test case changes) - options.strict_optional = False options.error_summary = False options.hide_error_codes = True + options.force_uppercase_builtins = True + options.force_union_syntax = True # Allow custom python version to override testfile_pyversion. - if all(flag.split("=")[0] not in ["--python-version", "-2", "--py2"] for flag in flag_list): + if all(flag.split("=")[0] != "--python-version" for flag in flag_list): options.python_version = testfile_pyversion(testcase.file) if testcase.config.getoption("--mypy-verbose"): diff --git a/mypy/test/update.py b/mypy/test/meta/__init__.py similarity index 100% rename from mypy/test/update.py rename to mypy/test/meta/__init__.py diff --git a/mypy/test/meta/_pytest.py b/mypy/test/meta/_pytest.py new file mode 100644 index 000000000000..b8648f033143 --- /dev/null +++ b/mypy/test/meta/_pytest.py @@ -0,0 +1,72 @@ +import shlex +import subprocess +import sys +import textwrap +import uuid +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable + +from mypy.test.config import test_data_prefix + + +@dataclass +class PytestResult: + input: str + input_updated: str # any updates made by --update-data + stdout: str + stderr: str + + +def dedent_docstring(s: str) -> str: + return textwrap.dedent(s).lstrip() + + +def run_pytest_data_suite( + data_suite: str, + *, + data_file_prefix: str = "check", + pytest_node_prefix: str = "mypy/test/testcheck.py::TypeCheckSuite", + extra_args: Iterable[str], + max_attempts: int, +) -> PytestResult: + """ + Runs a suite of data test cases through pytest until either tests pass + or until a maximum number of attempts (needed for incremental tests). + + :param data_suite: the actual "suite" i.e. the contents of a .test file + """ + p_test_data = Path(test_data_prefix) + p_root = p_test_data.parent.parent + p = p_test_data / f"{data_file_prefix}-meta-{uuid.uuid4()}.test" + assert not p.exists() + data_suite = dedent_docstring(data_suite) + try: + p.write_text(data_suite) + + test_nodeid = f"{pytest_node_prefix}::{p.name}" + extra_args = [sys.executable, "-m", "pytest", "-n", "0", "-s", *extra_args, test_nodeid] + cmd = shlex.join(extra_args) + for i in range(max_attempts - 1, -1, -1): + print(f">> {cmd}") + proc = subprocess.run(extra_args, capture_output=True, check=False, cwd=p_root) + if proc.returncode == 0: + break + prefix = "NESTED PYTEST STDOUT" + for line in proc.stdout.decode().splitlines(): + print(f"{prefix}: {line}") + prefix = " " * len(prefix) + prefix = "NESTED PYTEST STDERR" + for line in proc.stderr.decode().splitlines(): + print(f"{prefix}: {line}") + prefix = " " * len(prefix) + print(f"Exit code {proc.returncode} ({i} attempts remaining)") + + return PytestResult( + input=data_suite, + input_updated=p.read_text(), + stdout=proc.stdout.decode(), + stderr=proc.stderr.decode(), + ) + finally: + p.unlink() diff --git a/mypy/test/meta/test_diff_helper.py b/mypy/test/meta/test_diff_helper.py new file mode 100644 index 000000000000..047751fee1d2 --- /dev/null +++ b/mypy/test/meta/test_diff_helper.py @@ -0,0 +1,47 @@ +import io + +from mypy.test.helpers import Suite, diff_ranges, render_diff_range + + +class DiffHelperSuite(Suite): + def test_render_diff_range(self) -> None: + expected = ["hello", "world"] + actual = ["goodbye", "world"] + + expected_ranges, actual_ranges = diff_ranges(expected, actual) + + output = io.StringIO() + render_diff_range(expected_ranges, expected, output=output) + assert output.getvalue() == " hello (diff)\n world\n" + output = io.StringIO() + render_diff_range(actual_ranges, actual, output=output) + assert output.getvalue() == " goodbye (diff)\n world\n" + + expected = ["a", "b", "c", "d", "e", "f", "g", "h", "circle", "i", "j"] + actual = ["a", "b", "c", "d", "e", "f", "g", "h", "square", "i", "j"] + + expected_ranges, actual_ranges = diff_ranges(expected, actual) + + output = io.StringIO() + render_diff_range(expected_ranges, expected, output=output, indent=0) + assert output.getvalue() == "a\nb\nc\n...\nf\ng\nh\ncircle (diff)\ni\nj\n" + output = io.StringIO() + render_diff_range(actual_ranges, actual, output=output, indent=0) + assert output.getvalue() == "a\nb\nc\n...\nf\ng\nh\nsquare (diff)\ni\nj\n" + + def test_diff_ranges(self) -> None: + a = ["hello", "world"] + b = ["hello", "world"] + + assert diff_ranges(a, b) == ( + [(0, 0), (0, 2), (2, 2), (2, 2)], + [(0, 0), (0, 2), (2, 2), (2, 2)], + ) + + a = ["hello", "world"] + b = ["goodbye", "world"] + + assert diff_ranges(a, b) == ( + [(0, 1), (1, 2), (2, 2), (2, 2)], + [(0, 1), (1, 2), (2, 2), (2, 2)], + ) diff --git a/mypy/test/meta/test_parse_data.py b/mypy/test/meta/test_parse_data.py new file mode 100644 index 000000000000..bff2d6977612 --- /dev/null +++ b/mypy/test/meta/test_parse_data.py @@ -0,0 +1,73 @@ +""" +A "meta test" which tests the parsing of .test files. This is not meant to become exhaustive +but to ensure we maintain a basic level of ergonomics for mypy contributors. +""" + +from mypy.test.helpers import Suite +from mypy.test.meta._pytest import PytestResult, run_pytest_data_suite + + +def _run_pytest(data_suite: str) -> PytestResult: + return run_pytest_data_suite(data_suite, extra_args=[], max_attempts=1) + + +class ParseTestDataSuite(Suite): + def test_parse_invalid_case(self) -> None: + # Act + result = _run_pytest( + """ + [case abc] + s: str + [case foo-XFAIL] + s: str + """ + ) + + # Assert + assert "Invalid testcase id 'foo-XFAIL'" in result.stdout + + def test_parse_invalid_section(self) -> None: + # Act + result = _run_pytest( + """ + [case abc] + s: str + [unknownsection] + abc + """ + ) + + # Assert + expected_lineno = result.input.splitlines().index("[unknownsection]") + 1 + expected = ( + f".test:{expected_lineno}: Invalid section header [unknownsection] in case 'abc'" + ) + assert expected in result.stdout + + def test_bad_ge_version_check(self) -> None: + # Act + actual = _run_pytest( + """ + [case abc] + s: str + [out version>=3.8] + abc + """ + ) + + # Assert + assert "version>=3.8 always true since minimum runtime version is (3, 8)" in actual.stdout + + def test_bad_eq_version_check(self) -> None: + # Act + actual = _run_pytest( + """ + [case abc] + s: str + [out version==3.7] + abc + """ + ) + + # Assert + assert "version==3.7 always false since minimum runtime version is (3, 8)" in actual.stdout diff --git a/mypy/test/meta/test_update_data.py b/mypy/test/meta/test_update_data.py new file mode 100644 index 000000000000..820fd359893e --- /dev/null +++ b/mypy/test/meta/test_update_data.py @@ -0,0 +1,135 @@ +""" +A "meta test" which tests the `--update-data` feature for updating .test files. +Updating the expected output, especially when it's in the form of inline (comment) assertions, +can be brittle, which is why we're "meta-testing" here. +""" + +from mypy.test.helpers import Suite +from mypy.test.meta._pytest import PytestResult, dedent_docstring, run_pytest_data_suite + + +def _run_pytest_update_data(data_suite: str) -> PytestResult: + """ + Runs a suite of data test cases through 'pytest --update-data' until either tests pass + or until a maximum number of attempts (needed for incremental tests). + """ + return run_pytest_data_suite(data_suite, extra_args=["--update-data"], max_attempts=3) + + +class UpdateDataSuite(Suite): + def test_update_data(self) -> None: + # Note: We test multiple testcases rather than 'test case per test case' + # so we could also exercise rewriting multiple testcases at once. + result = _run_pytest_update_data( + """ + [case testCorrect] + s: str = 42 # E: Incompatible types in assignment (expression has type "int", variable has type "str") + + [case testWrong] + s: str = 42 # E: wrong error + + [case testXfail-xfail] + s: str = 42 # E: wrong error + + [case testWrongMultiline] + s: str = 42 # E: foo \ + # N: bar + + [case testMissingMultiline] + s: str = 42; i: int = 'foo' + + [case testExtraneous] + s: str = 'foo' # E: wrong error + + [case testExtraneousMultiline] + s: str = 'foo' # E: foo \ + # E: bar + + [case testExtraneousMultilineNonError] + s: str = 'foo' # W: foo \ + # N: bar + + [case testOutCorrect] + s: str = 42 + [out] + main:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") + + [case testOutWrong] + s: str = 42 + [out] + main:1: error: foobar + + [case testOutWrongIncremental] + s: str = 42 + [out] + main:1: error: foobar + [out2] + main:1: error: foobar + + [case testWrongMultipleFiles] + import a, b + s: str = 42 # E: foo + [file a.py] + s1: str = 42 # E: bar + [file b.py] + s2: str = 43 # E: baz + [builtins fixtures/list.pyi] + """ + ) + + # Assert + expected = dedent_docstring( + """ + [case testCorrect] + s: str = 42 # E: Incompatible types in assignment (expression has type "int", variable has type "str") + + [case testWrong] + s: str = 42 # E: Incompatible types in assignment (expression has type "int", variable has type "str") + + [case testXfail-xfail] + s: str = 42 # E: wrong error + + [case testWrongMultiline] + s: str = 42 # E: Incompatible types in assignment (expression has type "int", variable has type "str") + + [case testMissingMultiline] + s: str = 42; i: int = 'foo' # E: Incompatible types in assignment (expression has type "int", variable has type "str") \\ + # E: Incompatible types in assignment (expression has type "str", variable has type "int") + + [case testExtraneous] + s: str = 'foo' + + [case testExtraneousMultiline] + s: str = 'foo' + + [case testExtraneousMultilineNonError] + s: str = 'foo' + + [case testOutCorrect] + s: str = 42 + [out] + main:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") + + [case testOutWrong] + s: str = 42 + [out] + main:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") + + [case testOutWrongIncremental] + s: str = 42 + [out] + main:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") + [out2] + main:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") + + [case testWrongMultipleFiles] + import a, b + s: str = 42 # E: Incompatible types in assignment (expression has type "int", variable has type "str") + [file a.py] + s1: str = 42 # E: Incompatible types in assignment (expression has type "int", variable has type "str") + [file b.py] + s2: str = 43 # E: Incompatible types in assignment (expression has type "int", variable has type "str") + [builtins fixtures/list.pyi] + """ + ) + assert result.input_updated == expected diff --git a/mypy/test/test_ref_info.py b/mypy/test/test_ref_info.py new file mode 100644 index 000000000000..05052e491657 --- /dev/null +++ b/mypy/test/test_ref_info.py @@ -0,0 +1,45 @@ +"""Test exporting line-level reference information (undocumented feature)""" + +from __future__ import annotations + +import json +import os +import sys + +from mypy import build +from mypy.modulefinder import BuildSource +from mypy.options import Options +from mypy.test.config import test_temp_dir +from mypy.test.data import DataDrivenTestCase, DataSuite +from mypy.test.helpers import assert_string_arrays_equal + + +class RefInfoSuite(DataSuite): + required_out_section = True + files = ["ref-info.test"] + + def run_case(self, testcase: DataDrivenTestCase) -> None: + options = Options() + options.use_builtins_fixtures = True + options.show_traceback = True + options.export_ref_info = True # This is the flag we are testing + + src = "\n".join(testcase.input) + result = build.build( + sources=[BuildSource("main", None, src)], options=options, alt_lib_path=test_temp_dir + ) + assert not result.errors + + major, minor = sys.version_info[:2] + ref_path = os.path.join(options.cache_dir, f"{major}.{minor}", "__main__.refs.json") + + with open(ref_path) as refs_file: + data = json.load(refs_file) + + a = [] + for item in data: + a.append(f"{item['line']}:{item['column']}:{item['target']}") + + assert_string_arrays_equal( + testcase.output, a, f"Invalid output ({testcase.file}, line {testcase.line})" + ) diff --git a/mypy/test/testargs.py b/mypy/test/testargs.py index b0cc6b19aa80..7c139902fe90 100644 --- a/mypy/test/testargs.py +++ b/mypy/test/testargs.py @@ -4,6 +4,7 @@ defaults, and that argparse doesn't assign any new members to the Options object it creates. """ + from __future__ import annotations import argparse diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 4fe2ee6393c0..5fba6192dcaf 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -10,8 +10,6 @@ from mypy.build import Graph from mypy.errors import CompileError from mypy.modulefinder import BuildSource, FindModuleCache, SearchPaths -from mypy.options import TYPE_VAR_TUPLE, UNPACK -from mypy.semanal_main import core_modules from mypy.test.config import test_data_prefix, test_temp_dir from mypy.test.data import DataDrivenTestCase, DataSuite, FileOperation, module_from_path from mypy.test.helpers import ( @@ -23,29 +21,30 @@ normalize_error_messages, parse_options, perform_file_operations, - update_testcase_output, ) +from mypy.test.update_data import update_testcase_output try: - import lxml # type: ignore[import] + import lxml # type: ignore[import-untyped] except ImportError: lxml = None + import pytest # List of files that contain test case descriptions. # Includes all check-* files with the .test extension in the test-data/unit directory typecheck_files = find_test_files(pattern="check-*.test") -# Tests that use Python 3.8-only AST features (like expression-scoped ignores): -if sys.version_info < (3, 8): - typecheck_files.remove("check-python38.test") +# Tests that use Python version specific features: if sys.version_info < (3, 9): typecheck_files.remove("check-python39.test") if sys.version_info < (3, 10): typecheck_files.remove("check-python310.test") if sys.version_info < (3, 11): typecheck_files.remove("check-python311.test") +if sys.version_info < (3, 12): + typecheck_files.remove("check-python312.test") # Special tests for platforms with case-insensitive filesystems. if sys.platform not in ("darwin", "win32"): @@ -85,12 +84,27 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: else: self.run_case_once(testcase) + def _sort_output_if_needed(self, testcase: DataDrivenTestCase, a: list[str]) -> None: + idx = testcase.output_inline_start + if not testcase.files or idx == len(testcase.output): + return + + def _filename(_msg: str) -> str: + return _msg.partition(":")[0] + + file_weights = {file: idx for idx, file in enumerate(_filename(msg) for msg in a)} + testcase.output[idx:] = sorted( + testcase.output[idx:], key=lambda msg: file_weights.get(_filename(msg), -1) + ) + def run_case_once( self, testcase: DataDrivenTestCase, - operations: list[FileOperation] = [], + operations: list[FileOperation] | None = None, incremental_step: int = 0, ) -> None: + if operations is None: + operations = [] original_program_text = "\n".join(testcase.input) module_data = self.parse_module(original_program_text, incremental_step) @@ -113,19 +127,19 @@ def run_case_once( # Parse options after moving files (in case mypy.ini is being moved). options = parse_options(original_program_text, testcase, incremental_step) options.use_builtins_fixtures = True - if not testcase.name.endswith("_no_incomplete"): - options.enable_incomplete_feature = [TYPE_VAR_TUPLE, UNPACK] options.show_traceback = True # Enable some options automatically based on test file name. - if "optional" in testcase.file: - options.strict_optional = True if "columns" in testcase.file: options.show_column_numbers = True if "errorcodes" in testcase.file: options.hide_error_codes = False if "abstract" not in testcase.file: options.allow_empty_bodies = not testcase.name.endswith("_no_empty") + if "lowercase" not in testcase.file: + options.force_uppercase_builtins = True + if "union-error" not in testcase.file: + options.force_union_syntax = True if incremental_step and options.incremental: # Don't overwrite # flags: --no-incremental in incremental test cases @@ -160,24 +174,24 @@ def run_case_once( a = normalize_error_messages(a) # Make sure error messages match - if incremental_step == 0: - # Not incremental - msg = "Unexpected type checker output ({}, line {})" + if incremental_step < 2: + if incremental_step == 1: + msg = "Unexpected type checker output in incremental, run 1 ({}, line {})" + else: + assert incremental_step == 0 + msg = "Unexpected type checker output ({}, line {})" + self._sort_output_if_needed(testcase, a) output = testcase.output - elif incremental_step == 1: - msg = "Unexpected type checker output in incremental, run 1 ({}, line {})" - output = testcase.output - elif incremental_step > 1: + else: msg = ( f"Unexpected type checker output in incremental, run {incremental_step}" + " ({}, line {})" ) output = testcase.output2.get(incremental_step, []) - else: - raise AssertionError() if output != a and testcase.config.getoption("--update-data", False): - update_testcase_output(testcase, a) + update_testcase_output(testcase, a, incremental_step=incremental_step) + assert_string_arrays_equal(output, a, msg.format(testcase.file, testcase.line)) if res: @@ -188,12 +202,10 @@ def run_case_once( if incremental_step: name += str(incremental_step + 1) expected = testcase.expected_fine_grained_targets.get(incremental_step + 1) - actual = res.manager.processed_targets - # Skip the initial builtin cycle. actual = [ - t - for t in actual - if not any(t.startswith(mod) for mod in core_modules + ["mypy_extensions"]) + target + for module, target in res.manager.processed_targets + if module in testcase.test_modules ] if expected is not None: assert_target_equivalence(name, expected, actual) diff --git a/mypy/test/testcmdline.py b/mypy/test/testcmdline.py index 2e8b0dc9a1cd..9bc02d319964 100644 --- a/mypy/test/testcmdline.py +++ b/mypy/test/testcmdline.py @@ -20,7 +20,7 @@ ) try: - import lxml # type: ignore[import] + import lxml # type: ignore[import-untyped] except ImportError: lxml = None @@ -61,6 +61,10 @@ def test_python_cmdline(testcase: DataDrivenTestCase, step: int) -> None: args.append("--hide-error-codes") if "--disallow-empty-bodies" not in args: args.append("--allow-empty-bodies") + if "--no-force-uppercase-builtins" not in args: + args.append("--force-uppercase-builtins") + if "--no-force-union-syntax" not in args: + args.append("--force-union-syntax") # Type check the program. fixed = [python3_path, "-m", "mypy"] env = os.environ.copy() diff --git a/mypy/test/testconstraints.py b/mypy/test/testconstraints.py index b46f31327150..a701a173cbaa 100644 --- a/mypy/test/testconstraints.py +++ b/mypy/test/testconstraints.py @@ -1,7 +1,5 @@ from __future__ import annotations -import pytest - from mypy.constraints import SUBTYPE_OF, SUPERTYPE_OF, Constraint, infer_constraints from mypy.test.helpers import Suite from mypy.test.typefixture import TypeFixture @@ -22,7 +20,6 @@ def test_basic_type_variable(self) -> None: Constraint(type_var=fx.t, op=direction, target=fx.a) ] - @pytest.mark.xfail def test_basic_type_var_tuple_subtype(self) -> None: fx = self.fx assert infer_constraints( @@ -33,13 +30,18 @@ def test_basic_type_var_tuple_subtype(self) -> None: def test_basic_type_var_tuple(self) -> None: fx = self.fx - assert infer_constraints( - Instance(fx.gvi, [UnpackType(fx.ts)]), Instance(fx.gvi, [fx.a, fx.b]), SUPERTYPE_OF - ) == [ + assert set( + infer_constraints( + Instance(fx.gvi, [UnpackType(fx.ts)]), Instance(fx.gvi, [fx.a, fx.b]), SUPERTYPE_OF + ) + ) == { Constraint( type_var=fx.ts, op=SUPERTYPE_OF, target=TupleType([fx.a, fx.b], fx.std_tuple) - ) - ] + ), + Constraint( + type_var=fx.ts, op=SUBTYPE_OF, target=TupleType([fx.a, fx.b], fx.std_tuple) + ), + } def test_type_var_tuple_with_prefix_and_suffix(self) -> None: fx = self.fx @@ -54,6 +56,9 @@ def test_type_var_tuple_with_prefix_and_suffix(self) -> None: Constraint( type_var=fx.ts, op=SUPERTYPE_OF, target=TupleType([fx.b, fx.c], fx.std_tuple) ), + Constraint( + type_var=fx.ts, op=SUBTYPE_OF, target=TupleType([fx.b, fx.c], fx.std_tuple) + ), Constraint(type_var=fx.s, op=SUPERTYPE_OF, target=fx.d), } @@ -67,7 +72,9 @@ def test_unpack_homogenous_tuple(self) -> None: ) ) == { Constraint(type_var=fx.t, op=SUPERTYPE_OF, target=fx.a), + Constraint(type_var=fx.t, op=SUBTYPE_OF, target=fx.a), Constraint(type_var=fx.t, op=SUPERTYPE_OF, target=fx.b), + Constraint(type_var=fx.t, op=SUBTYPE_OF, target=fx.b), } def test_unpack_homogenous_tuple_with_prefix_and_suffix(self) -> None: @@ -81,51 +88,26 @@ def test_unpack_homogenous_tuple_with_prefix_and_suffix(self) -> None: ) == { Constraint(type_var=fx.t, op=SUPERTYPE_OF, target=fx.a), Constraint(type_var=fx.s, op=SUPERTYPE_OF, target=fx.b), + Constraint(type_var=fx.s, op=SUBTYPE_OF, target=fx.b), Constraint(type_var=fx.s, op=SUPERTYPE_OF, target=fx.c), + Constraint(type_var=fx.s, op=SUBTYPE_OF, target=fx.c), Constraint(type_var=fx.u, op=SUPERTYPE_OF, target=fx.d), } - def test_unpack_tuple(self) -> None: - fx = self.fx - assert set( - infer_constraints( - Instance( - fx.gvi, - [ - UnpackType( - TupleType([fx.t, fx.s], fallback=Instance(fx.std_tuplei, [fx.o])) - ) - ], - ), - Instance(fx.gvi, [fx.a, fx.b]), - SUPERTYPE_OF, - ) - ) == { - Constraint(type_var=fx.t, op=SUPERTYPE_OF, target=fx.a), - Constraint(type_var=fx.s, op=SUPERTYPE_OF, target=fx.b), - } - def test_unpack_with_prefix_and_suffix(self) -> None: fx = self.fx assert set( infer_constraints( - Instance( - fx.gv2i, - [ - fx.u, - UnpackType( - TupleType([fx.t, fx.s], fallback=Instance(fx.std_tuplei, [fx.o])) - ), - fx.u, - ], - ), + Instance(fx.gv2i, [fx.u, fx.t, fx.s, fx.u]), Instance(fx.gv2i, [fx.a, fx.b, fx.c, fx.d]), SUPERTYPE_OF, ) ) == { Constraint(type_var=fx.u, op=SUPERTYPE_OF, target=fx.a), Constraint(type_var=fx.t, op=SUPERTYPE_OF, target=fx.b), + Constraint(type_var=fx.t, op=SUBTYPE_OF, target=fx.b), Constraint(type_var=fx.s, op=SUPERTYPE_OF, target=fx.c), + Constraint(type_var=fx.s, op=SUBTYPE_OF, target=fx.c), Constraint(type_var=fx.u, op=SUPERTYPE_OF, target=fx.d), } @@ -133,16 +115,7 @@ def test_unpack_tuple_length_non_match(self) -> None: fx = self.fx assert set( infer_constraints( - Instance( - fx.gv2i, - [ - fx.u, - UnpackType( - TupleType([fx.t, fx.s], fallback=Instance(fx.std_tuplei, [fx.o])) - ), - fx.u, - ], - ), + Instance(fx.gv2i, [fx.u, fx.t, fx.s, fx.u]), Instance(fx.gv2i, [fx.a, fx.b, fx.d]), SUPERTYPE_OF, ) diff --git a/mypy/test/testdaemon.py b/mypy/test/testdaemon.py index e3cdf44d89f2..7115e682e60d 100644 --- a/mypy/test/testdaemon.py +++ b/mypy/test/testdaemon.py @@ -13,8 +13,6 @@ import tempfile import unittest -import pytest - from mypy.dmypy_server import filter_out_missing_top_level_packages from mypy.fscache import FileSystemCache from mypy.modulefinder import SearchPaths @@ -30,8 +28,6 @@ class DaemonSuite(DataSuite): files = daemon_files def run_case(self, testcase: DataDrivenTestCase) -> None: - if testcase.name.endswith("_python38") and sys.version_info < (3, 8): - pytest.skip("Not supported on this version of Python") try: test_daemon(testcase) finally: diff --git a/mypy/test/testdeps.py b/mypy/test/testdeps.py index 3343762cfaaf..f9a059672de8 100644 --- a/mypy/test/testdeps.py +++ b/mypy/test/testdeps.py @@ -41,23 +41,16 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: a = ["Unknown compile error (likely syntax error in test case or fixture)"] else: deps: defaultdict[str, set[str]] = defaultdict(set) - for module in files: - if ( - module in dumped_modules - or dump_all - and module - not in ("abc", "typing", "mypy_extensions", "typing_extensions", "enum") - ): - new_deps = get_dependencies( - files[module], type_map, options.python_version, options - ) + for module, file in files.items(): + if (module in dumped_modules or dump_all) and (module in testcase.test_modules): + new_deps = get_dependencies(file, type_map, options.python_version, options) for source in new_deps: deps[source].update(new_deps[source]) type_state.add_all_protocol_deps(deps) for source, targets in sorted(deps.items()): - if source.startswith((" {', '.join(sorted(targets))}" diff --git a/mypy/test/testerrorstream.py b/mypy/test/testerrorstream.py index 4b98f10fc9ca..a54a3495ddb2 100644 --- a/mypy/test/testerrorstream.py +++ b/mypy/test/testerrorstream.py @@ -1,4 +1,5 @@ """Tests for mypy incremental error output.""" + from __future__ import annotations from mypy import build @@ -29,7 +30,7 @@ def test_error_stream(testcase: DataDrivenTestCase) -> None: logged_messages: list[str] = [] - def flush_errors(msgs: list[str], serious: bool) -> None: + def flush_errors(filename: str | None, msgs: list[str], serious: bool) -> None: if msgs: logged_messages.append("==== Errors flushed ====") logged_messages.extend(msgs) diff --git a/mypy/test/testfinegrained.py b/mypy/test/testfinegrained.py index b19c49bf60bc..f61a58c425fc 100644 --- a/mypy/test/testfinegrained.py +++ b/mypy/test/testfinegrained.py @@ -16,9 +16,8 @@ import os import re -import sys import unittest -from typing import Any, cast +from typing import Any import pytest @@ -29,7 +28,7 @@ from mypy.errors import CompileError from mypy.find_sources import create_source_list from mypy.modulefinder import BuildSource -from mypy.options import TYPE_VAR_TUPLE, UNPACK, Options +from mypy.options import Options from mypy.server.mergecheck import check_consistency from mypy.server.update import sort_messages_preserving_file_order from mypy.test.config import test_temp_dir @@ -70,9 +69,6 @@ def should_skip(self, testcase: DataDrivenTestCase) -> bool: else: if testcase.only_when == "-only_when_cache": return True - - if "Inspect" in testcase.name and sys.version_info < (3, 8): - return True return False def run_case(self, testcase: DataDrivenTestCase) -> None: @@ -153,7 +149,7 @@ def get_options(self, source: str, testcase: DataDrivenTestCase, build_cache: bo options.use_fine_grained_cache = self.use_cache and not build_cache options.cache_fine_grained = self.use_cache options.local_partial_types = True - options.enable_incomplete_feature = [TYPE_VAR_TUPLE, UNPACK] + options.export_types = "inspect" in testcase.file # Treat empty bodies safely for these test cases. options.allow_empty_bodies = not testcase.name.endswith("_no_empty") if re.search("flags:.*--follow-imports", source) is None: @@ -168,8 +164,9 @@ def get_options(self, source: str, testcase: DataDrivenTestCase, build_cache: bo return options def run_check(self, server: Server, sources: list[BuildSource]) -> list[str]: - response = server.check(sources, export_types=True, is_tty=False, terminal_width=-1) - out = cast(str, response["out"] or response["err"]) + response = server.check(sources, export_types=False, is_tty=False, terminal_width=-1) + out = response["out"] or response["err"] + assert isinstance(out, str) return out.splitlines() def build(self, options: Options, sources: list[BuildSource]) -> list[str]: @@ -320,6 +317,7 @@ def maybe_suggest(self, step: int, server: Server, src: str, tmp_dir: str) -> li # JSON contains already escaped \ on Windows, so requires a bit of care. val = val.replace("\\\\", "\\") val = val.replace(os.path.realpath(tmp_dir) + os.path.sep, "") + val = val.replace(os.path.abspath(tmp_dir) + os.path.sep, "") output.extend(val.strip().split("\n")) return normalize_messages(output) @@ -354,7 +352,7 @@ def maybe_inspect(self, step: int, server: Server, src: str) -> list[str]: ) val = res["error"] if "error" in res else res["out"] + res["err"] output.extend(val.strip().split("\n")) - return normalize_messages(output) + return output def get_suggest(self, program_text: str, incremental_step: int) -> list[tuple[str, str]]: step_bit = "1?" if incremental_step == 1 else str(incremental_step) diff --git a/mypy/test/testformatter.py b/mypy/test/testformatter.py index f64527e7804a..9f8bb5d82408 100644 --- a/mypy/test/testformatter.py +++ b/mypy/test/testformatter.py @@ -52,14 +52,14 @@ def test_trim_source(self) -> None: def test_split_words(self) -> None: assert split_words("Simple message") == ["Simple", "message"] - assert split_words('Message with "Some[Long, Types]"' " in it") == [ + assert split_words('Message with "Some[Long, Types]" in it') == [ "Message", "with", '"Some[Long, Types]"', "in", "it", ] - assert split_words('Message with "Some[Long, Types]"' " and [error-code]") == [ + assert split_words('Message with "Some[Long, Types]" and [error-code]') == [ "Message", "with", '"Some[Long, Types]"', diff --git a/mypy/test/testgraph.py b/mypy/test/testgraph.py index b145d92aea6c..0355e75e8c34 100644 --- a/mypy/test/testgraph.py +++ b/mypy/test/testgraph.py @@ -5,17 +5,10 @@ import sys from typing import AbstractSet -from mypy.build import ( - BuildManager, - BuildSourceSet, - State, - order_ascc, - sorted_components, - strongly_connected_components, - topsort, -) +from mypy.build import BuildManager, BuildSourceSet, State, order_ascc, sorted_components from mypy.errors import Errors from mypy.fscache import FileSystemCache +from mypy.graph_utils import strongly_connected_components, topsort from mypy.modulefinder import SearchPaths from mypy.options import Options from mypy.plugin import Plugin @@ -41,9 +34,9 @@ def test_scc(self) -> None: assert_equal(sccs, {frozenset({"A"}), frozenset({"B", "C"}), frozenset({"D"})}) def _make_manager(self) -> BuildManager: - errors = Errors() options = Options() options.use_builtins_fixtures = True + errors = Errors(options) fscache = FileSystemCache() search_paths = SearchPaths((), (), (), ()) manager = BuildManager( @@ -57,7 +50,7 @@ def _make_manager(self) -> BuildManager: plugin=Plugin(options), plugins_snapshot={}, errors=errors, - flush_errors=lambda msgs, serious: None, + flush_errors=lambda filename, msgs, serious: None, fscache=fscache, stdout=sys.stdout, stderr=sys.stderr, diff --git a/mypy/test/testipc.py b/mypy/test/testipc.py index 9034f514bb45..0224035a7b61 100644 --- a/mypy/test/testipc.py +++ b/mypy/test/testipc.py @@ -2,7 +2,7 @@ import sys import time -from multiprocessing import Process, Queue +from multiprocessing import Queue, get_context from unittest import TestCase, main import pytest @@ -15,41 +15,84 @@ def server(msg: str, q: Queue[str]) -> None: server = IPCServer(CONNECTION_NAME) q.put(server.connection_name) - data = b"" + data = "" while not data: with server: - server.write(msg.encode()) + server.write(msg) data = server.read() server.cleanup() +def server_multi_message_echo(q: Queue[str]) -> None: + server = IPCServer(CONNECTION_NAME) + q.put(server.connection_name) + data = "" + with server: + while data != "quit": + data = server.read() + server.write(data) + server.cleanup() + + class IPCTests(TestCase): + def setUp(self) -> None: + if sys.platform == "linux": + # The default "fork" start method is potentially unsafe + self.ctx = get_context("forkserver") + else: + self.ctx = get_context("spawn") + def test_transaction_large(self) -> None: - queue: Queue[str] = Queue() + queue: Queue[str] = self.ctx.Queue() msg = "t" * 200000 # longer than the max read size of 100_000 - p = Process(target=server, args=(msg, queue), daemon=True) + p = self.ctx.Process(target=server, args=(msg, queue), daemon=True) p.start() connection_name = queue.get() with IPCClient(connection_name, timeout=1) as client: - assert client.read() == msg.encode() - client.write(b"test") + assert client.read() == msg + client.write("test") queue.close() queue.join_thread() p.join() def test_connect_twice(self) -> None: - queue: Queue[str] = Queue() + queue: Queue[str] = self.ctx.Queue() msg = "this is a test message" - p = Process(target=server, args=(msg, queue), daemon=True) + p = self.ctx.Process(target=server, args=(msg, queue), daemon=True) p.start() connection_name = queue.get() with IPCClient(connection_name, timeout=1) as client: - assert client.read() == msg.encode() - client.write(b"") # don't let the server hang up yet, we want to connect again. + assert client.read() == msg + client.write("") # don't let the server hang up yet, we want to connect again. with IPCClient(connection_name, timeout=1) as client: - assert client.read() == msg.encode() - client.write(b"test") + assert client.read() == msg + client.write("test") + queue.close() + queue.join_thread() + p.join() + assert p.exitcode == 0 + + def test_multiple_messages(self) -> None: + queue: Queue[str] = self.ctx.Queue() + p = self.ctx.Process(target=server_multi_message_echo, args=(queue,), daemon=True) + p.start() + connection_name = queue.get() + with IPCClient(connection_name, timeout=1) as client: + # "foo bar" with extra accents on letters. + # In UTF-8 encoding so we don't confuse editors opening this file. + fancy_text = b"f\xcc\xb6o\xcc\xb2\xf0\x9d\x91\x9c \xd0\xb2\xe2\xb7\xa1a\xcc\xb6r\xcc\x93\xcd\x98\xcd\x8c" + client.write(fancy_text.decode("utf-8")) + assert client.read() == fancy_text.decode("utf-8") + + client.write("Test with spaces") + client.write("Test write before reading previous") + time.sleep(0) # yield to the server to force reading of all messages by server. + assert client.read() == "Test with spaces" + assert client.read() == "Test write before reading previous" + + client.write("quit") + assert client.read() == "quit" queue.close() queue.join_thread() p.join() diff --git a/mypy/test/testmerge.py b/mypy/test/testmerge.py index 595aba49d8b7..0582c9ed5882 100644 --- a/mypy/test/testmerge.py +++ b/mypy/test/testmerge.py @@ -20,6 +20,7 @@ TypeVarExpr, Var, ) +from mypy.options import Options from mypy.server.subexpr import get_subexpressions from mypy.server.update import FineGrainedBuildManager from mypy.strconv import StrConv @@ -36,27 +37,15 @@ AST = "AST" -NOT_DUMPED_MODULES = ( - "builtins", - "typing", - "abc", - "contextlib", - "sys", - "mypy_extensions", - "typing_extensions", - "enum", -) - - class ASTMergeSuite(DataSuite): files = ["merge.test"] def setup(self) -> None: super().setup() - self.str_conv = StrConv(show_ids=True) + self.str_conv = StrConv(show_ids=True, options=Options()) assert self.str_conv.id_mapper is not None self.id_mapper: IdMapper = self.str_conv.id_mapper - self.type_str_conv = TypeStrVisitor(self.id_mapper) + self.type_str_conv = TypeStrVisitor(self.id_mapper, options=Options()) def run_case(self, testcase: DataDrivenTestCase) -> None: name = testcase.name @@ -84,13 +73,13 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: target_path = os.path.join(test_temp_dir, "target.py") shutil.copy(os.path.join(test_temp_dir, "target.py.next"), target_path) - a.extend(self.dump(fine_grained_manager, kind)) + a.extend(self.dump(fine_grained_manager, kind, testcase.test_modules)) old_subexpr = get_subexpressions(result.manager.modules["target"]) a.append("==>") new_file, new_types = self.build_increment(fine_grained_manager, "target", target_path) - a.extend(self.dump(fine_grained_manager, kind)) + a.extend(self.dump(fine_grained_manager, kind, testcase.test_modules)) for expr in old_subexpr: if isinstance(expr, TypeVarExpr): @@ -114,7 +103,11 @@ def build(self, source: str, testcase: DataDrivenTestCase) -> BuildResult | None options.export_types = True options.show_traceback = True options.allow_empty_bodies = True + options.force_uppercase_builtins = True main_path = os.path.join(test_temp_dir, "main") + + self.str_conv.options = options + self.type_str_conv.options = options with open(main_path, "w", encoding="utf8") as f: f.write(source) try: @@ -137,8 +130,12 @@ def build_increment( type_map = manager.graph[module_id].type_map() return module, type_map - def dump(self, manager: FineGrainedBuildManager, kind: str) -> list[str]: - modules = manager.manager.modules + def dump( + self, manager: FineGrainedBuildManager, kind: str, test_modules: list[str] + ) -> list[str]: + modules = { + name: file for name, file in manager.manager.modules.items() if name in test_modules + } if kind == AST: return self.dump_asts(modules) elif kind == TYPEINFO: @@ -146,15 +143,12 @@ def dump(self, manager: FineGrainedBuildManager, kind: str) -> list[str]: elif kind == SYMTABLE: return self.dump_symbol_tables(modules) elif kind == TYPES: - return self.dump_types(manager) + return self.dump_types(modules, manager) assert False, f"Invalid kind {kind}" def dump_asts(self, modules: dict[str, MypyFile]) -> list[str]: a = [] for m in sorted(modules): - if m in NOT_DUMPED_MODULES: - # We don't support incremental checking of changes to builtins, etc. - continue s = modules[m].accept(self.str_conv) a.extend(s.splitlines()) return a @@ -162,9 +156,6 @@ def dump_asts(self, modules: dict[str, MypyFile]) -> list[str]: def dump_symbol_tables(self, modules: dict[str, MypyFile]) -> list[str]: a = [] for id in sorted(modules): - if not is_dumped_module(id): - # We don't support incremental checking of changes to builtins, etc. - continue a.extend(self.dump_symbol_table(id, modules[id].names)) return a @@ -197,8 +188,6 @@ def format_symbol_table_node(self, node: SymbolTableNode) -> str: def dump_typeinfos(self, modules: dict[str, MypyFile]) -> list[str]: a = [] for id in sorted(modules): - if not is_dumped_module(id): - continue a.extend(self.dump_typeinfos_recursive(modules[id].names)) return a @@ -217,13 +206,13 @@ def dump_typeinfo(self, info: TypeInfo) -> list[str]: s = info.dump(str_conv=self.str_conv, type_str_conv=self.type_str_conv) return s.splitlines() - def dump_types(self, manager: FineGrainedBuildManager) -> list[str]: + def dump_types( + self, modules: dict[str, MypyFile], manager: FineGrainedBuildManager + ) -> list[str]: a = [] # To make the results repeatable, we try to generate unique and # deterministic sort keys. - for module_id in sorted(manager.manager.modules): - if not is_dumped_module(module_id): - continue + for module_id in sorted(modules): all_types = manager.manager.all_types # Compute a module type map from the global type map tree = manager.graph[module_id].tree @@ -234,7 +223,12 @@ def dump_types(self, manager: FineGrainedBuildManager) -> list[str]: if type_map: a.append(f"## {module_id}") for expr in sorted( - type_map, key=lambda n: (n.line, short_type(n), str(n) + str(type_map[n])) + type_map, + key=lambda n: ( + n.line, + short_type(n), + n.str_with_options(self.str_conv.options) + str(type_map[n]), + ), ): typ = type_map[expr] a.append(f"{short_type(expr)}:{expr.line}: {self.format_type(typ)}") @@ -242,7 +236,3 @@ def dump_types(self, manager: FineGrainedBuildManager) -> list[str]: def format_type(self, typ: Type) -> str: return typ.accept(self.type_str_conv) - - -def is_dumped_module(id: str) -> bool: - return id not in NOT_DUMPED_MODULES and (not id.startswith("_") or id == "__main__") diff --git a/mypy/test/testoutput.py b/mypy/test/testoutput.py new file mode 100644 index 000000000000..41f6881658c8 --- /dev/null +++ b/mypy/test/testoutput.py @@ -0,0 +1,58 @@ +"""Test cases for `--output=json`. + +These cannot be run by the usual unit test runner because of the backslashes in +the output, which get normalized to forward slashes by the test suite on Windows. +""" + +from __future__ import annotations + +import os +import os.path + +from mypy import api +from mypy.defaults import PYTHON3_VERSION +from mypy.test.config import test_temp_dir +from mypy.test.data import DataDrivenTestCase, DataSuite + + +class OutputJSONsuite(DataSuite): + files = ["outputjson.test"] + + def run_case(self, testcase: DataDrivenTestCase) -> None: + test_output_json(testcase) + + +def test_output_json(testcase: DataDrivenTestCase) -> None: + """Runs Mypy in a subprocess, and ensures that `--output=json` works as intended.""" + mypy_cmdline = ["--output=json"] + mypy_cmdline.append(f"--python-version={'.'.join(map(str, PYTHON3_VERSION))}") + + # Write the program to a file. + program_path = os.path.join(test_temp_dir, "main") + mypy_cmdline.append(program_path) + with open(program_path, "w", encoding="utf8") as file: + for s in testcase.input: + file.write(f"{s}\n") + + output = [] + # Type check the program. + out, err, returncode = api.run(mypy_cmdline) + # split lines, remove newlines, and remove directory of test case + for line in (out + err).rstrip("\n").splitlines(): + if line.startswith(test_temp_dir + os.sep): + output.append(line[len(test_temp_dir + os.sep) :].rstrip("\r\n")) + else: + output.append(line.rstrip("\r\n")) + + if returncode > 1: + output.append("!!! Mypy crashed !!!") + + # Remove temp file. + os.remove(program_path) + + # JSON encodes every `\` character into `\\`, so we need to remove `\\` from windows paths + # and `/` from POSIX paths + json_os_separator = os.sep.replace("\\", "\\\\") + normalized_output = [line.replace(test_temp_dir + json_os_separator, "") for line in output] + + assert normalized_output == testcase.output diff --git a/mypy/test/testparse.py b/mypy/test/testparse.py index 6a2d1e145251..e215920a6797 100644 --- a/mypy/test/testparse.py +++ b/mypy/test/testparse.py @@ -7,11 +7,13 @@ from pytest import skip from mypy import defaults -from mypy.errors import CompileError +from mypy.config_parser import parse_mypy_comments +from mypy.errors import CompileError, Errors from mypy.options import Options from mypy.parse import parse from mypy.test.data import DataDrivenTestCase, DataSuite from mypy.test.helpers import assert_string_arrays_equal, find_test_files, parse_options +from mypy.util import get_mypy_comments class ParserSuite(DataSuite): @@ -21,6 +23,8 @@ class ParserSuite(DataSuite): if sys.version_info < (3, 10): files.remove("parse-python310.test") + if sys.version_info < (3, 12): + files.remove("parse-python312.test") def run_case(self, testcase: DataDrivenTestCase) -> None: test_parser(testcase) @@ -32,22 +36,33 @@ def test_parser(testcase: DataDrivenTestCase) -> None: The argument contains the description of the test case. """ options = Options() + options.force_uppercase_builtins = True options.hide_error_codes = True if testcase.file.endswith("python310.test"): options.python_version = (3, 10) + elif testcase.file.endswith("python312.test"): + options.python_version = (3, 12) else: options.python_version = defaults.PYTHON3_VERSION + source = "\n".join(testcase.input) + + # Apply mypy: comments to options. + comments = get_mypy_comments(source) + changes, _ = parse_mypy_comments(comments, options) + options = options.apply_changes(changes) + try: n = parse( - bytes("\n".join(testcase.input), "ascii"), + bytes(source, "ascii"), fnam="main", module="__main__", - errors=None, + errors=Errors(options), options=options, + raise_on_error=True, ) - a = str(n).split("\n") + a = n.str_with_options(options).split("\n") except CompileError as e: a = e.messages assert_string_arrays_equal( @@ -76,7 +91,12 @@ def test_parse_error(testcase: DataDrivenTestCase) -> None: skip() # Compile temporary file. The test file contains non-ASCII characters. parse( - bytes("\n".join(testcase.input), "utf-8"), INPUT_FILE_NAME, "__main__", None, options + bytes("\n".join(testcase.input), "utf-8"), + INPUT_FILE_NAME, + "__main__", + errors=Errors(options), + options=options, + raise_on_error=True, ) raise AssertionError("No errors reported") except CompileError as e: diff --git a/mypy/test/testpep561.py b/mypy/test/testpep561.py index 1602bae6a51f..9d2628c1fa5f 100644 --- a/mypy/test/testpep561.py +++ b/mypy/test/testpep561.py @@ -46,23 +46,39 @@ def virtualenv(python_executable: str = sys.executable) -> Iterator[tuple[str, s yield venv_dir, os.path.abspath(os.path.join(venv_dir, "bin", "python")) +def upgrade_pip(python_executable: str) -> None: + """Install pip>=21.3.1. Required for editable installs with PEP 660.""" + if ( + sys.version_info >= (3, 11) + or (3, 10, 3) <= sys.version_info < (3, 11) + or (3, 9, 11) <= sys.version_info < (3, 10) + or (3, 8, 13) <= sys.version_info < (3, 9) + ): + # Skip for more recent Python releases which come with pip>=21.3.1 + # out of the box - for performance reasons. + return + + install_cmd = [python_executable, "-m", "pip", "install", "pip>=21.3.1"] + try: + with filelock.FileLock(pip_lock, timeout=pip_timeout): + proc = subprocess.run(install_cmd, capture_output=True, env=os.environ) + except filelock.Timeout as err: + raise Exception(f"Failed to acquire {pip_lock}") from err + if proc.returncode != 0: + raise Exception(proc.stdout.decode("utf-8") + proc.stderr.decode("utf-8")) + + def install_package( - pkg: str, python_executable: str = sys.executable, use_pip: bool = True, editable: bool = False + pkg: str, python_executable: str = sys.executable, editable: bool = False ) -> None: """Install a package from test-data/packages/pkg/""" working_dir = os.path.join(package_path, pkg) with tempfile.TemporaryDirectory() as dir: - if use_pip: - install_cmd = [python_executable, "-m", "pip", "install"] - if editable: - install_cmd.append("-e") - install_cmd.append(".") - else: - install_cmd = [python_executable, "setup.py"] - if editable: - install_cmd.append("develop") - else: - install_cmd.append("install") + install_cmd = [python_executable, "-m", "pip", "install"] + if editable: + install_cmd.append("-e") + install_cmd.append(".") + # Note that newer versions of pip (21.3+) don't # follow this env variable, but this is for compatibility env = {"PIP_BUILD": dir} @@ -85,18 +101,20 @@ def test_pep561(testcase: DataDrivenTestCase) -> None: assert python is not None, "Should be impossible" pkgs, pip_args = parse_pkgs(testcase.input[0]) mypy_args = parse_mypy_args(testcase.input[1]) - use_pip = True editable = False for arg in pip_args: - if arg == "no-pip": - use_pip = False - elif arg == "editable": + if arg == "editable": editable = True - assert pkgs != [], "No packages to install for PEP 561 test?" + else: + raise ValueError(f"Unknown pip argument: {arg}") + assert pkgs, "No packages to install for PEP 561 test?" with virtualenv(python) as venv: venv_dir, python_executable = venv + if editable: + # Editable installs with PEP 660 require pip>=21.3 + upgrade_pip(python_executable) for pkg in pkgs: - install_package(pkg, python_executable, use_pip, editable) + install_package(pkg, python_executable, editable) cmd_line = list(mypy_args) has_program = not ("-p" in cmd_line or "--package" in cmd_line) @@ -113,7 +131,7 @@ def test_pep561(testcase: DataDrivenTestCase) -> None: steps = testcase.find_steps() if steps != [[]]: - steps = [[]] + steps # type: ignore[assignment] + steps = [[]] + steps for i, operations in enumerate(steps): perform_file_operations(operations) diff --git a/mypy/test/testpythoneval.py b/mypy/test/testpythoneval.py index 02dd11655382..baeea1853ded 100644 --- a/mypy/test/testpythoneval.py +++ b/mypy/test/testpythoneval.py @@ -46,21 +46,31 @@ def test_python_evaluation(testcase: DataDrivenTestCase, cache_dir: str) -> None """ assert testcase.old_cwd is not None, "test was not properly set up" # We must enable site packages to get access to installed stubs. - # TODO: Enable strict optional for these tests mypy_cmdline = [ "--show-traceback", - "--no-strict-optional", "--no-silence-site-packages", "--no-error-summary", "--hide-error-codes", "--allow-empty-bodies", + "--force-uppercase-builtins", + "--test-env", # Speeds up some checks ] interpreter = python3_path mypy_cmdline.append(f"--python-version={'.'.join(map(str, PYTHON3_VERSION))}") m = re.search("# flags: (.*)$", "\n".join(testcase.input), re.MULTILINE) if m: - mypy_cmdline.extend(m.group(1).split()) + additional_flags = m.group(1).split() + for flag in additional_flags: + if flag.startswith("--python-version="): + targetted_python_version = flag.split("=")[1] + targetted_major, targetted_minor = targetted_python_version.split(".") + if (int(targetted_major), int(targetted_minor)) > ( + sys.version_info.major, + sys.version_info.minor, + ): + return + mypy_cmdline.extend(additional_flags) # Write the program to a file. program = "_" + testcase.name + ".py" diff --git a/mypy/test/testreports.py b/mypy/test/testreports.py index a422b4bb2a7b..f638756ad819 100644 --- a/mypy/test/testreports.py +++ b/mypy/test/testreports.py @@ -1,4 +1,5 @@ """Test cases for reports generated by mypy.""" + from __future__ import annotations import textwrap @@ -7,7 +8,7 @@ from mypy.test.helpers import Suite, assert_equal try: - import lxml # type: ignore[import] + import lxml # type: ignore[import-untyped] except ImportError: lxml = None @@ -22,7 +23,7 @@ def test_get_line_rate(self) -> None: @pytest.mark.skipif(lxml is None, reason="Cannot import lxml. Is it installed?") def test_as_xml(self) -> None: - import lxml.etree as etree # type: ignore[import] + import lxml.etree as etree # type: ignore[import-untyped] cobertura_package = CoberturaPackage("foobar") cobertura_package.covered_lines = 21 diff --git a/mypy/test/testsemanal.py b/mypy/test/testsemanal.py index 71ebc43df8c2..cdecc4739168 100644 --- a/mypy/test/testsemanal.py +++ b/mypy/test/testsemanal.py @@ -2,7 +2,6 @@ from __future__ import annotations -import os.path import sys from typing import Dict @@ -11,7 +10,7 @@ from mypy.errors import CompileError from mypy.modulefinder import BuildSource from mypy.nodes import TypeInfo -from mypy.options import TYPE_VAR_TUPLE, UNPACK, Options +from mypy.options import Options from mypy.test.config import test_temp_dir from mypy.test.data import DataDrivenTestCase, DataSuite from mypy.test.helpers import ( @@ -46,7 +45,7 @@ def get_semanal_options(program_text: str, testcase: DataDrivenTestCase) -> Opti options.semantic_analysis_only = True options.show_traceback = True options.python_version = PYTHON3_VERSION - options.enable_incomplete_feature = [TYPE_VAR_TUPLE, UNPACK] + options.force_uppercase_builtins = True return options @@ -77,27 +76,9 @@ def test_semanal(testcase: DataDrivenTestCase) -> None: raise CompileError(a) # Include string representations of the source files in the actual # output. - for fnam in sorted(result.files.keys()): - f = result.files[fnam] - # Omit the builtins module and files with a special marker in the - # path. - # TODO the test is not reliable - if ( - not f.path.endswith( - ( - os.sep + "builtins.pyi", - "typing.pyi", - "mypy_extensions.pyi", - "typing_extensions.pyi", - "abc.pyi", - "collections.pyi", - "sys.pyi", - ) - ) - and not os.path.basename(f.path).startswith("_") - and not os.path.splitext(os.path.basename(f.path))[0].endswith("_") - ): - a += str(f).split("\n") + for module in sorted(result.files.keys()): + if module in testcase.test_modules: + a += result.files[module].str_with_options(options).split("\n") except CompileError as e: a = e.messages if testcase.normalize_output: @@ -164,10 +145,10 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: a = result.errors if a: raise CompileError(a) - for f in sorted(result.files.keys()): - if f not in ("builtins", "typing", "abc"): - a.append(f"{f}:") - for s in str(result.files[f].names).split("\n"): + for module in sorted(result.files.keys()): + if module in testcase.test_modules: + a.append(f"{module}:") + for s in str(result.files[module].names).split("\n"): a.append(" " + s) except CompileError as e: a = e.messages @@ -199,11 +180,13 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: # Collect all TypeInfos in top-level modules. typeinfos = TypeInfoMap() - for f in result.files.values(): - for n in f.names.values(): - if isinstance(n.node, TypeInfo): - assert n.fullname - typeinfos[n.fullname] = n.node + for module, file in result.files.items(): + if module in testcase.test_modules: + for n in file.names.values(): + if isinstance(n.node, TypeInfo): + assert n.fullname + if any(n.fullname.startswith(m + ".") for m in testcase.test_modules): + typeinfos[n.fullname] = n.node # The output is the symbol table converted into a string. a = str(typeinfos).split("\n") @@ -220,12 +203,7 @@ class TypeInfoMap(Dict[str, TypeInfo]): def __str__(self) -> str: a: list[str] = ["TypeInfoMap("] for x, y in sorted(self.items()): - if ( - not x.startswith("builtins.") - and not x.startswith("typing.") - and not x.startswith("abc.") - ): - ti = ("\n" + " ").join(str(y).split("\n")) - a.append(f" {x} : {ti}") + ti = ("\n" + " ").join(str(y).split("\n")) + a.append(f" {x} : {ti}") a[-1] += ")" return "\n".join(a) diff --git a/mypy/test/testsolve.py b/mypy/test/testsolve.py index d6c585ef4aaa..6566b03ef5e9 100644 --- a/mypy/test/testsolve.py +++ b/mypy/test/testsolve.py @@ -3,10 +3,10 @@ from __future__ import annotations from mypy.constraints import SUBTYPE_OF, SUPERTYPE_OF, Constraint -from mypy.solve import solve_constraints +from mypy.solve import Bounds, Graph, solve_constraints, transitive_closure from mypy.test.helpers import Suite, assert_equal from mypy.test.typefixture import TypeFixture -from mypy.types import Type, TypeVarId, TypeVarType +from mypy.types import Type, TypeVarId, TypeVarLikeType, TypeVarType class SolveSuite(Suite): @@ -17,125 +17,266 @@ def test_empty_input(self) -> None: self.assert_solve([], [], []) def test_simple_supertype_constraints(self) -> None: + self.assert_solve([self.fx.t], [self.supc(self.fx.t, self.fx.a)], [self.fx.a]) self.assert_solve( - [self.fx.t.id], [self.supc(self.fx.t, self.fx.a)], [(self.fx.a, self.fx.o)] - ) - self.assert_solve( - [self.fx.t.id], + [self.fx.t], [self.supc(self.fx.t, self.fx.a), self.supc(self.fx.t, self.fx.b)], - [(self.fx.a, self.fx.o)], + [self.fx.a], ) def test_simple_subtype_constraints(self) -> None: - self.assert_solve([self.fx.t.id], [self.subc(self.fx.t, self.fx.a)], [self.fx.a]) + self.assert_solve([self.fx.t], [self.subc(self.fx.t, self.fx.a)], [self.fx.a]) self.assert_solve( - [self.fx.t.id], + [self.fx.t], [self.subc(self.fx.t, self.fx.a), self.subc(self.fx.t, self.fx.b)], [self.fx.b], ) def test_both_kinds_of_constraints(self) -> None: self.assert_solve( - [self.fx.t.id], + [self.fx.t], [self.supc(self.fx.t, self.fx.b), self.subc(self.fx.t, self.fx.a)], - [(self.fx.b, self.fx.a)], + [self.fx.b], ) def test_unsatisfiable_constraints(self) -> None: # The constraints are impossible to satisfy. self.assert_solve( - [self.fx.t.id], - [self.supc(self.fx.t, self.fx.a), self.subc(self.fx.t, self.fx.b)], - [None], + [self.fx.t], [self.supc(self.fx.t, self.fx.a), self.subc(self.fx.t, self.fx.b)], [None] ) def test_exactly_specified_result(self) -> None: self.assert_solve( - [self.fx.t.id], + [self.fx.t], [self.supc(self.fx.t, self.fx.b), self.subc(self.fx.t, self.fx.b)], - [(self.fx.b, self.fx.b)], + [self.fx.b], ) def test_multiple_variables(self) -> None: self.assert_solve( - [self.fx.t.id, self.fx.s.id], + [self.fx.t, self.fx.s], [ self.supc(self.fx.t, self.fx.b), self.supc(self.fx.s, self.fx.c), self.subc(self.fx.t, self.fx.a), ], - [(self.fx.b, self.fx.a), (self.fx.c, self.fx.o)], + [self.fx.b, self.fx.c], ) def test_no_constraints_for_var(self) -> None: - self.assert_solve([self.fx.t.id], [], [self.fx.uninhabited]) + self.assert_solve([self.fx.t], [], [self.fx.uninhabited]) + self.assert_solve([self.fx.t, self.fx.s], [], [self.fx.uninhabited, self.fx.uninhabited]) self.assert_solve( - [self.fx.t.id, self.fx.s.id], [], [self.fx.uninhabited, self.fx.uninhabited] - ) - self.assert_solve( - [self.fx.t.id, self.fx.s.id], + [self.fx.t, self.fx.s], [self.supc(self.fx.s, self.fx.a)], - [self.fx.uninhabited, (self.fx.a, self.fx.o)], + [self.fx.uninhabited, self.fx.a], ) def test_simple_constraints_with_dynamic_type(self) -> None: + self.assert_solve([self.fx.t], [self.supc(self.fx.t, self.fx.anyt)], [self.fx.anyt]) self.assert_solve( - [self.fx.t.id], [self.supc(self.fx.t, self.fx.anyt)], [(self.fx.anyt, self.fx.anyt)] - ) - self.assert_solve( - [self.fx.t.id], + [self.fx.t], [self.supc(self.fx.t, self.fx.anyt), self.supc(self.fx.t, self.fx.anyt)], - [(self.fx.anyt, self.fx.anyt)], + [self.fx.anyt], ) self.assert_solve( - [self.fx.t.id], + [self.fx.t], [self.supc(self.fx.t, self.fx.anyt), self.supc(self.fx.t, self.fx.a)], - [(self.fx.anyt, self.fx.anyt)], + [self.fx.anyt], ) + self.assert_solve([self.fx.t], [self.subc(self.fx.t, self.fx.anyt)], [self.fx.anyt]) self.assert_solve( - [self.fx.t.id], [self.subc(self.fx.t, self.fx.anyt)], [(self.fx.anyt, self.fx.anyt)] - ) - self.assert_solve( - [self.fx.t.id], + [self.fx.t], [self.subc(self.fx.t, self.fx.anyt), self.subc(self.fx.t, self.fx.anyt)], - [(self.fx.anyt, self.fx.anyt)], + [self.fx.anyt], ) - # self.assert_solve([self.fx.t.id], + # self.assert_solve([self.fx.t], # [self.subc(self.fx.t, self.fx.anyt), # self.subc(self.fx.t, self.fx.a)], - # [(self.fx.anyt, self.fx.anyt)]) + # [self.fx.anyt]) # TODO: figure out what this should be after changes to meet(any, X) def test_both_normal_and_any_types_in_results(self) -> None: # If one of the bounds is any, we promote the other bound to # any as well, since otherwise the type range does not make sense. self.assert_solve( - [self.fx.t.id], + [self.fx.t], [self.supc(self.fx.t, self.fx.a), self.subc(self.fx.t, self.fx.anyt)], - [(self.fx.anyt, self.fx.anyt)], + [self.fx.anyt], ) self.assert_solve( - [self.fx.t.id], + [self.fx.t], [self.supc(self.fx.t, self.fx.anyt), self.subc(self.fx.t, self.fx.a)], - [(self.fx.anyt, self.fx.anyt)], + [self.fx.anyt], + ) + + def test_poly_no_constraints(self) -> None: + self.assert_solve( + [self.fx.t, self.fx.u], + [], + [self.fx.uninhabited, self.fx.uninhabited], + allow_polymorphic=True, + ) + + def test_poly_trivial_free(self) -> None: + self.assert_solve( + [self.fx.t, self.fx.u], + [self.subc(self.fx.t, self.fx.a)], + [self.fx.a, self.fx.u], + [self.fx.u], + allow_polymorphic=True, + ) + + def test_poly_free_pair(self) -> None: + self.assert_solve( + [self.fx.t, self.fx.u], + [self.subc(self.fx.t, self.fx.u)], + [self.fx.t, self.fx.t], + [self.fx.t], + allow_polymorphic=True, + ) + + def test_poly_free_pair_with_bounds(self) -> None: + t_prime = self.fx.t.copy_modified(upper_bound=self.fx.b) + self.assert_solve( + [self.fx.t, self.fx.ub], + [self.subc(self.fx.t, self.fx.ub)], + [t_prime, t_prime], + [t_prime], + allow_polymorphic=True, + ) + + def test_poly_free_pair_with_bounds_uninhabited(self) -> None: + self.assert_solve( + [self.fx.ub, self.fx.uc], + [self.subc(self.fx.ub, self.fx.uc)], + [self.fx.uninhabited, self.fx.uninhabited], + [], + allow_polymorphic=True, + ) + + def test_poly_bounded_chain(self) -> None: + # B <: T <: U <: S <: A + self.assert_solve( + [self.fx.t, self.fx.u, self.fx.s], + [ + self.supc(self.fx.t, self.fx.b), + self.subc(self.fx.t, self.fx.u), + self.subc(self.fx.u, self.fx.s), + self.subc(self.fx.s, self.fx.a), + ], + [self.fx.b, self.fx.b, self.fx.b], + allow_polymorphic=True, + ) + + def test_poly_reverse_overlapping_chain(self) -> None: + # A :> T <: S :> B + self.assert_solve( + [self.fx.t, self.fx.s], + [ + self.subc(self.fx.t, self.fx.s), + self.subc(self.fx.t, self.fx.a), + self.supc(self.fx.s, self.fx.b), + ], + [self.fx.a, self.fx.a], + allow_polymorphic=True, + ) + + def test_poly_reverse_split_chain(self) -> None: + # B :> T <: S :> A + self.assert_solve( + [self.fx.t, self.fx.s], + [ + self.subc(self.fx.t, self.fx.s), + self.subc(self.fx.t, self.fx.b), + self.supc(self.fx.s, self.fx.a), + ], + [self.fx.b, self.fx.a], + allow_polymorphic=True, + ) + + def test_poly_unsolvable_chain(self) -> None: + # A <: T <: U <: S <: B + self.assert_solve( + [self.fx.t, self.fx.u, self.fx.s], + [ + self.supc(self.fx.t, self.fx.a), + self.subc(self.fx.t, self.fx.u), + self.subc(self.fx.u, self.fx.s), + self.subc(self.fx.s, self.fx.b), + ], + [None, None, None], + allow_polymorphic=True, + ) + + def test_simple_chain_closure(self) -> None: + self.assert_transitive_closure( + [self.fx.t.id, self.fx.s.id], + [ + self.supc(self.fx.t, self.fx.b), + self.subc(self.fx.t, self.fx.s), + self.subc(self.fx.s, self.fx.a), + ], + {(self.fx.t.id, self.fx.s.id)}, + {self.fx.t.id: {self.fx.b}, self.fx.s.id: {self.fx.b}}, + {self.fx.t.id: {self.fx.a}, self.fx.s.id: {self.fx.a}}, + ) + + def test_reverse_chain_closure(self) -> None: + self.assert_transitive_closure( + [self.fx.t.id, self.fx.s.id], + [ + self.subc(self.fx.t, self.fx.s), + self.subc(self.fx.t, self.fx.a), + self.supc(self.fx.s, self.fx.b), + ], + {(self.fx.t.id, self.fx.s.id)}, + {self.fx.t.id: set(), self.fx.s.id: {self.fx.b}}, + {self.fx.t.id: {self.fx.a}, self.fx.s.id: set()}, + ) + + def test_secondary_constraint_closure(self) -> None: + self.assert_transitive_closure( + [self.fx.t.id, self.fx.s.id], + [self.supc(self.fx.s, self.fx.gt), self.subc(self.fx.s, self.fx.ga)], + set(), + {self.fx.t.id: set(), self.fx.s.id: {self.fx.gt}}, + {self.fx.t.id: {self.fx.a}, self.fx.s.id: {self.fx.ga}}, ) def assert_solve( + self, + vars: list[TypeVarLikeType], + constraints: list[Constraint], + results: list[None | Type], + free_vars: list[TypeVarLikeType] | None = None, + allow_polymorphic: bool = False, + ) -> None: + if free_vars is None: + free_vars = [] + actual, actual_free = solve_constraints( + vars, constraints, allow_polymorphic=allow_polymorphic + ) + assert_equal(actual, results) + assert_equal(actual_free, free_vars) + + def assert_transitive_closure( self, vars: list[TypeVarId], constraints: list[Constraint], - results: list[None | Type | tuple[Type, Type]], + graph: Graph, + lowers: Bounds, + uppers: Bounds, ) -> None: - res: list[Type | None] = [] - for r in results: - if isinstance(r, tuple): - res.append(r[0]) - else: - res.append(r) - actual = solve_constraints(vars, constraints) - assert_equal(str(actual), str(res)) + actual_graph, actual_lowers, actual_uppers = transitive_closure(vars, constraints) + # Add trivial elements. + for v in vars: + graph.add((v, v)) + assert_equal(actual_graph, graph) + assert_equal(dict(actual_lowers), lowers) + assert_equal(dict(actual_uppers), uppers) def supc(self, type_var: TypeVarType, bound: Type) -> Constraint: return Constraint(type_var, SUPERTYPE_OF, bound) diff --git a/mypy/test/teststubgen.py b/mypy/test/teststubgen.py index c7b576f89389..3669772854cb 100644 --- a/mypy/test/teststubgen.py +++ b/mypy/test/teststubgen.py @@ -10,6 +10,8 @@ from types import ModuleType from typing import Any +import pytest + from mypy.errors import CompileError from mypy.moduleinspect import InspectError, ModuleInspect from mypy.stubdoc import ( @@ -28,20 +30,19 @@ Options, collect_build_targets, generate_stubs, - get_sig_generators, is_blacklisted_path, is_non_library_module, mypy_options, parse_options, ) -from mypy.stubgenc import ( - generate_c_function_stub, - generate_c_property_stub, - generate_c_type_stub, - infer_method_sig, - is_c_property_readonly, +from mypy.stubgenc import InspectionStubGenerator, infer_c_method_args +from mypy.stubutil import ( + ClassInfo, + common_dir_prefix, + infer_method_ret_type, + remove_misplaced_type_comments, + walk_packages, ) -from mypy.stubutil import common_dir_prefix, remove_misplaced_type_comments, walk_packages from mypy.test.data import DataDrivenTestCase, DataSuite from mypy.test.helpers import assert_equal, assert_string_arrays_equal, local_sys_path_set @@ -61,7 +62,8 @@ def test_files_found(self) -> None: os.mkdir(os.path.join("subdir", "pack")) self.make_file("subdir", "pack", "__init__.py") opts = parse_options(["subdir"]) - py_mods, c_mods = collect_build_targets(opts, mypy_options(opts)) + py_mods, pyi_mods, c_mods = collect_build_targets(opts, mypy_options(opts)) + assert_equal(pyi_mods, []) assert_equal(c_mods, []) files = {mod.path for mod in py_mods} assert_equal( @@ -86,7 +88,8 @@ def test_packages_found(self) -> None: self.make_file("pack", "a.py") self.make_file("pack", "b.py") opts = parse_options(["-p", "pack"]) - py_mods, c_mods = collect_build_targets(opts, mypy_options(opts)) + py_mods, pyi_mods, c_mods = collect_build_targets(opts, mypy_options(opts)) + assert_equal(pyi_mods, []) assert_equal(c_mods, []) files = {os.path.relpath(mod.path or "FAIL") for mod in py_mods} assert_equal( @@ -110,7 +113,7 @@ def test_module_not_found(self) -> None: os.chdir(tmp) self.make_file(tmp, "mymodule.py", content="import a") opts = parse_options(["-m", "mymodule"]) - py_mods, c_mods = collect_build_targets(opts, mypy_options(opts)) + collect_build_targets(opts, mypy_options(opts)) assert captured_output.getvalue() == "" finally: sys.stdout = sys.__stdout__ @@ -676,6 +679,7 @@ class StubgenPythonSuite(DataSuite): base_path = "." files = ["stubgen.test"] + @unittest.skipIf(sys.platform == "win32", "clean up fails on Windows") def run_case(self, testcase: DataDrivenTestCase) -> None: with local_sys_path_set(): self.run_case_inner(testcase) @@ -696,14 +700,20 @@ def run_case_inner(self, testcase: DataDrivenTestCase) -> None: f.write(content) options = self.parse_flags(source, extra) + if sys.version_info < options.pyversion: + pytest.skip() modules = self.parse_modules(source) out_dir = "out" try: try: - if not testcase.name.endswith("_import"): - options.no_import = True - if not testcase.name.endswith("_semanal"): - options.parse_only = True + if testcase.name.endswith("_inspect"): + options.inspect = True + else: + if not testcase.name.endswith("_import"): + options.no_import = True + if not testcase.name.endswith("_semanal"): + options.parse_only = True + generate_stubs(options) a: list[str] = [] for module in modules: @@ -722,11 +732,22 @@ def run_case_inner(self, testcase: DataDrivenTestCase) -> None: def parse_flags(self, program_text: str, extra: list[str]) -> Options: flags = re.search("# flags: (.*)$", program_text, flags=re.MULTILINE) + pyversion = None if flags: flag_list = flags.group(1).split() + for i, flag in enumerate(flag_list): + if flag.startswith("--python-version="): + pyversion = flag.split("=", 1)[1] + del flag_list[i] + break else: flag_list = [] options = parse_options(flag_list + extra) + if pyversion: + # A hack to allow testing old python versions with new language constructs + # This should be rarely used in general as stubgen output should not be version-specific + major, minor = pyversion.split(".", 1) + options.pyversion = (int(major), int(minor)) if "--verbose" not in flag_list: options.quiet = True else: @@ -768,70 +789,60 @@ class StubgencSuite(unittest.TestCase): """ def test_infer_hash_sig(self) -> None: - assert_equal(infer_method_sig("__hash__"), [self_arg]) + assert_equal(infer_c_method_args("__hash__"), [self_arg]) + assert_equal(infer_method_ret_type("__hash__"), "int") def test_infer_getitem_sig(self) -> None: - assert_equal(infer_method_sig("__getitem__"), [self_arg, ArgSig(name="index")]) + assert_equal(infer_c_method_args("__getitem__"), [self_arg, ArgSig(name="index")]) def test_infer_setitem_sig(self) -> None: assert_equal( - infer_method_sig("__setitem__"), + infer_c_method_args("__setitem__"), [self_arg, ArgSig(name="index"), ArgSig(name="object")], ) + assert_equal(infer_method_ret_type("__setitem__"), "None") + + def test_infer_eq_op_sig(self) -> None: + for op in ("eq", "ne", "lt", "le", "gt", "ge"): + assert_equal( + infer_c_method_args(f"__{op}__"), [self_arg, ArgSig(name="other", type="object")] + ) def test_infer_binary_op_sig(self) -> None: - for op in ( - "eq", - "ne", - "lt", - "le", - "gt", - "ge", - "add", - "radd", - "sub", - "rsub", - "mul", - "rmul", - ): - assert_equal(infer_method_sig(f"__{op}__"), [self_arg, ArgSig(name="other")]) + for op in ("add", "radd", "sub", "rsub", "mul", "rmul"): + assert_equal(infer_c_method_args(f"__{op}__"), [self_arg, ArgSig(name="other")]) + + def test_infer_equality_op_sig(self) -> None: + for op in ("eq", "ne", "lt", "le", "gt", "ge", "contains"): + assert_equal(infer_method_ret_type(f"__{op}__"), "bool") def test_infer_unary_op_sig(self) -> None: for op in ("neg", "pos"): - assert_equal(infer_method_sig(f"__{op}__"), [self_arg]) + assert_equal(infer_c_method_args(f"__{op}__"), [self_arg]) + + def test_infer_cast_sig(self) -> None: + for op in ("float", "bool", "bytes", "int"): + assert_equal(infer_method_ret_type(f"__{op}__"), op) - def test_generate_c_type_stub_no_crash_for_object(self) -> None: + def test_generate_class_stub_no_crash_for_object(self) -> None: output: list[str] = [] mod = ModuleType("module", "") # any module is fine - imports: list[str] = [] - generate_c_type_stub( - mod, - "alias", - object, - output, - imports, - sig_generators=get_sig_generators(parse_options([])), - ) - assert_equal(imports, []) + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + + gen.generate_class_stub("alias", object, output) + assert_equal(gen.get_imports().splitlines(), []) assert_equal(output[0], "class alias:") - def test_generate_c_type_stub_variable_type_annotation(self) -> None: + def test_generate_class_stub_variable_type_annotation(self) -> None: # This class mimics the stubgen unit test 'testClassVariable' class TestClassVariableCls: x = 1 output: list[str] = [] - imports: list[str] = [] mod = ModuleType("module", "") # any module is fine - generate_c_type_stub( - mod, - "C", - TestClassVariableCls, - output, - imports, - sig_generators=get_sig_generators(parse_options([])), - ) - assert_equal(imports, []) + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_class_stub("C", TestClassVariableCls, output) + assert_equal(gen.get_imports().splitlines(), ["from typing import ClassVar"]) assert_equal(output, ["class C:", " x: ClassVar[int] = ..."]) def test_generate_c_type_inheritance(self) -> None: @@ -839,33 +850,19 @@ class TestClass(KeyError): pass output: list[str] = [] - imports: list[str] = [] mod = ModuleType("module, ") - generate_c_type_stub( - mod, - "C", - TestClass, - output, - imports, - sig_generators=get_sig_generators(parse_options([])), - ) + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_class_stub("C", TestClass, output) assert_equal(output, ["class C(KeyError): ..."]) - assert_equal(imports, []) + assert_equal(gen.get_imports().splitlines(), []) def test_generate_c_type_inheritance_same_module(self) -> None: output: list[str] = [] - imports: list[str] = [] mod = ModuleType(TestBaseClass.__module__, "") - generate_c_type_stub( - mod, - "C", - TestClass, - output, - imports, - sig_generators=get_sig_generators(parse_options([])), - ) + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_class_stub("C", TestClass, output) assert_equal(output, ["class C(TestBaseClass): ..."]) - assert_equal(imports, []) + assert_equal(gen.get_imports().splitlines(), []) def test_generate_c_type_inheritance_other_module(self) -> None: import argparse @@ -874,36 +871,22 @@ class TestClass(argparse.Action): pass output: list[str] = [] - imports: list[str] = [] mod = ModuleType("module", "") - generate_c_type_stub( - mod, - "C", - TestClass, - output, - imports, - sig_generators=get_sig_generators(parse_options([])), - ) + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_class_stub("C", TestClass, output) assert_equal(output, ["class C(argparse.Action): ..."]) - assert_equal(imports, ["import argparse"]) + assert_equal(gen.get_imports().splitlines(), ["import argparse"]) def test_generate_c_type_inheritance_builtin_type(self) -> None: class TestClass(type): pass output: list[str] = [] - imports: list[str] = [] mod = ModuleType("module", "") - generate_c_type_stub( - mod, - "C", - TestClass, - output, - imports, - sig_generators=get_sig_generators(parse_options([])), - ) + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_class_stub("C", TestClass, output) assert_equal(output, ["class C(type): ..."]) - assert_equal(imports, []) + assert_equal(gen.get_imports().splitlines(), []) def test_generate_c_type_with_docstring(self) -> None: class TestClass: @@ -913,20 +896,16 @@ def test(self, arg0: str) -> None: """ output: list[str] = [] - imports: list[str] = [] mod = ModuleType(TestClass.__module__, "") - generate_c_function_stub( - mod, + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_function_stub( "test", TestClass.test, - output, - imports, - self_var="self", - class_name="TestClass", - sig_generators=get_sig_generators(parse_options([])), + output=output, + class_info=ClassInfo(self_var="self", cls=TestClass, name="TestClass"), ) assert_equal(output, ["def test(self, arg0: int) -> Any: ..."]) - assert_equal(imports, []) + assert_equal(gen.get_imports().splitlines(), []) def test_generate_c_type_with_docstring_no_self_arg(self) -> None: class TestClass: @@ -936,20 +915,16 @@ def test(self, arg0: str) -> None: """ output: list[str] = [] - imports: list[str] = [] mod = ModuleType(TestClass.__module__, "") - generate_c_function_stub( - mod, + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_function_stub( "test", TestClass.test, - output, - imports, - self_var="self", - class_name="TestClass", - sig_generators=get_sig_generators(parse_options([])), + output=output, + class_info=ClassInfo(self_var="self", cls=TestClass, name="TestClass"), ) assert_equal(output, ["def test(self, arg0: int) -> Any: ..."]) - assert_equal(imports, []) + assert_equal(gen.get_imports().splitlines(), []) def test_generate_c_type_classmethod(self) -> None: class TestClass: @@ -958,20 +933,16 @@ def test(cls, arg0: str) -> None: pass output: list[str] = [] - imports: list[str] = [] mod = ModuleType(TestClass.__module__, "") - generate_c_function_stub( - mod, + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_function_stub( "test", TestClass.test, - output, - imports, - self_var="cls", - class_name="TestClass", - sig_generators=get_sig_generators(parse_options([])), + output=output, + class_info=ClassInfo(self_var="cls", cls=TestClass, name="TestClass"), ) - assert_equal(output, ["@classmethod", "def test(cls, *args, **kwargs) -> Any: ..."]) - assert_equal(imports, []) + assert_equal(output, ["@classmethod", "def test(cls, *args, **kwargs): ..."]) + assert_equal(gen.get_imports().splitlines(), []) def test_generate_c_type_classmethod_with_overloads(self) -> None: class TestClass: @@ -984,17 +955,13 @@ def test(self, arg0: str) -> None: pass output: list[str] = [] - imports: list[str] = [] mod = ModuleType(TestClass.__module__, "") - generate_c_function_stub( - mod, + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_function_stub( "test", TestClass.test, - output, - imports, - self_var="cls", - class_name="TestClass", - sig_generators=get_sig_generators(parse_options([])), + output=output, + class_info=ClassInfo(self_var="cls", cls=TestClass, name="TestClass"), ) assert_equal( output, @@ -1007,7 +974,7 @@ def test(self, arg0: str) -> None: "def test(cls, arg0: int) -> Any: ...", ], ) - assert_equal(imports, ["from typing import overload"]) + assert_equal(gen.get_imports().splitlines(), ["from typing import overload"]) def test_generate_c_type_with_docstring_empty_default(self) -> None: class TestClass: @@ -1017,23 +984,20 @@ def test(self, arg0: str = "") -> None: """ output: list[str] = [] - imports: list[str] = [] mod = ModuleType(TestClass.__module__, "") - generate_c_function_stub( - mod, + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_function_stub( "test", TestClass.test, - output, - imports, - self_var="self", - class_name="TestClass", - sig_generators=get_sig_generators(parse_options([])), + output=output, + class_info=ClassInfo(self_var="self", cls=TestClass, name="TestClass"), ) assert_equal(output, ["def test(self, arg0: str = ...) -> Any: ..."]) - assert_equal(imports, []) + assert_equal(gen.get_imports().splitlines(), []) def test_generate_c_function_other_module_arg(self) -> None: """Test that if argument references type from other module, module will be imported.""" + # Provide different type in python spec than in docstring to make sure, that docstring # information is used. def test(arg0: str) -> None: @@ -1042,89 +1006,113 @@ def test(arg0: str) -> None: """ output: list[str] = [] - imports: list[str] = [] mod = ModuleType(self.__module__, "") - generate_c_function_stub( - mod, - "test", - test, - output, - imports, - sig_generators=get_sig_generators(parse_options([])), - ) + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_function_stub("test", test, output=output) assert_equal(output, ["def test(arg0: argparse.Action) -> Any: ..."]) - assert_equal(imports, ["import argparse"]) + assert_equal(gen.get_imports().splitlines(), ["import argparse"]) - def test_generate_c_function_same_module_arg(self) -> None: - """Test that if argument references type from same module but using full path, no module + def test_generate_c_function_same_module(self) -> None: + """Test that if annotation references type from same module but using full path, no module will be imported, and type specification will be striped to local reference. """ + # Provide different type in python spec than in docstring to make sure, that docstring # information is used. def test(arg0: str) -> None: """ - test(arg0: argparse.Action) + test(arg0: argparse.Action) -> argparse.Action """ output: list[str] = [] - imports: list[str] = [] mod = ModuleType("argparse", "") - generate_c_function_stub( - mod, - "test", - test, - output, - imports, - sig_generators=get_sig_generators(parse_options([])), - ) - assert_equal(output, ["def test(arg0: Action) -> Any: ..."]) - assert_equal(imports, []) + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_function_stub("test", test, output=output) + assert_equal(output, ["def test(arg0: Action) -> Action: ..."]) + assert_equal(gen.get_imports().splitlines(), []) - def test_generate_c_function_other_module_ret(self) -> None: - """Test that if return type references type from other module, module will be imported.""" + def test_generate_c_function_other_module(self) -> None: + """Test that if annotation references type from other module, module will be imported.""" def test(arg0: str) -> None: """ - test(arg0: str) -> argparse.Action + test(arg0: argparse.Action) -> argparse.Action """ output: list[str] = [] - imports: list[str] = [] mod = ModuleType(self.__module__, "") - generate_c_function_stub( - mod, - "test", - test, - output, - imports, - sig_generators=get_sig_generators(parse_options([])), - ) - assert_equal(output, ["def test(arg0: str) -> argparse.Action: ..."]) - assert_equal(imports, ["import argparse"]) + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_function_stub("test", test, output=output) + assert_equal(output, ["def test(arg0: argparse.Action) -> argparse.Action: ..."]) + assert_equal(gen.get_imports().splitlines(), ["import argparse"]) + + def test_generate_c_function_same_module_nested(self) -> None: + """Test that if annotation references type from same module but using full path, no module + will be imported, and type specification will be stripped to local reference. + """ + + # Provide different type in python spec than in docstring to make sure, that docstring + # information is used. + def test(arg0: str) -> None: + """ + test(arg0: list[argparse.Action]) -> list[argparse.Action] + """ - def test_generate_c_function_same_module_ret(self) -> None: - """Test that if return type references type from same module but using full path, - no module will be imported, and type specification will be striped to local reference. + output: list[str] = [] + mod = ModuleType("argparse", "") + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_function_stub("test", test, output=output) + assert_equal(output, ["def test(arg0: list[Action]) -> list[Action]: ..."]) + assert_equal(gen.get_imports().splitlines(), []) + + def test_generate_c_function_same_module_compound(self) -> None: + """Test that if annotation references type from same module but using full path, no module + will be imported, and type specification will be stripped to local reference. """ + # Provide different type in python spec than in docstring to make sure, that docstring + # information is used. def test(arg0: str) -> None: """ - test(arg0: str) -> argparse.Action + test(arg0: Union[argparse.Action, NoneType]) -> Tuple[argparse.Action, NoneType] """ output: list[str] = [] - imports: list[str] = [] mod = ModuleType("argparse", "") - generate_c_function_stub( - mod, - "test", - test, - output, - imports, - sig_generators=get_sig_generators(parse_options([])), + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_function_stub("test", test, output=output) + assert_equal(output, ["def test(arg0: Union[Action, None]) -> Tuple[Action, None]: ..."]) + assert_equal(gen.get_imports().splitlines(), []) + + def test_generate_c_function_other_module_nested(self) -> None: + """Test that if annotation references type from other module, module will be imported, + and the import will be restricted to one of the known modules.""" + + def test(arg0: str) -> None: + """ + test(arg0: foo.bar.Action) -> other.Thing + """ + + output: list[str] = [] + mod = ModuleType(self.__module__, "") + gen = InspectionStubGenerator( + mod.__name__, known_modules=["foo", "foo.spangle", "bar"], module=mod ) - assert_equal(output, ["def test(arg0: str) -> Action: ..."]) - assert_equal(imports, []) + gen.generate_function_stub("test", test, output=output) + assert_equal(output, ["def test(arg0: foo.bar.Action) -> other.Thing: ..."]) + assert_equal(gen.get_imports().splitlines(), ["import foo", "import other"]) + + def test_generate_c_function_no_crash_for_non_str_docstring(self) -> None: + def test(arg0: str) -> None: ... + + test.__doc__ = property(lambda self: "test(arg0: str) -> None") # type: ignore[assignment] + + output: list[str] = [] + mod = ModuleType(self.__module__, "") + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_function_stub("test", test, output=output) + assert_equal(output, ["def test(*args, **kwargs): ..."]) + assert_equal(gen.get_imports().splitlines(), []) def test_generate_c_property_with_pybind11(self) -> None: """Signatures included by PyBind11 inside property.fget are read.""" @@ -1139,13 +1127,15 @@ def get_attribute(self) -> None: readwrite_properties: list[str] = [] readonly_properties: list[str] = [] - generate_c_property_stub( + mod = ModuleType("module", "") # any module is fine + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_property_stub( "attribute", + TestClass.__dict__["attribute"], TestClass.attribute, [], readwrite_properties, readonly_properties, - is_c_property_readonly(TestClass.attribute), ) assert_equal(readwrite_properties, []) assert_equal(readonly_properties, ["@property", "def attribute(self) -> str: ..."]) @@ -1165,15 +1155,17 @@ def attribute(self, value: int) -> None: readwrite_properties: list[str] = [] readonly_properties: list[str] = [] - generate_c_property_stub( + mod = ModuleType("module", "") # any module is fine + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_property_stub( "attribute", - type(TestClass.attribute), + TestClass.__dict__["attribute"], + TestClass.attribute, [], readwrite_properties, readonly_properties, - is_c_property_readonly(TestClass.attribute), ) - assert_equal(readwrite_properties, ["attribute: Any"]) + assert_equal(readwrite_properties, ["attribute: Incomplete"]) assert_equal(readonly_properties, []) def test_generate_c_type_with_single_arg_generic(self) -> None: @@ -1184,20 +1176,16 @@ def test(self, arg0: str) -> None: """ output: list[str] = [] - imports: list[str] = [] mod = ModuleType(TestClass.__module__, "") - generate_c_function_stub( - mod, + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_function_stub( "test", TestClass.test, - output, - imports, - self_var="self", - class_name="TestClass", - sig_generators=get_sig_generators(parse_options([])), + output=output, + class_info=ClassInfo(self_var="self", cls=TestClass, name="TestClass"), ) assert_equal(output, ["def test(self, arg0: List[int]) -> Any: ..."]) - assert_equal(imports, []) + assert_equal(gen.get_imports().splitlines(), []) def test_generate_c_type_with_double_arg_generic(self) -> None: class TestClass: @@ -1207,20 +1195,16 @@ def test(self, arg0: str) -> None: """ output: list[str] = [] - imports: list[str] = [] mod = ModuleType(TestClass.__module__, "") - generate_c_function_stub( - mod, + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_function_stub( "test", TestClass.test, - output, - imports, - self_var="self", - class_name="TestClass", - sig_generators=get_sig_generators(parse_options([])), + output=output, + class_info=ClassInfo(self_var="self", cls=TestClass, name="TestClass"), ) - assert_equal(output, ["def test(self, arg0: Dict[str,int]) -> Any: ..."]) - assert_equal(imports, []) + assert_equal(output, ["def test(self, arg0: Dict[str, int]) -> Any: ..."]) + assert_equal(gen.get_imports().splitlines(), []) def test_generate_c_type_with_nested_generic(self) -> None: class TestClass: @@ -1230,20 +1214,16 @@ def test(self, arg0: str) -> None: """ output: list[str] = [] - imports: list[str] = [] mod = ModuleType(TestClass.__module__, "") - generate_c_function_stub( - mod, + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_function_stub( "test", TestClass.test, - output, - imports, - self_var="self", - class_name="TestClass", - sig_generators=get_sig_generators(parse_options([])), + output=output, + class_info=ClassInfo(self_var="self", cls=TestClass, name="TestClass"), ) - assert_equal(output, ["def test(self, arg0: Dict[str,List[int]]) -> Any: ..."]) - assert_equal(imports, []) + assert_equal(output, ["def test(self, arg0: Dict[str, List[int]]) -> Any: ..."]) + assert_equal(gen.get_imports().splitlines(), []) def test_generate_c_type_with_generic_using_other_module_first(self) -> None: class TestClass: @@ -1253,20 +1233,16 @@ def test(self, arg0: str) -> None: """ output: list[str] = [] - imports: list[str] = [] mod = ModuleType(TestClass.__module__, "") - generate_c_function_stub( - mod, + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_function_stub( "test", TestClass.test, - output, - imports, - self_var="self", - class_name="TestClass", - sig_generators=get_sig_generators(parse_options([])), + output=output, + class_info=ClassInfo(self_var="self", cls=TestClass, name="TestClass"), ) - assert_equal(output, ["def test(self, arg0: Dict[argparse.Action,int]) -> Any: ..."]) - assert_equal(imports, ["import argparse"]) + assert_equal(output, ["def test(self, arg0: Dict[argparse.Action, int]) -> Any: ..."]) + assert_equal(gen.get_imports().splitlines(), ["import argparse"]) def test_generate_c_type_with_generic_using_other_module_last(self) -> None: class TestClass: @@ -1276,20 +1252,16 @@ def test(self, arg0: str) -> None: """ output: list[str] = [] - imports: list[str] = [] mod = ModuleType(TestClass.__module__, "") - generate_c_function_stub( - mod, + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_function_stub( "test", TestClass.test, - output, - imports, - self_var="self", - class_name="TestClass", - sig_generators=get_sig_generators(parse_options([])), + output=output, + class_info=ClassInfo(self_var="self", cls=TestClass, name="TestClass"), ) - assert_equal(output, ["def test(self, arg0: Dict[str,argparse.Action]) -> Any: ..."]) - assert_equal(imports, ["import argparse"]) + assert_equal(output, ["def test(self, arg0: Dict[str, argparse.Action]) -> Any: ..."]) + assert_equal(gen.get_imports().splitlines(), ["import argparse"]) def test_generate_c_type_with_overload_pybind11(self) -> None: class TestClass: @@ -1304,17 +1276,13 @@ def __init__(self, arg0: str) -> None: """ output: list[str] = [] - imports: list[str] = [] mod = ModuleType(TestClass.__module__, "") - generate_c_function_stub( - mod, + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_function_stub( "__init__", TestClass.__init__, - output, - imports, - self_var="self", - class_name="TestClass", - sig_generators=get_sig_generators(parse_options([])), + output=output, + class_info=ClassInfo(self_var="self", cls=TestClass, name="TestClass"), ) assert_equal( output, @@ -1327,7 +1295,42 @@ def __init__(self, arg0: str) -> None: "def __init__(self, *args, **kwargs) -> Any: ...", ], ) - assert_equal(set(imports), {"from typing import overload"}) + assert_equal(gen.get_imports().splitlines(), ["from typing import overload"]) + + def test_generate_c_type_with_overload_shiboken(self) -> None: + class TestClass: + """ + TestClass(self: TestClass, arg0: str) -> None + TestClass(self: TestClass, arg0: str, arg1: str) -> None + """ + + def __init__(self, arg0: str) -> None: + pass + + output: list[str] = [] + mod = ModuleType(TestClass.__module__, "") + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.generate_function_stub( + "__init__", + TestClass.__init__, + output=output, + class_info=ClassInfo( + self_var="self", + cls=TestClass, + name="TestClass", + docstring=getattr(TestClass, "__doc__", None), + ), + ) + assert_equal( + output, + [ + "@overload", + "def __init__(self, arg0: str) -> None: ...", + "@overload", + "def __init__(self, arg0: str, arg1: str) -> None: ...", + ], + ) + assert_equal(gen.get_imports().splitlines(), ["from typing import overload"]) class ArgSigSuite(unittest.TestCase): diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 42dd40d76414..418308e2e65e 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -19,7 +19,7 @@ @contextlib.contextmanager def use_tmp_dir(mod_name: str) -> Iterator[str]: current = os.getcwd() - current_syspath = sys.path[:] + current_syspath = sys.path.copy() with tempfile.TemporaryDirectory() as tmp: try: os.chdir(tmp) @@ -27,7 +27,7 @@ def use_tmp_dir(mod_name: str) -> Iterator[str]: sys.path.insert(0, tmp) yield tmp finally: - sys.path = current_syspath[:] + sys.path = current_syspath.copy() if mod_name in sys.modules: del sys.modules[mod_name] @@ -64,11 +64,15 @@ def __init__(self, name: str) -> None: ... class Coroutine(Generic[_T_co, _S, _R]): ... class Iterable(Generic[_T_co]): ... +class Iterator(Iterable[_T_co]): ... class Mapping(Generic[_K, _V]): ... class Match(Generic[AnyStr]): ... class Sequence(Iterable[_T_co]): ... class Tuple(Sequence[_T_co]): ... +class NamedTuple(tuple[Any, ...]): ... def overload(func: _T) -> _T: ... +def type_check_only(func: _T) -> _T: ... +def final(func: _T) -> _T: ... """ stubtest_builtins_stub = """ @@ -82,9 +86,12 @@ def overload(func: _T) -> _T: ... class object: __module__: str def __init__(self) -> None: pass + def __repr__(self) -> str: pass class type: ... -class tuple(Sequence[T_co], Generic[T_co]): ... +class tuple(Sequence[T_co], Generic[T_co]): + def __ge__(self, __other: tuple[T_co, ...]) -> bool: pass + class dict(Mapping[KT, VT]): ... class function: pass @@ -103,6 +110,39 @@ def classmethod(f: T) -> T: ... def staticmethod(f: T) -> T: ... """ +stubtest_enum_stub = """ +import sys +from typing import Any, TypeVar, Iterator + +_T = TypeVar('_T') + +class EnumMeta(type): + def __len__(self) -> int: pass + def __iter__(self: type[_T]) -> Iterator[_T]: pass + def __reversed__(self: type[_T]) -> Iterator[_T]: pass + def __getitem__(self: type[_T], name: str) -> _T: pass + +class Enum(metaclass=EnumMeta): + def __new__(cls: type[_T], value: object) -> _T: pass + def __repr__(self) -> str: pass + def __str__(self) -> str: pass + def __format__(self, format_spec: str) -> str: pass + def __hash__(self) -> Any: pass + def __reduce_ex__(self, proto: Any) -> Any: pass + name: str + value: Any + +class Flag(Enum): + def __or__(self: _T, other: _T) -> _T: pass + def __and__(self: _T, other: _T) -> _T: pass + def __xor__(self: _T, other: _T) -> _T: pass + def __invert__(self: _T) -> _T: pass + if sys.version_info >= (3, 11): + __ror__ = __or__ + __rand__ = __and__ + __rxor__ = __xor__ +""" + def run_stubtest( stub: str, runtime: str, options: list[str], config_file: str | None = None @@ -112,6 +152,8 @@ def run_stubtest( f.write(stubtest_builtins_stub) with open("typing.pyi", "w") as f: f.write(stubtest_typing_stub) + with open("enum.pyi", "w") as f: + f.write(stubtest_enum_stub) with open(f"{TEST_MODULE_NAME}.pyi", "w") as f: f.write(stub) with open(f"{TEST_MODULE_NAME}.py", "w") as f: @@ -123,16 +165,15 @@ def run_stubtest( output = io.StringIO() with contextlib.redirect_stdout(output): test_stubs(parse_options([TEST_MODULE_NAME] + options), use_builtins_fixtures=True) - # remove cwd as it's not available from outside - return ( + return remove_color_code( output.getvalue() - .replace(os.path.realpath(tmp_dir) + os.sep, "") - .replace(tmp_dir + os.sep, "") + # remove cwd as it's not available from outside + .replace(os.path.realpath(tmp_dir) + os.sep, "").replace(tmp_dir + os.sep, "") ) class Case: - def __init__(self, stub: str, runtime: str, error: str | None): + def __init__(self, stub: str, runtime: str, error: str | None) -> None: self.stub = stub self.runtime = runtime self.error = error @@ -169,7 +210,13 @@ def test(*args: Any, **kwargs: Any) -> None: ) actual_errors = set(output.splitlines()) - assert actual_errors == expected_errors, output + if actual_errors != expected_errors: + output = run_stubtest( + stub="\n\n".join(textwrap.dedent(c.stub.lstrip("\n")) for c in cases), + runtime="\n\n".join(textwrap.dedent(c.runtime.lstrip("\n")) for c in cases), + options=[], + ) + assert actual_errors == expected_errors, output return test @@ -233,17 +280,16 @@ def test_arg_name(self) -> Iterator[Case]: runtime="def bad(num, text) -> None: pass", error="bad", ) - if sys.version_info >= (3, 8): - yield Case( - stub="def good_posonly(__number: int, text: str) -> None: ...", - runtime="def good_posonly(num, /, text): pass", - error=None, - ) - yield Case( - stub="def bad_posonly(__number: int, text: str) -> None: ...", - runtime="def bad_posonly(flag, /, text): pass", - error="bad_posonly", - ) + yield Case( + stub="def good_posonly(__number: int, text: str) -> None: ...", + runtime="def good_posonly(num, /, text): pass", + error=None, + ) + yield Case( + stub="def bad_posonly(__number: int, text: str) -> None: ...", + runtime="def bad_posonly(flag, /, text): pass", + error="bad_posonly", + ) yield Case( stub=""" class BadMethod: @@ -284,22 +330,93 @@ def test_arg_kind(self) -> Iterator[Case]: runtime="def stub_posonly(number, text): pass", error="stub_posonly", ) - if sys.version_info >= (3, 8): - yield Case( - stub="def good_posonly(__number: int, text: str) -> None: ...", - runtime="def good_posonly(number, /, text): pass", - error=None, - ) - yield Case( - stub="def runtime_posonly(number: int, text: str) -> None: ...", - runtime="def runtime_posonly(number, /, text): pass", - error="runtime_posonly", - ) - yield Case( - stub="def stub_posonly_570(number: int, /, text: str) -> None: ...", - runtime="def stub_posonly_570(number, text): pass", - error="stub_posonly_570", - ) + yield Case( + stub="def good_posonly(__number: int, text: str) -> None: ...", + runtime="def good_posonly(number, /, text): pass", + error=None, + ) + yield Case( + stub="def runtime_posonly(number: int, text: str) -> None: ...", + runtime="def runtime_posonly(number, /, text): pass", + error="runtime_posonly", + ) + yield Case( + stub="def stub_posonly_570(number: int, /, text: str) -> None: ...", + runtime="def stub_posonly_570(number, text): pass", + error="stub_posonly_570", + ) + + @collect_cases + def test_private_parameters(self) -> Iterator[Case]: + # Private parameters can optionally be omitted. + yield Case( + stub="def priv_pos_arg_missing() -> None: ...", + runtime="def priv_pos_arg_missing(_p1=None): pass", + error=None, + ) + yield Case( + stub="def multi_priv_args() -> None: ...", + runtime="def multi_priv_args(_p='', _q=''): pass", + error=None, + ) + yield Case( + stub="def priv_kwarg_missing() -> None: ...", + runtime="def priv_kwarg_missing(*, _p2=''): pass", + error=None, + ) + # But if they are included, they must be correct. + yield Case( + stub="def priv_pos_arg_wrong(_p: int = ...) -> None: ...", + runtime="def priv_pos_arg_wrong(_p=None): pass", + error="priv_pos_arg_wrong", + ) + yield Case( + stub="def priv_kwarg_wrong(*, _p: int = ...) -> None: ...", + runtime="def priv_kwarg_wrong(*, _p=None): pass", + error="priv_kwarg_wrong", + ) + # Private parameters must have a default and start with exactly one + # underscore. + yield Case( + stub="def pos_arg_no_default() -> None: ...", + runtime="def pos_arg_no_default(_np): pass", + error="pos_arg_no_default", + ) + yield Case( + stub="def kwarg_no_default() -> None: ...", + runtime="def kwarg_no_default(*, _np): pass", + error="kwarg_no_default", + ) + yield Case( + stub="def double_underscore_pos_arg() -> None: ...", + runtime="def double_underscore_pos_arg(__np = None): pass", + error="double_underscore_pos_arg", + ) + yield Case( + stub="def double_underscore_kwarg() -> None: ...", + runtime="def double_underscore_kwarg(*, __np = None): pass", + error="double_underscore_kwarg", + ) + # But spot parameters that are accidentally not marked kw-only and + # vice-versa. + yield Case( + stub="def priv_arg_is_kwonly(_p=...) -> None: ...", + runtime="def priv_arg_is_kwonly(*, _p=''): pass", + error="priv_arg_is_kwonly", + ) + yield Case( + stub="def priv_arg_is_positional(*, _p=...) -> None: ...", + runtime="def priv_arg_is_positional(_p=''): pass", + error="priv_arg_is_positional", + ) + # Private parameters not at the end of the parameter list must be + # included so that users can pass the following arguments using + # positional syntax. + yield Case( + stub="def priv_args_not_at_end(*, q='') -> None: ...", + runtime="def priv_args_not_at_end(_p='', q=''): pass", + error="priv_args_not_at_end", + ) @collect_cases def test_default_presence(self) -> Iterator[Case]: @@ -389,6 +506,16 @@ def test_default_value(self) -> Iterator[Case]: error=None, ) + # Simulate "" + yield Case( + stub="def f11() -> None: ...", + runtime=""" + def f11(text=None) -> None: pass + f11.__text_signature__ = "(text=)" + """, + error="f11", + ) + @collect_cases def test_static_class_method(self) -> Iterator[Case]: yield Case( @@ -583,17 +710,84 @@ def f4(a: str, *args, b: int, **kwargs) -> str: ... runtime="def f4(a, *args, b, **kwargs): pass", error=None, ) - if sys.version_info >= (3, 8): - yield Case( - stub=""" + yield Case( + stub=""" + @overload + def f5(__a: int) -> int: ... + @overload + def f5(__b: str) -> str: ... + """, + runtime="def f5(x, /): pass", + error=None, + ) + yield Case( + stub=""" + from typing import final + from typing_extensions import deprecated + class Foo: @overload - def f5(__a: int) -> int: ... + @final + def f6(self, __a: int) -> int: ... @overload - def f5(__b: str) -> str: ... - """, - runtime="def f5(x, /): pass", - error=None, - ) + @deprecated("evil") + def f6(self, __b: str) -> str: ... + """, + runtime=""" + class Foo: + def f6(self, x, /): pass + """, + error=None, + ) + yield Case( + stub=""" + @overload + def f7(a: int, /) -> int: ... + @overload + def f7(b: str, /) -> str: ... + """, + runtime="def f7(x, /): pass", + error=None, + ) + yield Case( + stub=""" + @overload + def f8(a: int, c: int = 0, /) -> int: ... + @overload + def f8(b: str, d: int, /) -> str: ... + """, + runtime="def f8(x, y, /): pass", + error="f8", + ) + yield Case( + stub=""" + @overload + def f9(a: int, c: int = 0, /) -> int: ... + @overload + def f9(b: str, d: int, /) -> str: ... + """, + runtime="def f9(x, y=0, /): pass", + error=None, + ) + yield Case( + stub=""" + class Bar: + @overload + def f1(self) -> int: ... + @overload + def f1(self, a: int, /) -> int: ... + + @overload + def f2(self, a: int, /) -> int: ... + @overload + def f2(self, a: str, /) -> int: ... + """, + runtime=""" + class Bar: + def f1(self, *a) -> int: ... + def f2(self, *a) -> int: ... + """, + error=None, + ) @collect_cases def test_property(self) -> Iterator[Case]: @@ -765,6 +959,20 @@ def read_write_attr(self, val): self._val = val @collect_cases def test_type_alias(self) -> Iterator[Case]: + yield Case( + stub=""" + import collections.abc + import re + import typing + from typing import Callable, Dict, Generic, Iterable, List, Match, Tuple, TypeVar, Union + """, + runtime=""" + import collections.abc + import re + from typing import Callable, Dict, Generic, Iterable, List, Match, Tuple, TypeVar, Union + """, + error=None, + ) yield Case( stub=""" class X: @@ -778,27 +986,18 @@ class Y: ... """, error="Y.f", ) - yield Case( - stub=""" - from typing import Tuple - A = Tuple[int, str] - """, - runtime="A = (int, str)", - error="A", - ) + yield Case(stub="A = Tuple[int, str]", runtime="A = (int, str)", error="A") # Error if an alias isn't present at runtime... yield Case(stub="B = str", runtime="", error="B") # ... but only if the alias isn't private yield Case(stub="_C = int", runtime="", error=None) yield Case( stub=""" - from typing import Tuple D = tuple[str, str] E = Tuple[int, int, int] F = Tuple[str, int] """, runtime=""" - from typing import List, Tuple D = Tuple[str, str] E = Tuple[int, int, int] F = List[str] @@ -807,13 +1006,11 @@ class Y: ... ) yield Case( stub=""" - from typing import Union G = str | int H = Union[str, bool] I = str | int """, runtime=""" - from typing import Union G = Union[str, int] H = Union[str, bool] I = str @@ -822,16 +1019,12 @@ class Y: ... ) yield Case( stub=""" - import typing - from collections.abc import Iterable - from typing import Dict K = dict[str, str] L = Dict[int, int] - KK = Iterable[str] + KK = collections.abc.Iterable[str] LL = typing.Iterable[str] """, runtime=""" - from typing import Iterable, Dict K = Dict[str, str] L = Dict[int, int] KK = Iterable[str] @@ -841,14 +1034,12 @@ class Y: ... ) yield Case( stub=""" - from typing import Generic, TypeVar _T = TypeVar("_T") class _Spam(Generic[_T]): def foo(self) -> None: ... IntFood = _Spam[int] """, runtime=""" - from typing import Generic, TypeVar _T = TypeVar("_T") class _Bacon(Generic[_T]): def foo(self, arg): pass @@ -859,14 +1050,11 @@ def foo(self, arg): pass yield Case(stub="StrList = list[str]", runtime="StrList = ['foo', 'bar']", error="StrList") yield Case( stub=""" - import collections.abc - from typing import Callable - N = Callable[[str], bool] + N = typing.Callable[[str], bool] O = collections.abc.Callable[[int], str] - P = Callable[[str], bool] + P = typing.Callable[[str], bool] """, runtime=""" - from typing import Callable N = Callable[[str], bool] O = Callable[[int], str] P = int @@ -897,17 +1085,7 @@ class Bar: pass """, error=None, ) - yield Case( - stub=""" - from typing import Match - M = Match[str] - """, - runtime=""" - from typing import Match - M = Match[str] - """, - error=None, - ) + yield Case(stub="M = Match[str]", runtime="M = Match[str]", error=None) yield Case( stub=""" class Baz: @@ -940,53 +1118,47 @@ def fizz(self): pass if sys.version_info >= (3, 10): yield Case( stub=""" - import collections.abc - import re - from typing import Callable, Dict, Match, Iterable, Tuple, Union Q = Dict[str, str] R = dict[int, int] S = Tuple[int, int] T = tuple[str, str] U = int | str V = Union[int, str] - W = Callable[[str], bool] + W = typing.Callable[[str], bool] Z = collections.abc.Callable[[str], bool] - QQ = Iterable[str] + QQ = typing.Iterable[str] RR = collections.abc.Iterable[str] - MM = Match[str] + MM = typing.Match[str] MMM = re.Match[str] """, runtime=""" - from collections.abc import Callable, Iterable - from re import Match Q = dict[str, str] R = dict[int, int] S = tuple[int, int] T = tuple[str, str] U = int | str V = int | str - W = Callable[[str], bool] - Z = Callable[[str], bool] - QQ = Iterable[str] - RR = Iterable[str] - MM = Match[str] - MMM = Match[str] + W = collections.abc.Callable[[str], bool] + Z = collections.abc.Callable[[str], bool] + QQ = collections.abc.Iterable[str] + RR = collections.abc.Iterable[str] + MM = re.Match[str] + MMM = re.Match[str] """, error=None, ) @collect_cases def test_enum(self) -> Iterator[Case]: + yield Case(stub="import enum", runtime="import enum", error=None) yield Case( stub=""" - import enum class X(enum.Enum): a: int b: str c: str """, runtime=""" - import enum class X(enum.Enum): a = 1 b = "asdf" @@ -994,6 +1166,105 @@ class X(enum.Enum): """, error="X.c", ) + yield Case( + stub=""" + class Flags1(enum.Flag): + a: int + b: int + def foo(x: Flags1 = ...) -> None: ... + """, + runtime=""" + class Flags1(enum.Flag): + a = 1 + b = 2 + def foo(x=Flags1.a|Flags1.b): pass + """, + error=None, + ) + yield Case( + stub=""" + class Flags2(enum.Flag): + a: int + b: int + def bar(x: Flags2 | None = None) -> None: ... + """, + runtime=""" + class Flags2(enum.Flag): + a = 1 + b = 2 + def bar(x=Flags2.a|Flags2.b): pass + """, + error="bar", + ) + yield Case( + stub=""" + class Flags3(enum.Flag): + a: int + b: int + def baz(x: Flags3 | None = ...) -> None: ... + """, + runtime=""" + class Flags3(enum.Flag): + a = 1 + b = 2 + def baz(x=Flags3(0)): pass + """, + error=None, + ) + yield Case( + runtime=""" + import enum + class SomeObject: ... + + class WeirdEnum(enum.Enum): + a = SomeObject() + b = SomeObject() + """, + stub=""" + import enum + class SomeObject: ... + class WeirdEnum(enum.Enum): + _value_: SomeObject + a = ... + b = ... + """, + error=None, + ) + yield Case( + stub=""" + class Flags4(enum.Flag): + a: int + b: int + def spam(x: Flags4 | None = None) -> None: ... + """, + runtime=""" + class Flags4(enum.Flag): + a = 1 + b = 2 + def spam(x=Flags4(0)): pass + """, + error="spam", + ) + yield Case( + stub=""" + from typing_extensions import Final, Literal + class BytesEnum(bytes, enum.Enum): + a: bytes + FOO: Literal[BytesEnum.a] + BAR: Final = BytesEnum.a + BAZ: BytesEnum + EGGS: bytes + """, + runtime=""" + class BytesEnum(bytes, enum.Enum): + a = b'foo' + FOO = BytesEnum.a + BAR = BytesEnum.a + BAZ = BytesEnum.a + EGGS = BytesEnum.a + """, + error=None, + ) @collect_cases def test_decorator(self) -> Iterator[Case]: @@ -1086,6 +1357,24 @@ def test_missing_no_runtime_all(self) -> Iterator[Case]: yield Case(stub="", runtime="from json.scanner import NUMBER_RE", error=None) yield Case(stub="", runtime="from string import ascii_letters", error=None) + @collect_cases + def test_missing_no_runtime_all_terrible(self) -> Iterator[Case]: + yield Case( + stub="", + runtime=""" +import sys +import types +import __future__ +_m = types.SimpleNamespace() +_m.annotations = __future__.annotations +sys.modules["_terrible_stubtest_test_module"] = _m + +from _terrible_stubtest_test_module import * +assert annotations +""", + error=None, + ) + @collect_cases def test_non_public_1(self) -> Iterator[Case]: yield Case( @@ -1139,6 +1428,263 @@ def test_not_subclassable(self) -> Iterator[Case]: error="CannotBeSubclassed", ) + @collect_cases + def test_has_runtime_final_decorator(self) -> Iterator[Case]: + yield Case( + stub="from typing_extensions import final", + runtime=""" + import functools + from typing_extensions import final + """, + error=None, + ) + yield Case( + stub=""" + @final + class A: ... + """, + runtime=""" + @final + class A: ... + """, + error=None, + ) + yield Case( # Runtime can miss `@final` decorator + stub=""" + @final + class B: ... + """, + runtime=""" + class B: ... + """, + error=None, + ) + yield Case( # Stub cannot miss `@final` decorator + stub=""" + class C: ... + """, + runtime=""" + @final + class C: ... + """, + error="C", + ) + yield Case( + stub=""" + class D: + @final + def foo(self) -> None: ... + @final + @staticmethod + def bar() -> None: ... + @staticmethod + @final + def bar2() -> None: ... + @final + @classmethod + def baz(cls) -> None: ... + @classmethod + @final + def baz2(cls) -> None: ... + @property + @final + def eggs(self) -> int: ... + @final + @property + def eggs2(self) -> int: ... + @final + def ham(self, obj: int) -> int: ... + """, + runtime=""" + class D: + @final + def foo(self): pass + @final + @staticmethod + def bar(): pass + @staticmethod + @final + def bar2(): pass + @final + @classmethod + def baz(cls): pass + @classmethod + @final + def baz2(cls): pass + @property + @final + def eggs(self): return 42 + @final + @property + def eggs2(self): pass + @final + @functools.lru_cache() + def ham(self, obj): return obj * 2 + """, + error=None, + ) + # Stub methods are allowed to have @final even if the runtime doesn't... + yield Case( + stub=""" + class E: + @final + def foo(self) -> None: ... + @final + @staticmethod + def bar() -> None: ... + @staticmethod + @final + def bar2() -> None: ... + @final + @classmethod + def baz(cls) -> None: ... + @classmethod + @final + def baz2(cls) -> None: ... + @property + @final + def eggs(self) -> int: ... + @final + @property + def eggs2(self) -> int: ... + @final + def ham(self, obj: int) -> int: ... + """, + runtime=""" + class E: + def foo(self): pass + @staticmethod + def bar(): pass + @staticmethod + def bar2(): pass + @classmethod + def baz(cls): pass + @classmethod + def baz2(cls): pass + @property + def eggs(self): return 42 + @property + def eggs2(self): return 42 + @functools.lru_cache() + def ham(self, obj): return obj * 2 + """, + error=None, + ) + # ...But if the runtime has @final, the stub must have it as well + yield Case( + stub=""" + class F: + def foo(self) -> None: ... + """, + runtime=""" + class F: + @final + def foo(self): pass + """, + error="F.foo", + ) + yield Case( + stub=""" + class G: + @staticmethod + def foo() -> None: ... + """, + runtime=""" + class G: + @final + @staticmethod + def foo(): pass + """, + error="G.foo", + ) + yield Case( + stub=""" + class H: + @staticmethod + def foo() -> None: ... + """, + runtime=""" + class H: + @staticmethod + @final + def foo(): pass + """, + error="H.foo", + ) + yield Case( + stub=""" + class I: + @classmethod + def foo(cls) -> None: ... + """, + runtime=""" + class I: + @final + @classmethod + def foo(cls): pass + """, + error="I.foo", + ) + yield Case( + stub=""" + class J: + @classmethod + def foo(cls) -> None: ... + """, + runtime=""" + class J: + @classmethod + @final + def foo(cls): pass + """, + error="J.foo", + ) + yield Case( + stub=""" + class K: + @property + def foo(self) -> int: ... + """, + runtime=""" + class K: + @property + @final + def foo(self): return 42 + """, + error="K.foo", + ) + # This test wouldn't pass, + # because the runtime can't set __final__ on instances of builtins.property, + # so stubtest has non way of knowing that the runtime was decorated with @final: + # + # yield Case( + # stub=""" + # class K2: + # @property + # def foo(self) -> int: ... + # """, + # runtime=""" + # class K2: + # @final + # @property + # def foo(self): return 42 + # """, + # error="K2.foo", + # ) + yield Case( + stub=""" + class L: + def foo(self, obj: int) -> int: ... + """, + runtime=""" + class L: + @final + @functools.lru_cache() + def foo(self, obj): return obj * 2 + """, + error="L.foo", + ) + @collect_cases def test_name_mangling(self) -> Iterator[Case]: yield Case( @@ -1336,24 +1882,102 @@ class _Options(TypedDict): ) @collect_cases - def test_protocol(self) -> Iterator[Case]: + def test_runtime_typing_objects(self) -> Iterator[Case]: + yield Case( + stub="from typing_extensions import Protocol, TypedDict", + runtime="from typing_extensions import Protocol, TypedDict", + error=None, + ) yield Case( stub=""" - from typing_extensions import Protocol - class X(Protocol): bar: int def foo(self, x: int, y: bytes = ...) -> str: ... """, runtime=""" - from typing_extensions import Protocol - class X(Protocol): bar: int def foo(self, x: int, y: bytes = ...) -> str: ... """, error=None, ) + yield Case( + stub=""" + class Y(TypedDict): + a: int + """, + runtime=""" + class Y(TypedDict): + a: int + """, + error=None, + ) + + @collect_cases + def test_named_tuple(self) -> Iterator[Case]: + yield Case( + stub="from typing import NamedTuple", + runtime="from typing import NamedTuple", + error=None, + ) + yield Case( + stub=""" + class X1(NamedTuple): + bar: int + foo: str = ... + """, + runtime=""" + class X1(NamedTuple): + bar: int + foo: str = 'a' + """, + error=None, + ) + yield Case( + stub=""" + class X2(NamedTuple): + bar: int + foo: str + """, + runtime=""" + class X2(NamedTuple): + bar: int + foo: str = 'a' + """, + # `__new__` will miss a default value for a `foo` parameter, + # but we don't generate special errors for `foo` missing `...` part. + error="X2.__new__", + ) + + @collect_cases + def test_named_tuple_typing_and_collections(self) -> Iterator[Case]: + yield Case( + stub="from typing import NamedTuple", + runtime="from collections import namedtuple", + error=None, + ) + yield Case( + stub=""" + class X1(NamedTuple): + bar: int + foo: str = ... + """, + runtime=""" + X1 = namedtuple('X1', ['bar', 'foo'], defaults=['a']) + """, + error=None, + ) + yield Case( + stub=""" + class X2(NamedTuple): + bar: int + foo: str + """, + runtime=""" + X2 = namedtuple('X1', ['bar', 'foo'], defaults=['a']) + """, + error="X2.__new__", + ) @collect_cases def test_type_var(self) -> Iterator[Case]: @@ -1436,7 +2060,10 @@ def test_metaclass_abcmeta(self) -> Iterator[Case]: @collect_cases def test_abstract_methods(self) -> Iterator[Case]: yield Case( - stub="from abc import abstractmethod", + stub=""" + from abc import abstractmethod + from typing import overload + """, runtime="from abc import abstractmethod", error=None, ) @@ -1465,15 +2092,64 @@ def some(self) -> None: ... """, error=None, ) - # Runtime can miss `@abstractmethod`: yield Case( stub=""" class A3: + @overload + def some(self, other: int) -> str: ... + @overload + def some(self, other: str) -> int: ... + """, + runtime=""" + class A3: + @abstractmethod + def some(self, other) -> None: ... + """, + error="A3.some", + ) + yield Case( + stub=""" + class A4: + @overload + @abstractmethod + def some(self, other: int) -> str: ... + @overload + @abstractmethod + def some(self, other: str) -> int: ... + """, + runtime=""" + class A4: + @abstractmethod + def some(self, other) -> None: ... + """, + error=None, + ) + yield Case( + stub=""" + class A5: + @abstractmethod + @overload + def some(self, other: int) -> str: ... + @abstractmethod + @overload + def some(self, other: str) -> int: ... + """, + runtime=""" + class A5: + @abstractmethod + def some(self, other) -> None: ... + """, + error=None, + ) + # Runtime can miss `@abstractmethod`: + yield Case( + stub=""" + class A6: @abstractmethod def some(self) -> None: ... """, runtime=""" - class A3: + class A6: def some(self) -> None: ... """, error=None, @@ -1546,6 +2222,72 @@ def some(self) -> int: ... error=None, ) + @collect_cases + def test_type_check_only(self) -> Iterator[Case]: + yield Case( + stub="from typing import type_check_only, overload", + runtime="from typing import overload", + error=None, + ) + # You can have public types that are only defined in stubs + # with `@type_check_only`: + yield Case( + stub=""" + @type_check_only + class A1: ... + """, + runtime="", + error=None, + ) + # Having `@type_check_only` on a type that exists at runtime is an error + yield Case( + stub=""" + @type_check_only + class A2: ... + """, + runtime="class A2: ...", + error="A2", + ) + # The same is true for NamedTuples and TypedDicts: + yield Case( + stub="from typing_extensions import NamedTuple, TypedDict", + runtime="from typing_extensions import NamedTuple, TypedDict", + error=None, + ) + yield Case( + stub=""" + @type_check_only + class NT1(NamedTuple): ... + """, + runtime="class NT1(NamedTuple): ...", + error="NT1", + ) + yield Case( + stub=""" + @type_check_only + class TD1(TypedDict): ... + """, + runtime="class TD1(TypedDict): ...", + error="TD1", + ) + # The same is true for functions: + yield Case( + stub=""" + @type_check_only + def func1() -> None: ... + """, + runtime="", + error=None, + ) + yield Case( + stub=""" + @type_check_only + def func2() -> None: ... + """, + runtime="def func2() -> None: ...", + error="func2", + ) + def remove_color_code(s: str) -> str: return re.sub("\\x1b.*?m", "", s) # this works! @@ -1561,12 +2303,12 @@ def test_output(self) -> None: expected = ( f'error: {TEST_MODULE_NAME}.bad is inconsistent, stub argument "number" differs ' 'from runtime argument "num"\n' - f"Stub: at line 1 in file {TEST_MODULE_NAME}.pyi\n" + f"Stub: in file {TEST_MODULE_NAME}.pyi:1\n" "def (number: builtins.int, text: builtins.str)\n" - f"Runtime: at line 1 in file {TEST_MODULE_NAME}.py\ndef (num, text)\n\n" + f"Runtime: in file {TEST_MODULE_NAME}.py:1\ndef (num, text)\n\n" "Found 1 error (checked 1 module)\n" ) - assert remove_color_code(output) == expected + assert output == expected output = run_stubtest( stub="def bad(number: int, text: str) -> None: ...", @@ -1577,7 +2319,7 @@ def test_output(self) -> None: "{}.bad is inconsistent, " 'stub argument "number" differs from runtime argument "num"\n'.format(TEST_MODULE_NAME) ) - assert remove_color_code(output) == expected + assert output == expected def test_ignore_flags(self) -> None: output = run_stubtest( @@ -1649,20 +2391,20 @@ def also_bad(asdf): pass options=["--allowlist", allowlist.name, "--generate-allowlist"], ) assert output == ( - f"note: unused allowlist entry unused.*\n" f"{TEST_MODULE_NAME}.also_bad\n" + f"note: unused allowlist entry unused.*\n{TEST_MODULE_NAME}.also_bad\n" ) finally: os.unlink(allowlist.name) def test_mypy_build(self) -> None: output = run_stubtest(stub="+", runtime="", options=[]) - assert remove_color_code(output) == ( + assert output == ( "error: not checking stubs due to failed mypy compile:\n{}.pyi:1: " "error: invalid syntax [syntax]\n".format(TEST_MODULE_NAME) ) output = run_stubtest(stub="def f(): ...\ndef f(): ...", runtime="", options=[]) - assert remove_color_code(output) == ( + assert output == ( "error: not checking stubs due to mypy build errors:\n{}.pyi:2: " 'error: Name "f" already defined on line 1 [no-redef]\n'.format(TEST_MODULE_NAME) ) @@ -1714,14 +2456,22 @@ def f(a: int, b: int, *, c: int, d: int = 0, **kwargs: Any) -> None: == "def (a, b, *, c, d = ..., **kwargs)" ) + def test_builtin_signature_with_unrepresentable_default(self) -> None: + sig = mypy.stubtest.safe_inspect_signature(bytes.hex) + assert sig is not None + assert ( + str(mypy.stubtest.Signature.from_inspect_signature(sig)) + == "def (self, sep = ..., bytes_per_sep = ...)" + ) + def test_config_file(self) -> None: runtime = "temp = 5\n" stub = "from decimal import Decimal\ntemp: Decimal\n" config_file = f"[mypy]\nplugins={root_dir}/test-data/unit/plugins/decimal_to_int.py\n" output = run_stubtest(stub=stub, runtime=runtime, options=[]) - assert remove_color_code(output) == ( + assert output == ( f"error: {TEST_MODULE_NAME}.temp variable differs from runtime type Literal[5]\n" - f"Stub: at line 2 in file {TEST_MODULE_NAME}.pyi\n_decimal.Decimal\nRuntime:\n5\n\n" + f"Stub: in file {TEST_MODULE_NAME}.pyi:2\n_decimal.Decimal\nRuntime:\n5\n\n" "Found 1 error (checked 1 module)\n" ) output = run_stubtest(stub=stub, runtime=runtime, options=[], config_file=config_file) diff --git a/mypy/test/testsubtypes.py b/mypy/test/testsubtypes.py index c76a34ff00d7..480fe38a90a7 100644 --- a/mypy/test/testsubtypes.py +++ b/mypy/test/testsubtypes.py @@ -2,9 +2,9 @@ from mypy.nodes import CONTRAVARIANT, COVARIANT, INVARIANT from mypy.subtypes import is_subtype -from mypy.test.helpers import Suite, skip +from mypy.test.helpers import Suite from mypy.test.typefixture import InterfaceTypeFixture, TypeFixture -from mypy.types import Instance, TupleType, Type, UnpackType +from mypy.types import Instance, Type, UnpackType class SubtypingSuite(Suite): @@ -69,7 +69,6 @@ def test_interface_subtyping(self) -> None: self.assert_equivalent(self.fx.f, self.fx.f) self.assert_not_subtype(self.fx.a, self.fx.f) - @skip def test_generic_interface_subtyping(self) -> None: # TODO make this work fx2 = InterfaceTypeFixture() @@ -222,10 +221,6 @@ def test_type_var_tuple(self) -> None: Instance(self.fx.gvi, [UnpackType(self.fx.us)]), ) - self.assert_subtype( - Instance(self.fx.gvi, [UnpackType(self.fx.anyt)]), - Instance(self.fx.gvi, [self.fx.anyt]), - ) self.assert_not_subtype( Instance(self.fx.gvi, [UnpackType(self.fx.ss)]), Instance(self.fx.gvi, []) ) @@ -273,83 +268,8 @@ def test_type_var_tuple_with_prefix_suffix(self) -> None: Instance(self.fx.gvi, [self.fx.a, UnpackType(self.fx.ss), self.fx.b, self.fx.c]), ) - def test_type_var_tuple_unpacked_varlength_tuple(self) -> None: - self.assert_subtype( - Instance( - self.fx.gvi, - [ - UnpackType( - TupleType( - [self.fx.a, self.fx.b], - fallback=Instance(self.fx.std_tuplei, [self.fx.o]), - ) - ) - ], - ), - Instance(self.fx.gvi, [self.fx.a, self.fx.b]), - ) - - def test_type_var_tuple_unpacked_tuple(self) -> None: - self.assert_subtype( - Instance( - self.fx.gvi, - [ - UnpackType( - TupleType( - [self.fx.a, self.fx.b], - fallback=Instance(self.fx.std_tuplei, [self.fx.o]), - ) - ) - ], - ), - Instance(self.fx.gvi, [self.fx.a, self.fx.b]), - ) - self.assert_subtype( - Instance( - self.fx.gvi, - [ - UnpackType( - TupleType( - [self.fx.a, self.fx.b], - fallback=Instance(self.fx.std_tuplei, [self.fx.o]), - ) - ) - ], - ), - Instance(self.fx.gvi, [self.fx.anyt, self.fx.anyt]), - ) - self.assert_not_subtype( - Instance( - self.fx.gvi, - [ - UnpackType( - TupleType( - [self.fx.a, self.fx.b], - fallback=Instance(self.fx.std_tuplei, [self.fx.o]), - ) - ) - ], - ), - Instance(self.fx.gvi, [self.fx.a]), - ) - self.assert_not_subtype( - Instance( - self.fx.gvi, - [ - UnpackType( - TupleType( - [self.fx.a, self.fx.b], - fallback=Instance(self.fx.std_tuplei, [self.fx.o]), - ) - ) - ], - ), - # Order flipped here. - Instance(self.fx.gvi, [self.fx.b, self.fx.a]), - ) - def test_type_var_tuple_unpacked_variable_length_tuple(self) -> None: - self.assert_equivalent( + self.assert_subtype( Instance(self.fx.gvi, [self.fx.a, self.fx.a]), Instance(self.fx.gvi, [UnpackType(Instance(self.fx.std_tuplei, [self.fx.a]))]), ) diff --git a/mypy/test/testtransform.py b/mypy/test/testtransform.py index 1d3d4468444e..9388dca02c7a 100644 --- a/mypy/test/testtransform.py +++ b/mypy/test/testtransform.py @@ -2,12 +2,9 @@ from __future__ import annotations -import os.path - from mypy import build from mypy.errors import CompileError from mypy.modulefinder import BuildSource -from mypy.options import TYPE_VAR_TUPLE, UNPACK from mypy.test.config import test_temp_dir from mypy.test.data import DataDrivenTestCase, DataSuite from mypy.test.helpers import assert_string_arrays_equal, normalize_error_messages, parse_options @@ -40,8 +37,8 @@ def test_transform(testcase: DataDrivenTestCase) -> None: options = parse_options(src, testcase, 1) options.use_builtins_fixtures = True options.semantic_analysis_only = True - options.enable_incomplete_feature = [TYPE_VAR_TUPLE, UNPACK] options.show_traceback = True + options.force_uppercase_builtins = True result = build.build( sources=[BuildSource("main", None, src)], options=options, alt_lib_path=test_temp_dir ) @@ -50,29 +47,12 @@ def test_transform(testcase: DataDrivenTestCase) -> None: raise CompileError(a) # Include string representations of the source files in the actual # output. - for fnam in sorted(result.files.keys()): - f = result.files[fnam] - - # Omit the builtins module and files with a special marker in the - # path. - # TODO the test is not reliable - if ( - not f.path.endswith( - ( - os.sep + "builtins.pyi", - "typing_extensions.pyi", - "typing.pyi", - "abc.pyi", - "sys.pyi", - ) - ) - and not os.path.basename(f.path).startswith("_") - and not os.path.splitext(os.path.basename(f.path))[0].endswith("_") - ): + for module in sorted(result.files.keys()): + if module in testcase.test_modules: t = TypeAssertTransformVisitor() t.test_only = True - f = t.mypyfile(f) - a += str(f).split("\n") + file = t.mypyfile(result.files[module]) + a += file.str_with_options(options).split("\n") except CompileError as e: a = e.messages if testcase.normalize_output: diff --git a/mypy/test/testtypegen.py b/mypy/test/testtypegen.py index 22ef4272e933..4933bd3522a0 100644 --- a/mypy/test/testtypegen.py +++ b/mypy/test/testtypegen.py @@ -35,6 +35,7 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: options.export_types = True options.preserve_asts = True options.allow_empty_bodies = True + options.force_uppercase_builtins = True result = build.build( sources=[BuildSource("main", None, src)], options=options, @@ -48,6 +49,7 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: # to simplify output. searcher = SkippedNodeSearcher() for file in result.files.values(): + searcher.ignore_file = file.fullname not in testcase.test_modules file.accept(searcher) ignored = searcher.nodes @@ -65,8 +67,11 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: # Include node in output. keys.append(node) - for key in sorted(keys, key=lambda n: (n.line, short_type(n), str(n) + str(map[n]))): - ts = str(map[key]).replace("*", "") # Remove erased tags + for key in sorted( + keys, + key=lambda n: (n.line, short_type(n), str(n) + map[n].str_with_options(options)), + ): + ts = map[key].str_with_options(options).replace("*", "") # Remove erased tags ts = ts.replace("__main__.", "") a.append(f"{short_type(key)}({key.line}) : {ts}") except CompileError as e: diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index ee0256e2057a..b3f84905c47e 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -2,12 +2,29 @@ from __future__ import annotations +import re +from unittest import TestCase, skipUnless + from mypy.erasetype import erase_type, remove_instance_last_known_values -from mypy.expandtype import expand_type from mypy.indirection import TypeIndirectionVisitor from mypy.join import join_simple, join_types from mypy.meet import meet_types, narrow_declared_type -from mypy.nodes import ARG_OPT, ARG_POS, ARG_STAR, ARG_STAR2, CONTRAVARIANT, COVARIANT, INVARIANT +from mypy.nodes import ( + ARG_NAMED, + ARG_OPT, + ARG_POS, + ARG_STAR, + ARG_STAR2, + CONTRAVARIANT, + COVARIANT, + INVARIANT, + ArgKind, + CallExpr, + Expression, + NameExpr, +) +from mypy.options import Options +from mypy.plugins.common import find_shallow_matching_overload_item from mypy.state import state from mypy.subtypes import is_more_precise, is_proper_subtype, is_same_type, is_subtype from mypy.test.helpers import Suite, assert_equal, assert_type, skip @@ -30,10 +47,14 @@ UnboundType, UninhabitedType, UnionType, + UnpackType, get_proper_type, has_recursive_types, ) +# Solving the import cycle: +import mypy.expandtype # ruff: isort: skip + class TypesSuite(Suite): def setUp(self) -> None: @@ -109,17 +130,36 @@ def test_callable_type_with_var_args(self) -> None: ) assert_equal(str(c3), "def (X? =, *Y?) -> Any") - def test_tuple_type(self) -> None: - assert_equal(str(TupleType([], self.fx.std_tuple)), "Tuple[]") - assert_equal(str(TupleType([self.x], self.fx.std_tuple)), "Tuple[X?]") + def test_tuple_type_upper(self) -> None: + options = Options() + options.force_uppercase_builtins = True + assert_equal(TupleType([], self.fx.std_tuple).str_with_options(options), "Tuple[()]") + assert_equal(TupleType([self.x], self.fx.std_tuple).str_with_options(options), "Tuple[X?]") assert_equal( - str(TupleType([self.x, AnyType(TypeOfAny.special_form)], self.fx.std_tuple)), + TupleType( + [self.x, AnyType(TypeOfAny.special_form)], self.fx.std_tuple + ).str_with_options(options), "Tuple[X?, Any]", ) def test_type_variable_binding(self) -> None: - assert_equal(str(TypeVarType("X", "X", 1, [], self.fx.o)), "X`1") - assert_equal(str(TypeVarType("X", "X", 1, [self.x, self.y], self.fx.o)), "X`1") + assert_equal( + str(TypeVarType("X", "X", 1, [], self.fx.o, AnyType(TypeOfAny.from_omitted_generics))), + "X`1", + ) + assert_equal( + str( + TypeVarType( + "X", + "X", + 1, + [self.x, self.y], + self.fx.o, + AnyType(TypeOfAny.from_omitted_generics), + ) + ), + "X`1", + ) def test_generic_function_type(self) -> None: c = CallableType( @@ -129,11 +169,16 @@ def test_generic_function_type(self) -> None: self.y, self.function, name=None, - variables=[TypeVarType("X", "X", -1, [], self.fx.o)], + variables=[ + TypeVarType("X", "X", -1, [], self.fx.o, AnyType(TypeOfAny.from_omitted_generics)) + ], ) assert_equal(str(c), "def [X] (X?, Y?) -> Y?") - v = [TypeVarType("Y", "Y", -1, [], self.fx.o), TypeVarType("X", "X", -2, [], self.fx.o)] + v = [ + TypeVarType("Y", "Y", -1, [], self.fx.o, AnyType(TypeOfAny.from_omitted_generics)), + TypeVarType("X", "X", -2, [], self.fx.o, AnyType(TypeOfAny.from_omitted_generics)), + ] c2 = CallableType([], [], [], NoneType(), self.function, name=None, variables=v) assert_equal(str(c2), "def [Y, X] ()") @@ -160,7 +205,7 @@ def test_type_alias_expand_all(self) -> None: def test_recursive_nested_in_non_recursive(self) -> None: A, _ = self.fx.def_alias_1(self.fx.a) - T = TypeVarType("T", "T", -1, [], self.fx.o) + T = TypeVarType("T", "T", -1, [], self.fx.o, AnyType(TypeOfAny.from_omitted_generics)) NA = self.fx.non_rec_alias(Instance(self.fx.gi, [T]), [T], [A]) assert not NA.is_recursive assert has_recursive_types(NA) @@ -225,7 +270,7 @@ def assert_expand( for id, t in map_items: lower_bounds[id] = t - exp = expand_type(orig, lower_bounds) + exp = mypy.expandtype.expand_type(orig, lower_bounds) # Remove erased tags (asterisks). assert_equal(str(exp).replace("*", ""), str(result)) @@ -592,10 +637,7 @@ def test_simplified_union_with_mixed_str_literals(self) -> None: [fx.lit_str1, fx.lit_str2, fx.lit_str3_inst], UnionType([fx.lit_str1, fx.lit_str2, fx.lit_str3_inst]), ) - self.assert_simplified_union( - [fx.lit_str1, fx.lit_str1, fx.lit_str1_inst], - UnionType([fx.lit_str1, fx.lit_str1_inst]), - ) + self.assert_simplified_union([fx.lit_str1, fx.lit_str1, fx.lit_str1_inst], fx.lit_str1) def assert_simplified_union(self, original: list[Type], union: Type) -> None: assert_equal(make_simplified_union(original), union) @@ -614,7 +656,9 @@ def callable(self, vars: list[str], *a: Type) -> CallableType: tv: list[TypeVarType] = [] n = -1 for v in vars: - tv.append(TypeVarType(v, v, n, [], self.fx.o)) + tv.append( + TypeVarType(v, v, n, [], self.fx.o, AnyType(TypeOfAny.from_omitted_generics)) + ) n -= 1 return CallableType( list(a[:-1]), @@ -857,13 +901,11 @@ def ov(*items: CallableType) -> Overloaded: self.assert_join(ov(c(fx.a, fx.a), c(fx.b, fx.b)), c(any, fx.b), c(any, fx.b)) self.assert_join(ov(c(fx.a, fx.a), c(any, fx.b)), c(fx.b, fx.b), c(any, fx.b)) - @skip def test_join_interface_types(self) -> None: self.assert_join(self.fx.f, self.fx.f, self.fx.f) self.assert_join(self.fx.f, self.fx.f2, self.fx.o) self.assert_join(self.fx.f, self.fx.f3, self.fx.f) - @skip def test_join_interface_and_class_types(self) -> None: self.assert_join(self.fx.o, self.fx.f, self.fx.o) self.assert_join(self.fx.a, self.fx.f, self.fx.o) @@ -945,6 +987,54 @@ def test_literal_type(self) -> None: UnionType([lit2, lit3]), UnionType([lit1, lit2]), UnionType([lit2, lit3, lit1]) ) + def test_variadic_tuple_joins(self) -> None: + # These tests really test just the "arity", to be sure it is handled correctly. + self.assert_join( + self.tuple(self.fx.a, self.fx.a), + self.tuple(UnpackType(Instance(self.fx.std_tuplei, [self.fx.a]))), + self.tuple(UnpackType(Instance(self.fx.std_tuplei, [self.fx.a]))), + ) + self.assert_join( + self.tuple(self.fx.a, self.fx.a), + self.tuple(UnpackType(Instance(self.fx.std_tuplei, [self.fx.a])), self.fx.a), + self.tuple(UnpackType(Instance(self.fx.std_tuplei, [self.fx.a])), self.fx.a), + ) + self.assert_join( + self.tuple(self.fx.a, self.fx.a), + self.tuple(self.fx.a, UnpackType(Instance(self.fx.std_tuplei, [self.fx.a]))), + self.tuple(self.fx.a, UnpackType(Instance(self.fx.std_tuplei, [self.fx.a]))), + ) + self.assert_join( + self.tuple( + self.fx.a, UnpackType(Instance(self.fx.std_tuplei, [self.fx.a])), self.fx.a + ), + self.tuple( + self.fx.a, UnpackType(Instance(self.fx.std_tuplei, [self.fx.a])), self.fx.a + ), + self.tuple( + self.fx.a, UnpackType(Instance(self.fx.std_tuplei, [self.fx.a])), self.fx.a + ), + ) + self.assert_join( + self.tuple(UnpackType(Instance(self.fx.std_tuplei, [self.fx.a]))), + self.tuple( + self.fx.a, UnpackType(Instance(self.fx.std_tuplei, [self.fx.a])), self.fx.a + ), + self.tuple(UnpackType(Instance(self.fx.std_tuplei, [self.fx.a]))), + ) + self.assert_join( + self.tuple(UnpackType(Instance(self.fx.std_tuplei, [self.fx.a]))), + self.tuple(UnpackType(Instance(self.fx.std_tuplei, [self.fx.a]))), + self.tuple(UnpackType(Instance(self.fx.std_tuplei, [self.fx.a]))), + ) + self.assert_join( + self.tuple(UnpackType(Instance(self.fx.std_tuplei, [self.fx.a])), self.fx.a), + self.tuple( + self.fx.b, UnpackType(Instance(self.fx.std_tuplei, [self.fx.b])), self.fx.b + ), + self.tuple(UnpackType(Instance(self.fx.std_tuplei, [self.fx.a])), self.fx.a), + ) + # There are additional test cases in check-inference.test. # TODO: Function types + varargs and default args. @@ -1138,7 +1228,6 @@ def test_meet_class_types_with_shared_interfaces(self) -> None: self.assert_meet(self.fx.e, self.fx.e2, self.fx.nonet) self.assert_meet(self.fx.e2, self.fx.e3, self.fx.nonet) - @skip def test_meet_with_generic_interfaces(self) -> None: fx = InterfaceTypeFixture() self.assert_meet(fx.gfa, fx.m1, fx.m1) @@ -1181,6 +1270,34 @@ def assert_meet_uninhabited(self, s: Type, t: Type) -> None: with state.strict_optional_set(True): self.assert_meet(s, t, self.fx.uninhabited) + def test_variadic_tuple_meets(self) -> None: + # These tests really test just the "arity", to be sure it is handled correctly. + self.assert_meet( + self.tuple(self.fx.a, self.fx.a), + self.tuple(UnpackType(Instance(self.fx.std_tuplei, [self.fx.a]))), + self.tuple(self.fx.a, self.fx.a), + ) + self.assert_meet( + self.tuple(self.fx.a, self.fx.a), + self.tuple(UnpackType(Instance(self.fx.std_tuplei, [self.fx.a])), self.fx.a), + self.tuple(self.fx.a, self.fx.a), + ) + self.assert_meet( + self.tuple(self.fx.a, self.fx.a), + self.tuple(self.fx.a, UnpackType(Instance(self.fx.std_tuplei, [self.fx.a]))), + self.tuple(self.fx.a, self.fx.a), + ) + self.assert_meet( + self.tuple(UnpackType(Instance(self.fx.std_tuplei, [self.fx.a]))), + self.tuple(UnpackType(Instance(self.fx.std_tuplei, [self.fx.a]))), + self.tuple(UnpackType(Instance(self.fx.std_tuplei, [self.fx.a]))), + ) + self.assert_meet( + self.tuple(UnpackType(Instance(self.fx.std_tuplei, [self.fx.a])), self.fx.a), + self.tuple(self.fx.b, UnpackType(Instance(self.fx.std_tuplei, [self.fx.b]))), + self.tuple(self.fx.b, UnpackType(Instance(self.fx.std_tuplei, [self.fx.b]))), + ) + def assert_meet(self, s: Type, t: Type, meet: Type) -> None: self.assert_simple_meet(s, t, meet) self.assert_simple_meet(t, s, meet) @@ -1287,3 +1404,148 @@ def assert_union_result(self, t: ProperType, expected: list[Type]) -> None: t2 = remove_instance_last_known_values(t) assert type(t2) is UnionType assert t2.items == expected + + +class ShallowOverloadMatchingSuite(Suite): + def setUp(self) -> None: + self.fx = TypeFixture() + + def test_simple(self) -> None: + fx = self.fx + ov = self.make_overload([[("x", fx.anyt, ARG_NAMED)], [("y", fx.anyt, ARG_NAMED)]]) + # Match first only + self.assert_find_shallow_matching_overload_item(ov, make_call(("foo", "x")), 0) + # Match second only + self.assert_find_shallow_matching_overload_item(ov, make_call(("foo", "y")), 1) + # No match -- invalid keyword arg name + self.assert_find_shallow_matching_overload_item(ov, make_call(("foo", "z")), 1) + # No match -- missing arg + self.assert_find_shallow_matching_overload_item(ov, make_call(), 1) + # No match -- extra arg + self.assert_find_shallow_matching_overload_item( + ov, make_call(("foo", "x"), ("foo", "z")), 1 + ) + + def test_match_using_types(self) -> None: + fx = self.fx + ov = self.make_overload( + [ + [("x", fx.nonet, ARG_POS)], + [("x", fx.lit_false, ARG_POS)], + [("x", fx.lit_true, ARG_POS)], + [("x", fx.anyt, ARG_POS)], + ] + ) + self.assert_find_shallow_matching_overload_item(ov, make_call(("None", None)), 0) + self.assert_find_shallow_matching_overload_item(ov, make_call(("builtins.False", None)), 1) + self.assert_find_shallow_matching_overload_item(ov, make_call(("builtins.True", None)), 2) + self.assert_find_shallow_matching_overload_item(ov, make_call(("foo", None)), 3) + + def test_none_special_cases(self) -> None: + fx = self.fx + ov = self.make_overload( + [[("x", fx.callable(fx.nonet), ARG_POS)], [("x", fx.nonet, ARG_POS)]] + ) + self.assert_find_shallow_matching_overload_item(ov, make_call(("None", None)), 1) + self.assert_find_shallow_matching_overload_item(ov, make_call(("func", None)), 0) + ov = self.make_overload([[("x", fx.str_type, ARG_POS)], [("x", fx.nonet, ARG_POS)]]) + self.assert_find_shallow_matching_overload_item(ov, make_call(("None", None)), 1) + self.assert_find_shallow_matching_overload_item(ov, make_call(("func", None)), 0) + ov = self.make_overload( + [[("x", UnionType([fx.str_type, fx.a]), ARG_POS)], [("x", fx.nonet, ARG_POS)]] + ) + self.assert_find_shallow_matching_overload_item(ov, make_call(("None", None)), 1) + self.assert_find_shallow_matching_overload_item(ov, make_call(("func", None)), 0) + ov = self.make_overload([[("x", fx.o, ARG_POS)], [("x", fx.nonet, ARG_POS)]]) + self.assert_find_shallow_matching_overload_item(ov, make_call(("None", None)), 0) + self.assert_find_shallow_matching_overload_item(ov, make_call(("func", None)), 0) + ov = self.make_overload( + [[("x", UnionType([fx.str_type, fx.nonet]), ARG_POS)], [("x", fx.nonet, ARG_POS)]] + ) + self.assert_find_shallow_matching_overload_item(ov, make_call(("None", None)), 0) + self.assert_find_shallow_matching_overload_item(ov, make_call(("func", None)), 0) + ov = self.make_overload([[("x", fx.anyt, ARG_POS)], [("x", fx.nonet, ARG_POS)]]) + self.assert_find_shallow_matching_overload_item(ov, make_call(("None", None)), 0) + self.assert_find_shallow_matching_overload_item(ov, make_call(("func", None)), 0) + + def test_optional_arg(self) -> None: + fx = self.fx + ov = self.make_overload( + [[("x", fx.anyt, ARG_NAMED)], [("y", fx.anyt, ARG_OPT)], [("z", fx.anyt, ARG_NAMED)]] + ) + self.assert_find_shallow_matching_overload_item(ov, make_call(), 1) + self.assert_find_shallow_matching_overload_item(ov, make_call(("foo", "x")), 0) + self.assert_find_shallow_matching_overload_item(ov, make_call(("foo", "y")), 1) + self.assert_find_shallow_matching_overload_item(ov, make_call(("foo", "z")), 2) + + def test_two_args(self) -> None: + fx = self.fx + ov = self.make_overload( + [ + [("x", fx.nonet, ARG_OPT), ("y", fx.anyt, ARG_OPT)], + [("x", fx.anyt, ARG_OPT), ("y", fx.anyt, ARG_OPT)], + ] + ) + self.assert_find_shallow_matching_overload_item(ov, make_call(), 0) + self.assert_find_shallow_matching_overload_item(ov, make_call(("None", "x")), 0) + self.assert_find_shallow_matching_overload_item(ov, make_call(("foo", "x")), 1) + self.assert_find_shallow_matching_overload_item( + ov, make_call(("foo", "y"), ("None", "x")), 0 + ) + self.assert_find_shallow_matching_overload_item( + ov, make_call(("foo", "y"), ("bar", "x")), 1 + ) + + def assert_find_shallow_matching_overload_item( + self, ov: Overloaded, call: CallExpr, expected_index: int + ) -> None: + c = find_shallow_matching_overload_item(ov, call) + assert c in ov.items + assert ov.items.index(c) == expected_index + + def make_overload(self, items: list[list[tuple[str, Type, ArgKind]]]) -> Overloaded: + result = [] + for item in items: + arg_types = [] + arg_names = [] + arg_kinds = [] + for name, typ, kind in item: + arg_names.append(name) + arg_types.append(typ) + arg_kinds.append(kind) + result.append( + CallableType( + arg_types, arg_kinds, arg_names, ret_type=NoneType(), fallback=self.fx.o + ) + ) + return Overloaded(result) + + +def make_call(*items: tuple[str, str | None]) -> CallExpr: + args: list[Expression] = [] + arg_names = [] + arg_kinds = [] + for arg, name in items: + shortname = arg.split(".")[-1] + n = NameExpr(shortname) + n.fullname = arg + args.append(n) + arg_names.append(name) + if name: + arg_kinds.append(ARG_NAMED) + else: + arg_kinds.append(ARG_POS) + return CallExpr(NameExpr("f"), args, arg_kinds, arg_names) + + +class TestExpandTypeLimitGetProperType(TestCase): + # WARNING: do not increase this number unless absolutely necessary, + # and you understand what you are doing. + ALLOWED_GET_PROPER_TYPES = 9 + + @skipUnless(mypy.expandtype.__file__.endswith(".py"), "Skip for compiled mypy") + def test_count_get_proper_type(self) -> None: + with open(mypy.expandtype.__file__) as f: + code = f.read() + get_proper_type_count = len(re.findall("get_proper_type", code)) + assert get_proper_type_count == self.ALLOWED_GET_PROPER_TYPES diff --git a/mypy/test/testutil.py b/mypy/test/testutil.py index 89184b11a826..a7c3f1c00fee 100644 --- a/mypy/test/testutil.py +++ b/mypy/test/testutil.py @@ -3,7 +3,8 @@ import os from unittest import TestCase, mock -from mypy.util import get_terminal_width +from mypy.inspections import parse_location +from mypy.util import _generate_junit_contents, get_terminal_width class TestGetTerminalSize(TestCase): @@ -15,3 +16,96 @@ def test_get_terminal_size_in_pty_defaults_to_80(self) -> None: with mock.patch.object(os, "get_terminal_size", return_value=ret): with mock.patch.dict(os.environ, values=mock_environ, clear=True): assert get_terminal_width() == 80 + + def test_parse_location_windows(self) -> None: + assert parse_location(r"C:\test.py:1:1") == (r"C:\test.py", [1, 1]) + assert parse_location(r"C:\test.py:1:1:1:1") == (r"C:\test.py", [1, 1, 1, 1]) + + +class TestWriteJunitXml(TestCase): + def test_junit_pass(self) -> None: + serious = False + messages_by_file: dict[str | None, list[str]] = {} + expected = """ + + + + +""" + result = _generate_junit_contents( + dt=1.23, + serious=serious, + messages_by_file=messages_by_file, + version="3.14", + platform="test-plat", + ) + assert result == expected + + def test_junit_fail_escape_xml_chars(self) -> None: + serious = False + messages_by_file: dict[str | None, list[str]] = { + "file1.py": ["Test failed", "another line < > &"] + } + expected = """ + + + Test failed +another line < > & + + +""" + result = _generate_junit_contents( + dt=1.23, + serious=serious, + messages_by_file=messages_by_file, + version="3.14", + platform="test-plat", + ) + assert result == expected + + def test_junit_fail_two_files(self) -> None: + serious = False + messages_by_file: dict[str | None, list[str]] = { + "file1.py": ["Test failed", "another line"], + "file2.py": ["Another failure", "line 2"], + } + expected = """ + + + Test failed +another line + + + Another failure +line 2 + + +""" + result = _generate_junit_contents( + dt=1.23, + serious=serious, + messages_by_file=messages_by_file, + version="3.14", + platform="test-plat", + ) + assert result == expected + + def test_serious_error(self) -> None: + serious = True + messages_by_file: dict[str | None, list[str]] = {None: ["Error line 1", "Error line 2"]} + expected = """ + + + Error line 1 +Error line 2 + + +""" + result = _generate_junit_contents( + dt=1.23, + serious=serious, + messages_by_file=messages_by_file, + version="3.14", + platform="test-plat", + ) + assert result == expected diff --git a/mypy/test/typefixture.py b/mypy/test/typefixture.py index d12e7abab0e2..b7bde16e6be2 100644 --- a/mypy/test/typefixture.py +++ b/mypy/test/typefixture.py @@ -54,7 +54,15 @@ def __init__(self, variance: int = COVARIANT) -> None: def make_type_var( name: str, id: int, values: list[Type], upper_bound: Type, variance: int ) -> TypeVarType: - return TypeVarType(name, name, id, values, upper_bound, variance) + return TypeVarType( + name, + name, + id, + values, + upper_bound, + AnyType(TypeOfAny.from_omitted_generics), + variance, + ) self.t = make_type_var("T", 1, [], self.o, variance) # T`1 (type variable) self.tf = make_type_var("T", -1, [], self.o, variance) # T`-1 (type variable) @@ -136,6 +144,7 @@ def make_type_var( self.type_type = Instance(self.type_typei, []) # type self.function = Instance(self.functioni, []) # function TODO self.str_type = Instance(self.str_type_info, []) + self.bool_type = Instance(self.bool_type_info, []) self.a = Instance(self.ai, []) # A self.b = Instance(self.bi, []) # B self.c = Instance(self.ci, []) # C @@ -197,6 +206,9 @@ def make_type_var( self.lit_str2_inst = Instance(self.str_type_info, [], last_known_value=self.lit_str2) self.lit_str3_inst = Instance(self.str_type_info, [], last_known_value=self.lit_str3) + self.lit_false = LiteralType(False, self.bool_type) + self.lit_true = LiteralType(True, self.bool_type) + self.type_a = TypeType.make_normalized(self.a) self.type_b = TypeType.make_normalized(self.b) self.type_c = TypeType.make_normalized(self.c) @@ -207,12 +219,24 @@ def make_type_var( self._add_bool_dunder(self.bool_type_info) self._add_bool_dunder(self.ai) - def make_type_var_tuple(name: str, id: int, upper_bound: Type) -> TypeVarTupleType: - return TypeVarTupleType(name, name, id, upper_bound, self.std_tuple) + # TypeVars with non-trivial bounds + self.ub = make_type_var("UB", 5, [], self.b, variance) # UB`5 (type variable) + self.uc = make_type_var("UC", 6, [], self.c, variance) # UC`6 (type variable) - self.ts = make_type_var_tuple("Ts", 1, self.o) # Ts`1 (type var tuple) - self.ss = make_type_var_tuple("Ss", 2, self.o) # Ss`2 (type var tuple) - self.us = make_type_var_tuple("Us", 3, self.o) # Us`3 (type var tuple) + def make_type_var_tuple(name: str, id: int, upper_bound: Type) -> TypeVarTupleType: + return TypeVarTupleType( + name, + name, + id, + upper_bound, + self.std_tuple, + AnyType(TypeOfAny.from_omitted_generics), + ) + + obj_tuple = self.std_tuple.copy_modified(args=[self.o]) + self.ts = make_type_var_tuple("Ts", 1, obj_tuple) # Ts`1 (type var tuple) + self.ss = make_type_var_tuple("Ss", 2, obj_tuple) # Ss`2 (type var tuple) + self.us = make_type_var_tuple("Us", 3, obj_tuple) # Us`3 (type var tuple) self.gvi = self.make_type_info("GV", mro=[self.oi], typevars=["Ts"], typevar_tuple_index=0) self.gv2i = self.make_type_info( @@ -297,13 +321,32 @@ def make_type_info( v: list[TypeVarLikeType] = [] for id, n in enumerate(typevars, 1): if typevar_tuple_index is not None and id - 1 == typevar_tuple_index: - v.append(TypeVarTupleType(n, n, id, self.o, self.std_tuple)) + v.append( + TypeVarTupleType( + n, + n, + id, + self.std_tuple.copy_modified(args=[self.o]), + self.std_tuple.copy_modified(args=[self.o]), + AnyType(TypeOfAny.from_omitted_generics), + ) + ) else: if variances: variance = variances[id - 1] else: variance = COVARIANT - v.append(TypeVarType(n, n, id, [], self.o, variance=variance)) + v.append( + TypeVarType( + n, + n, + id, + [], + self.o, + AnyType(TypeOfAny.from_omitted_generics), + variance=variance, + ) + ) class_def.type_vars = v info = TypeInfo(SymbolTable(), class_def, module_name) diff --git a/mypy/test/update_data.py b/mypy/test/update_data.py new file mode 100644 index 000000000000..2d66752f61bd --- /dev/null +++ b/mypy/test/update_data.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import re +from collections import defaultdict +from typing import Iterator + +from mypy.test.data import DataDrivenTestCase, DataFileCollector, DataFileFix, parse_test_data + + +def update_testcase_output( + testcase: DataDrivenTestCase, actual: list[str], *, incremental_step: int +) -> None: + if testcase.xfail: + return + collector = testcase.parent + assert isinstance(collector, DataFileCollector) + for fix in _iter_fixes(testcase, actual, incremental_step=incremental_step): + collector.enqueue_fix(fix) + + +def _iter_fixes( + testcase: DataDrivenTestCase, actual: list[str], *, incremental_step: int +) -> Iterator[DataFileFix]: + reports_by_line: dict[tuple[str, int], list[tuple[str, str]]] = defaultdict(list) + for error_line in actual: + comment_match = re.match( + r"^(?P[^:]+):(?P\d+): (?Perror|note|warning): (?P.+)$", + error_line, + ) + if comment_match: + filename = comment_match.group("filename") + lineno = int(comment_match.group("lineno")) + severity = comment_match.group("severity") + msg = comment_match.group("msg") + reports_by_line[filename, lineno].append((severity, msg)) + + test_items = parse_test_data(testcase.data, testcase.name) + + # If we have [out] and/or [outN], we update just those sections. + if any(re.match(r"^out\d*$", test_item.id) for test_item in test_items): + for test_item in test_items: + if (incremental_step < 2 and test_item.id == "out") or ( + incremental_step >= 2 and test_item.id == f"out{incremental_step}" + ): + yield DataFileFix( + lineno=testcase.line + test_item.line - 1, + end_lineno=testcase.line + test_item.end_line - 1, + lines=actual + [""] * test_item.trimmed_newlines, + ) + + return + + # Update assertion comments within the sections + for test_item in test_items: + if test_item.id == "case": + source_lines = test_item.data + file_path = "main" + elif test_item.id == "file": + source_lines = test_item.data + file_path = f"tmp/{test_item.arg}" + else: + continue # other sections we don't touch + + fix_lines = [] + for lineno, source_line in enumerate(source_lines, start=1): + reports = reports_by_line.get((file_path, lineno)) + comment_match = re.search(r"(?P\s+)(?P# [EWN]: .+)$", source_line) + if comment_match: + source_line = source_line[: comment_match.start("indent")] # strip old comment + if reports: + indent = comment_match.group("indent") if comment_match else " " + # multiline comments are on the first line and then on subsequent lines emtpy lines + # with a continuation backslash + for j, (severity, msg) in enumerate(reports): + out_l = source_line if j == 0 else " " * len(source_line) + is_last = j == len(reports) - 1 + severity_char = severity[0].upper() + continuation = "" if is_last else " \\" + fix_lines.append(f"{out_l}{indent}# {severity_char}: {msg}{continuation}") + else: + fix_lines.append(source_line) + + yield DataFileFix( + lineno=testcase.line + test_item.line - 1, + end_lineno=testcase.line + test_item.end_line - 1, + lines=fix_lines + [""] * test_item.trimmed_newlines, + ) diff --git a/mypy/test/visitors.py b/mypy/test/visitors.py index 771119dbdc70..2b748ec1bdc4 100644 --- a/mypy/test/visitors.py +++ b/mypy/test/visitors.py @@ -8,15 +8,7 @@ from __future__ import annotations -from mypy.nodes import ( - AssignmentStmt, - CallExpr, - Expression, - IntExpr, - MypyFile, - NameExpr, - TypeVarExpr, -) +from mypy.nodes import AssignmentStmt, CallExpr, Expression, IntExpr, NameExpr, Node, TypeVarExpr from mypy.traverser import TraverserVisitor from mypy.treetransform import TransformVisitor from mypy.types import Type @@ -25,12 +17,8 @@ # from testtypegen class SkippedNodeSearcher(TraverserVisitor): def __init__(self) -> None: - self.nodes: set[Expression] = set() - self.is_typing = False - - def visit_mypy_file(self, f: MypyFile) -> None: - self.is_typing = f.fullname == "typing" or f.fullname == "builtins" - super().visit_mypy_file(f) + self.nodes: set[Node] = set() + self.ignore_file = False def visit_assignment_stmt(self, s: AssignmentStmt) -> None: if s.type or ignore_node(s.rvalue): @@ -40,14 +28,14 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: super().visit_assignment_stmt(s) def visit_name_expr(self, n: NameExpr) -> None: - self.skip_if_typing(n) + if self.ignore_file: + self.nodes.add(n) + super().visit_name_expr(n) def visit_int_expr(self, n: IntExpr) -> None: - self.skip_if_typing(n) - - def skip_if_typing(self, n: Expression) -> None: - if self.is_typing: + if self.ignore_file: self.nodes.add(n) + super().visit_int_expr(n) def ignore_node(node: Expression) -> bool: diff --git a/mypy/traverser.py b/mypy/traverser.py index 038d948522f0..225de27e7002 100644 --- a/mypy/traverser.py +++ b/mypy/traverser.py @@ -2,7 +2,7 @@ from __future__ import annotations -from mypy_extensions import mypyc_attr +from mypy_extensions import mypyc_attr, trait from mypy.nodes import ( REVEAL_TYPE, @@ -71,6 +71,7 @@ TupleExpr, TypeAlias, TypeAliasExpr, + TypeAliasStmt, TypeApplication, TypedDictExpr, TypeVarExpr, @@ -94,6 +95,7 @@ from mypy.visitor import NodeVisitor +@trait @mypyc_attr(allow_interpreted_subclasses=True) class TraverserVisitor(NodeVisitor[None]): """A parse tree visitor that traverses the parse tree during visiting. @@ -242,6 +244,11 @@ def visit_match_stmt(self, o: MatchStmt) -> None: guard.accept(self) o.bodies[i].accept(self) + def visit_type_alias_stmt(self, o: TypeAliasStmt) -> None: + o.name.accept(self) + # TODO: params + o.value.accept(self) + def visit_member_expr(self, o: MemberExpr) -> None: o.expr.accept(self) @@ -873,6 +880,21 @@ def has_yield_expression(fdef: FuncBase) -> bool: return seeker.found +class YieldFromSeeker(FuncCollectorBase): + def __init__(self) -> None: + super().__init__() + self.found = False + + def visit_yield_from_expr(self, o: YieldFromExpr) -> None: + self.found = True + + +def has_yield_from_expression(fdef: FuncBase) -> bool: + seeker = YieldFromSeeker() + fdef.accept(seeker) + return seeker.found + + class AwaitSeeker(TraverserVisitor): def __init__(self) -> None: super().__init__() @@ -922,3 +944,24 @@ def all_yield_expressions(node: Node) -> list[tuple[YieldExpr, bool]]: v = YieldCollector() node.accept(v) return v.yield_expressions + + +class YieldFromCollector(FuncCollectorBase): + def __init__(self) -> None: + super().__init__() + self.in_assignment = False + self.yield_from_expressions: list[tuple[YieldFromExpr, bool]] = [] + + def visit_assignment_stmt(self, stmt: AssignmentStmt) -> None: + self.in_assignment = True + super().visit_assignment_stmt(stmt) + self.in_assignment = False + + def visit_yield_from_expr(self, expr: YieldFromExpr) -> None: + self.yield_from_expressions.append((expr, self.in_assignment)) + + +def all_yield_from_expressions(node: Node) -> list[tuple[YieldFromExpr, bool]]: + v = YieldFromCollector() + node.accept(v) + return v.yield_from_expressions diff --git a/mypy/treetransform.py b/mypy/treetransform.py index 535f50d5cf5e..bb34d8de2884 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -145,7 +145,7 @@ def __init__(self) -> None: def visit_mypy_file(self, node: MypyFile) -> MypyFile: assert self.test_only, "This visitor should not be used for whole files." # NOTE: The 'names' and 'imports' instance variables will be empty! - ignored_lines = {line: codes[:] for line, codes in node.ignored_lines.items()} + ignored_lines = {line: codes.copy() for line, codes in node.ignored_lines.items()} new = MypyFile(self.statements(node.defs), [], node.is_bom, ignored_lines=ignored_lines) new._fullname = node._fullname new.path = node.path @@ -153,10 +153,10 @@ def visit_mypy_file(self, node: MypyFile) -> MypyFile: return new def visit_import(self, node: Import) -> Import: - return Import(node.ids[:]) + return Import(node.ids.copy()) def visit_import_from(self, node: ImportFrom) -> ImportFrom: - return ImportFrom(node.id, node.relative, node.names[:]) + return ImportFrom(node.id, node.relative, node.names.copy()) def visit_import_all(self, node: ImportAll) -> ImportAll: return ImportAll(node.id, node.relative) @@ -233,6 +233,9 @@ def copy_function_attributes(self, new: FuncItem, original: FuncItem) -> None: new.max_pos = original.max_pos new.is_overload = original.is_overload new.is_generator = original.is_generator + new.is_coroutine = original.is_coroutine + new.is_async_generator = original.is_async_generator + new.is_awaitable_coroutine = original.is_awaitable_coroutine new.line = original.line def visit_overloaded_func_def(self, node: OverloadedFuncDef) -> OverloadedFuncDef: @@ -267,10 +270,10 @@ def visit_class_def(self, node: ClassDef) -> ClassDef: return new def visit_global_decl(self, node: GlobalDecl) -> GlobalDecl: - return GlobalDecl(node.names[:]) + return GlobalDecl(node.names.copy()) def visit_nonlocal_decl(self, node: NonlocalDecl) -> NonlocalDecl: - return NonlocalDecl(node.names[:]) + return NonlocalDecl(node.names.copy()) def visit_block(self, node: Block) -> Block: return Block(self.statements(node.body)) @@ -513,8 +516,8 @@ def visit_call_expr(self, node: CallExpr) -> CallExpr: return CallExpr( self.expr(node.callee), self.expressions(node.args), - node.arg_kinds[:], - node.arg_names[:], + node.arg_kinds.copy(), + node.arg_names.copy(), self.optional_expr(node.analyzed), ) @@ -643,12 +646,17 @@ def visit_type_var_expr(self, node: TypeVarExpr) -> TypeVarExpr: node.fullname, self.types(node.values), self.type(node.upper_bound), + self.type(node.default), variance=node.variance, ) def visit_paramspec_expr(self, node: ParamSpecExpr) -> ParamSpecExpr: return ParamSpecExpr( - node.name, node.fullname, self.type(node.upper_bound), variance=node.variance + node.name, + node.fullname, + self.type(node.upper_bound), + self.type(node.default), + variance=node.variance, ) def visit_type_var_tuple_expr(self, node: TypeVarTupleExpr) -> TypeVarTupleExpr: @@ -657,6 +665,7 @@ def visit_type_var_tuple_expr(self, node: TypeVarTupleExpr) -> TypeVarTupleExpr: node.fullname, self.type(node.upper_bound), node.tuple_fallback, + self.type(node.default), variance=node.variance, ) diff --git a/mypy/tvar_scope.py b/mypy/tvar_scope.py index 9b432d8e68ec..4dc663df0399 100644 --- a/mypy/tvar_scope.py +++ b/mypy/tvar_scope.py @@ -15,6 +15,26 @@ TypeVarTupleType, TypeVarType, ) +from mypy.typetraverser import TypeTraverserVisitor + + +class TypeVarLikeNamespaceSetter(TypeTraverserVisitor): + """Set namespace for all TypeVarLikeTypes types.""" + + def __init__(self, namespace: str) -> None: + self.namespace = namespace + + def visit_type_var(self, t: TypeVarType) -> None: + t.id.namespace = self.namespace + super().visit_type_var(t) + + def visit_param_spec(self, t: ParamSpecType) -> None: + t.id.namespace = self.namespace + return super().visit_param_spec(t) + + def visit_type_var_tuple(self, t: TypeVarTupleType) -> None: + t.id.namespace = self.namespace + super().visit_type_var_tuple(t) class TypeVarLikeScope: @@ -88,13 +108,16 @@ def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType: i = self.func_id # TODO: Consider also using namespaces for functions namespace = "" + tvar_expr.default.accept(TypeVarLikeNamespaceSetter(namespace)) + if isinstance(tvar_expr, TypeVarExpr): tvar_def: TypeVarLikeType = TypeVarType( - name, - tvar_expr.fullname, - TypeVarId(i, namespace=namespace), + name=name, + fullname=tvar_expr.fullname, + id=TypeVarId(i, namespace=namespace), values=tvar_expr.values, upper_bound=tvar_expr.upper_bound, + default=tvar_expr.default, variance=tvar_expr.variance, line=tvar_expr.line, column=tvar_expr.column, @@ -106,6 +129,7 @@ def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType: i, flavor=ParamSpecFlavor.BARE, upper_bound=tvar_expr.upper_bound, + default=tvar_expr.default, line=tvar_expr.line, column=tvar_expr.column, ) @@ -116,6 +140,7 @@ def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType: i, upper_bound=tvar_expr.upper_bound, tuple_fallback=tvar_expr.tuple_fallback, + default=tvar_expr.default, line=tvar_expr.line, column=tvar_expr.column, ) diff --git a/mypy/type_visitor.py b/mypy/type_visitor.py index 5a5643f35c01..a6ae77832ceb 100644 --- a/mypy/type_visitor.py +++ b/mypy/type_visitor.py @@ -14,8 +14,7 @@ from __future__ import annotations from abc import abstractmethod -from typing import Any, Callable, Generic, Iterable, Sequence, TypeVar, cast -from typing_extensions import Final +from typing import Any, Callable, Final, Generic, Iterable, Sequence, TypeVar, cast from mypy_extensions import mypyc_attr, trait @@ -346,13 +345,13 @@ def visit_deleted_type(self, t: DeletedType) -> T: return self.strategy([]) def visit_type_var(self, t: TypeVarType) -> T: - return self.query_types([t.upper_bound] + t.values) + return self.query_types([t.upper_bound, t.default] + t.values) def visit_param_spec(self, t: ParamSpecType) -> T: - return self.strategy([]) + return self.query_types([t.upper_bound, t.default, t.prefix]) def visit_type_var_tuple(self, t: TypeVarTupleType) -> T: - return self.strategy([]) + return self.query_types([t.upper_bound, t.default]) def visit_unpack_type(self, t: UnpackType) -> T: return self.query_types([t.type]) @@ -377,6 +376,8 @@ def visit_typeddict_type(self, t: TypedDictType) -> T: return self.query_types(t.items.values()) def visit_raw_expression_type(self, t: RawExpressionType) -> T: + if t.node is not None: + return t.node.accept(self) return self.strategy([]) def visit_literal_type(self, t: LiteralType) -> T: @@ -480,13 +481,13 @@ def visit_deleted_type(self, t: DeletedType) -> bool: return self.default def visit_type_var(self, t: TypeVarType) -> bool: - return self.query_types([t.upper_bound] + t.values) + return self.query_types([t.upper_bound, t.default] + t.values) def visit_param_spec(self, t: ParamSpecType) -> bool: - return self.default + return self.query_types([t.upper_bound, t.default]) def visit_type_var_tuple(self, t: TypeVarTupleType) -> bool: - return self.default + return self.query_types([t.upper_bound, t.default]) def visit_unpack_type(self, t: UnpackType) -> bool: return self.query_types([t.type]) @@ -517,6 +518,8 @@ def visit_typeddict_type(self, t: TypedDictType) -> bool: return self.query_types(list(t.items.values())) def visit_raw_expression_type(self, t: RawExpressionType) -> bool: + if t.node is not None: + return t.node.accept(self) return self.default def visit_literal_type(self, t: LiteralType) -> bool: diff --git a/mypy/typeanal.py b/mypy/typeanal.py index f3329af6207a..31d451b0831a 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -4,11 +4,12 @@ import itertools from contextlib import contextmanager -from typing import Callable, Iterable, Iterator, List, Sequence, Tuple, TypeVar -from typing_extensions import Final, Protocol +from typing import Callable, Final, Iterable, Iterator, List, Sequence, Tuple, TypeVar +from typing_extensions import Protocol from mypy import errorcodes as codes, message_registry, nodes from mypy.errorcodes import ErrorCode +from mypy.expandtype import expand_type from mypy.messages import MessageBuilder, format_type_bare, quote_type_string, wrong_type_arg_count from mypy.nodes import ( ARG_NAMED, @@ -35,9 +36,15 @@ check_arg_names, get_nongen_builtins, ) -from mypy.options import UNPACK, Options +from mypy.options import Options from mypy.plugin import AnalyzeTypeContext, Plugin, TypeAnalyzerPluginInterface -from mypy.semanal_shared import SemanticAnalyzerCoreInterface, paramspec_args, paramspec_kwargs +from mypy.semanal_shared import ( + SemanticAnalyzerCoreInterface, + SemanticAnalyzerInterface, + paramspec_args, + paramspec_kwargs, +) +from mypy.state import state from mypy.tvar_scope import TypeVarLikeScope from mypy.types import ( ANNOTATED_TYPE_NAMES, @@ -62,6 +69,7 @@ ParamSpecType, PartialType, PlaceholderType, + ProperType, RawExpressionType, RequiredType, SyntheticTypeVisitor, @@ -74,6 +82,7 @@ TypeOfAny, TypeQuery, TypeType, + TypeVarId, TypeVarLikeType, TypeVarTupleType, TypeVarType, @@ -81,13 +90,14 @@ UninhabitedType, UnionType, UnpackType, - bad_type_type_item, callable_with_ellipsis, + find_unpack_in_list, + flatten_nested_tuples, flatten_nested_unions, get_proper_type, has_type_vars, ) -from mypy.typetraverser import TypeTraverserVisitor +from mypy.types_utils import is_bad_type_type_item from mypy.typevars import fill_typevars T = TypeVar("T") @@ -131,6 +141,7 @@ def analyze_type_alias( in_dynamic_func: bool = False, global_scope: bool = True, allowed_alias_tvars: list[TypeVarLikeType] | None = None, + alias_type_params_names: list[str] | None = None, ) -> tuple[Type, set[str]]: """Analyze r.h.s. of a (potential) type alias definition. @@ -148,6 +159,7 @@ def analyze_type_alias( allow_placeholder=allow_placeholder, prohibit_self_type="type alias target", allowed_alias_tvars=allowed_alias_tvars, + alias_type_params_names=alias_type_params_names, ) analyzer.in_dynamic_func = in_dynamic_func analyzer.global_scope = global_scope @@ -195,10 +207,12 @@ def __init__( allow_placeholder: bool = False, allow_required: bool = False, allow_param_spec_literals: bool = False, + allow_unpack: bool = False, report_invalid_types: bool = True, prohibit_self_type: str | None = None, allowed_alias_tvars: list[TypeVarLikeType] | None = None, allow_type_any: bool = False, + alias_type_params_names: list[str] | None = None, ) -> None: self.api = api self.fail_func = api.fail @@ -220,12 +234,15 @@ def __init__( if allowed_alias_tvars is None: allowed_alias_tvars = [] self.allowed_alias_tvars = allowed_alias_tvars + self.alias_type_params_names = alias_type_params_names # If false, record incomplete ref if we generate PlaceholderType. self.allow_placeholder = allow_placeholder # Are we in a context where Required[] is allowed? self.allow_required = allow_required # Are we in a context where ParamSpec literals are allowed? self.allow_param_spec_literals = allow_param_spec_literals + # Are we in context where literal "..." specifically is allowed? + self.allow_ellipsis = False # Should we report an error whenever we encounter a RawExpressionType outside # of a Literal context: e.g. whenever we encounter an invalid type? Normally, # we want to report an error, but the caller may want to do more specialized @@ -239,6 +256,8 @@ def __init__( self.prohibit_self_type = prohibit_self_type # Allow variables typed as Type[Any] and type (useful for base classes). self.allow_type_any = allow_type_any + self.allow_type_var_tuple = False + self.allow_unpack = allow_unpack def lookup_qualified( self, name: str, ctx: Context, suppress_errors: bool = False @@ -256,6 +275,12 @@ def visit_unbound_type(self, t: UnboundType, defining_literal: bool = False) -> return make_optional_type(typ) return typ + def not_declared_in_type_params(self, tvar_name: str) -> bool: + return ( + self.alias_type_params_names is not None + and tvar_name not in self.alias_type_params_names + ) + def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) -> Type: sym = self.lookup_qualified(t.name, t) if sym is not None: @@ -275,7 +300,10 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) return PlaceholderType( node.fullname, self.anal_array( - t.args, allow_param_spec=True, allow_param_spec_literals=True + t.args, + allow_param_spec=True, + allow_param_spec_literals=True, + allow_unpack=True, ), t.line, ) @@ -307,7 +335,11 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) if tvar_def is None: if self.allow_unbound_tvars: return t - self.fail(f'ParamSpec "{t.name}" is unbound', t, code=codes.VALID_TYPE) + if self.defining_alias and self.not_declared_in_type_params(t.name): + msg = f'ParamSpec "{t.name}" is not included in type_params' + else: + msg = f'ParamSpec "{t.name}" is unbound' + self.fail(msg, t, code=codes.VALID_TYPE) return AnyType(TypeOfAny.from_error) assert isinstance(tvar_def, ParamSpecType) if len(t.args) > 0: @@ -321,6 +353,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) tvar_def.id, tvar_def.flavor, tvar_def.upper_bound, + tvar_def.default, line=t.line, column=t.column, ) @@ -330,11 +363,11 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) and not defining_literal and (tvar_def is None or tvar_def not in self.allowed_alias_tvars) ): - self.fail( - f'Can\'t use bound type variable "{t.name}" to define generic alias', - t, - code=codes.VALID_TYPE, - ) + if self.not_declared_in_type_params(t.name): + msg = f'Type variable "{t.name}" is not included in type_params' + else: + msg = f'Can\'t use bound type variable "{t.name}" to define generic alias' + self.fail(msg, t, code=codes.VALID_TYPE) return AnyType(TypeOfAny.from_error) if isinstance(sym.node, TypeVarExpr) and tvar_def is not None: assert isinstance(tvar_def, TypeVarType) @@ -343,32 +376,36 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) f'Type variable "{t.name}" used with arguments', t, code=codes.VALID_TYPE ) # Change the line number - return TypeVarType( - tvar_def.name, - tvar_def.fullname, - tvar_def.id, - tvar_def.values, - tvar_def.upper_bound, - tvar_def.variance, - line=t.line, - column=t.column, - ) + return tvar_def.copy_modified(line=t.line, column=t.column) if isinstance(sym.node, TypeVarTupleExpr) and ( tvar_def is not None and self.defining_alias and tvar_def not in self.allowed_alias_tvars ): - self.fail( - f'Can\'t use bound type variable "{t.name}" to define generic alias', - t, - code=codes.VALID_TYPE, - ) + if self.not_declared_in_type_params(t.name): + msg = f'Type variable "{t.name}" is not included in type_params' + else: + msg = f'Can\'t use bound type variable "{t.name}" to define generic alias' + self.fail(msg, t, code=codes.VALID_TYPE) return AnyType(TypeOfAny.from_error) if isinstance(sym.node, TypeVarTupleExpr): if tvar_def is None: - self.fail(f'TypeVarTuple "{t.name}" is unbound', t, code=codes.VALID_TYPE) + if self.allow_unbound_tvars: + return t + if self.defining_alias and self.not_declared_in_type_params(t.name): + msg = f'TypeVarTuple "{t.name}" is not included in type_params' + else: + msg = f'TypeVarTuple "{t.name}" is unbound' + self.fail(msg, t, code=codes.VALID_TYPE) return AnyType(TypeOfAny.from_error) assert isinstance(tvar_def, TypeVarTupleType) + if not self.allow_type_var_tuple: + self.fail( + f'TypeVarTuple "{t.name}" is only valid with an unpack', + t, + code=codes.VALID_TYPE, + ) + return AnyType(TypeOfAny.from_error) if len(t.args) > 0: self.fail( f'Type variable "{t.name}" used with arguments', t, code=codes.VALID_TYPE @@ -381,6 +418,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) tvar_def.id, tvar_def.upper_bound, sym.node.tuple_fallback, + tvar_def.default, line=t.line, column=t.column, ) @@ -393,33 +431,37 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) t.args, allow_param_spec=True, allow_param_spec_literals=node.has_param_spec_type, + allow_unpack=True, # Fixed length unpacks can be used for non-variadic aliases. ) if node.has_param_spec_type and len(node.alias_tvars) == 1: an_args = self.pack_paramspec_args(an_args) disallow_any = self.options.disallow_any_generics and not self.is_typeshed_stub - res = expand_type_alias( + res = instantiate_type_alias( node, an_args, self.fail, node.no_args, t, + self.options, unexpanded_type=t, disallow_any=disallow_any, + empty_tuple_index=t.empty_tuple_index, ) - # The only case where expand_type_alias() can return an incorrect instance is + # The only case where instantiate_type_alias() can return an incorrect instance is # when it is top-level instance, so no need to recurse. if ( - isinstance(res, Instance) # type: ignore[misc] - and len(res.args) != len(res.type.type_vars) - and not self.defining_alias + isinstance(res, ProperType) + and isinstance(res, Instance) + and not (self.defining_alias and self.nesting_level == 0) + and not validate_instance(res, self.fail, t.empty_tuple_index) ): fix_instance( res, self.fail, self.note, disallow_any=disallow_any, - python_version=self.options.python_version, + options=self.options, use_generic_error=True, unexpanded_type=t, ) @@ -427,7 +469,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) res = get_proper_type(res) return res elif isinstance(node, TypeInfo): - return self.analyze_type_with_type_info(node, t.args, t) + return self.analyze_type_with_type_info(node, t.args, t, t.empty_tuple_index) elif node.fullname in TYPE_ALIAS_NAMES: return AnyType(TypeOfAny.special_form) # Concatenate is an operator, no need for a proper type @@ -444,18 +486,37 @@ def pack_paramspec_args(self, an_args: Sequence[Type]) -> list[Type]: # These do not support mypy_extensions VarArgs, etc. as they were already analyzed # TODO: should these be re-analyzed to get rid of this inconsistency? count = len(an_args) - if count > 0: - first_arg = get_proper_type(an_args[0]) - if not (count == 1 and isinstance(first_arg, (Parameters, ParamSpecType, AnyType))): - return [Parameters(an_args, [ARG_POS] * count, [None] * count)] - return list(an_args) + if count == 0: + return [] + if count == 1 and isinstance(get_proper_type(an_args[0]), AnyType): + # Single Any is interpreted as ..., rather that a single argument with Any type. + # I didn't find this in the PEP, but it sounds reasonable. + return list(an_args) + if any(isinstance(a, (Parameters, ParamSpecType)) for a in an_args): + if len(an_args) > 1: + first_wrong = next( + arg for arg in an_args if isinstance(arg, (Parameters, ParamSpecType)) + ) + self.fail( + "Nested parameter specifications are not allowed", + first_wrong, + code=codes.VALID_TYPE, + ) + return [AnyType(TypeOfAny.from_error)] + return list(an_args) + first = an_args[0] + return [ + Parameters( + an_args, [ARG_POS] * count, [None] * count, line=first.line, column=first.column + ) + ] def cannot_resolve_type(self, t: UnboundType) -> None: # TODO: Move error message generation to messages.py. We'd first # need access to MessageBuilder here. Also move the similar # message generation logic in semanal.py. self.api.fail(f'Cannot resolve name "{t.name}" (possible cyclic definition)', t) - if not self.options.disable_recursive_aliases and self.api.is_func_scope(): + if self.api.is_func_scope(): self.note("Recursive types are not allowed at function scope", t) def apply_concatenate_operator(self, t: UnboundType) -> Type: @@ -463,9 +524,9 @@ def apply_concatenate_operator(self, t: UnboundType) -> Type: self.api.fail("Concatenate needs type arguments", t, code=codes.VALID_TYPE) return AnyType(TypeOfAny.from_error) - # last argument has to be ParamSpec - ps = self.anal_type(t.args[-1], allow_param_spec=True) - if not isinstance(ps, ParamSpecType): + # Last argument has to be ParamSpec or Ellipsis. + ps = self.anal_type(t.args[-1], allow_param_spec=True, allow_ellipsis=True) + if not isinstance(ps, (ParamSpecType, Parameters)): if isinstance(ps, UnboundType) and self.allow_unbound_tvars: sym = self.lookup_qualified(ps.name, t) if sym is not None and isinstance(sym.node, ParamSpecExpr): @@ -476,22 +537,23 @@ def apply_concatenate_operator(self, t: UnboundType) -> Type: code=codes.VALID_TYPE, ) return AnyType(TypeOfAny.from_error) - - # TODO: this may not work well with aliases, if those worked. - # Those should be special-cased. - elif ps.prefix.arg_types: + elif isinstance(ps, ParamSpecType) and ps.prefix.arg_types: self.api.fail("Nested Concatenates are invalid", t, code=codes.VALID_TYPE) args = self.anal_array(t.args[:-1]) - pre = ps.prefix + pre = ps.prefix if isinstance(ps, ParamSpecType) else ps # mypy can't infer this :( names: list[str | None] = [None] * len(args) pre = Parameters( - args + pre.arg_types, [ARG_POS] * len(args) + pre.arg_kinds, names + pre.arg_names + args + pre.arg_types, + [ARG_POS] * len(args) + pre.arg_kinds, + names + pre.arg_names, + line=t.line, + column=t.column, ) - return ps.copy_modified(prefix=pre) + return ps.copy_modified(prefix=pre) if isinstance(ps, ParamSpecType) else pre def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Type | None: """Bind special type that is recognized through magic name such as 'typing.Any'. @@ -531,7 +593,9 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ instance = self.named_type("builtins.tuple", [self.anal_type(t.args[0])]) instance.line = t.line return instance - return self.tuple_type(self.anal_array(t.args)) + return self.tuple_type( + self.anal_array(t.args, allow_unpack=True), line=t.line, column=t.column + ) elif fullname == "typing.Union": items = self.anal_array(t.args) return UnionType.make_union(items) @@ -563,7 +627,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ type_str + " must have exactly one type argument", t, code=codes.VALID_TYPE ) item = self.anal_type(t.args[0]) - if bad_type_type_item(item): + if is_bad_type_type_item(item): self.fail("Type[...] can't contain another Type[...]", t, code=codes.VALID_TYPE) item = AnyType(TypeOfAny.from_error) return TypeType.make_normalized(item, line=t.line, column=t.column) @@ -622,16 +686,23 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ ) return AnyType(TypeOfAny.from_error) return RequiredType(self.anal_type(t.args[0]), required=False) - elif self.anal_type_guard_arg(t, fullname) is not None: + elif ( + self.anal_type_guard_arg(t, fullname) is not None + or self.anal_type_is_arg(t, fullname) is not None + ): # In most contexts, TypeGuard[...] acts as an alias for bool (ignoring its args) return self.named_type("builtins.bool") elif fullname in ("typing.Unpack", "typing_extensions.Unpack"): - if not self.api.incomplete_feature_enabled(UNPACK, t): - return AnyType(TypeOfAny.from_error) if len(t.args) != 1: self.fail("Unpack[...] requires exactly one type argument", t) return AnyType(TypeOfAny.from_error) - return UnpackType(self.anal_type(t.args[0]), line=t.line, column=t.column) + if not self.allow_unpack: + self.fail(message_registry.INVALID_UNPACK_POSITION, t, code=codes.VALID_TYPE) + return AnyType(TypeOfAny.from_error) + self.allow_type_var_tuple = True + result = UnpackType(self.anal_type(t.args[0]), line=t.line, column=t.column) + self.allow_type_var_tuple = False + return result elif fullname in SELF_TYPE_NAMES: if t.args: self.fail("Self type cannot have type arguments", t) @@ -654,12 +725,10 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ def get_omitted_any(self, typ: Type, fullname: str | None = None) -> AnyType: disallow_any = not self.is_typeshed_stub and self.options.disallow_any_generics - return get_omitted_any( - disallow_any, self.fail, self.note, typ, self.options.python_version, fullname - ) + return get_omitted_any(disallow_any, self.fail, self.note, typ, self.options, fullname) def analyze_type_with_type_info( - self, info: TypeInfo, args: Sequence[Type], ctx: Context + self, info: TypeInfo, args: Sequence[Type], ctx: Context, empty_tuple_index: bool ) -> Type: """Bind unbound type when were able to find target TypeInfo. @@ -668,7 +737,7 @@ def analyze_type_with_type_info( if len(args) > 0 and info.fullname == "builtins.tuple": fallback = Instance(info, [AnyType(TypeOfAny.special_form)], ctx.line) - return TupleType(self.anal_array(args), fallback, ctx.line) + return TupleType(self.anal_array(args, allow_unpack=True), fallback, ctx.line) # Analyze arguments and (usually) construct Instance type. The # number of type arguments and their values are @@ -681,7 +750,10 @@ def analyze_type_with_type_info( instance = Instance( info, self.anal_array( - args, allow_param_spec=True, allow_param_spec_literals=info.has_param_spec_type + args, + allow_param_spec=True, + allow_param_spec_literals=info.has_param_spec_type, + allow_unpack=True, # Fixed length tuples can be used for non-variadic types. ), ctx.line, ctx.column, @@ -689,20 +761,17 @@ def analyze_type_with_type_info( if len(info.type_vars) == 1 and info.has_param_spec_type: instance.args = tuple(self.pack_paramspec_args(instance.args)) - if info.has_type_var_tuple_type: - # - 1 to allow for the empty type var tuple case. - valid_arg_length = len(instance.args) >= len(info.type_vars) - 1 - else: - valid_arg_length = len(instance.args) == len(info.type_vars) - # Check type argument count. - if not valid_arg_length and not self.defining_alias: + instance.args = tuple(flatten_nested_tuples(instance.args)) + if not (self.defining_alias and self.nesting_level == 0) and not validate_instance( + instance, self.fail, empty_tuple_index + ): fix_instance( instance, self.fail, self.note, disallow_any=self.options.disallow_any_generics and not self.is_typeshed_stub, - python_version=self.options.python_version, + options=self.options, ) tup = info.tuple_type @@ -710,28 +779,32 @@ def analyze_type_with_type_info( # The class has a Tuple[...] base class so it will be # represented as a tuple type. if info.special_alias: - return expand_type_alias( + return instantiate_type_alias( info.special_alias, # TODO: should we allow NamedTuples generic in ParamSpec? - self.anal_array(args), + self.anal_array(args, allow_unpack=True), self.fail, False, ctx, + self.options, use_standard_error=True, ) - return tup.copy_modified(items=self.anal_array(tup.items), fallback=instance) + return tup.copy_modified( + items=self.anal_array(tup.items, allow_unpack=True), fallback=instance + ) td = info.typeddict_type if td is not None: # The class has a TypedDict[...] base class so it will be # represented as a typeddict type. if info.special_alias: - return expand_type_alias( + return instantiate_type_alias( info.special_alias, # TODO: should we allow TypedDicts generic in ParamSpec? - self.anal_array(args), + self.anal_array(args, allow_unpack=True), self.fail, False, ctx, + self.options, use_standard_error=True, ) # Create a named TypedDictType @@ -795,7 +868,12 @@ def analyze_unbound_type_without_type_info( # If, in the distant future, we decide to permit things like # `def foo(x: Color.RED) -> None: ...`, we can remove that # check entirely. - if isinstance(sym.node, Var) and sym.node.info and sym.node.info.is_enum: + if ( + isinstance(sym.node, Var) + and sym.node.info + and sym.node.info.is_enum + and not sym.node.name.startswith("__") + ): value = sym.node.name base_enum_short_name = sym.node.info.name if not defining_literal: @@ -816,6 +894,7 @@ def analyze_unbound_type_without_type_info( t = t.copy_modified(args=self.anal_array(t.args)) # TODO: Move this message building logic to messages.py. notes: list[str] = [] + error_code = codes.VALID_TYPE if isinstance(sym.node, Var): notes.append( "See https://mypy.readthedocs.io/en/" @@ -834,25 +913,33 @@ def analyze_unbound_type_without_type_info( message = 'Module "{}" is not valid as a type' notes.append("Perhaps you meant to use a protocol matching the module structure?") elif unbound_tvar: - message = 'Type variable "{}" is unbound' - short = name.split(".")[-1] - notes.append( - ( - '(Hint: Use "Generic[{}]" or "Protocol[{}]" base class' - ' to bind "{}" inside a class)' - ).format(short, short, short) - ) - notes.append( - '(Hint: Use "{}" in function signature to bind "{}"' - " inside a function)".format(short, short) - ) + assert isinstance(sym.node, TypeVarLikeExpr) + if sym.node.is_new_style: + # PEP 695 type paramaters are never considered unbound -- they are undefined + # in contexts where they aren't valid, such as in argument default values. + message = 'Name "{}" is not defined' + name = name.split(".")[-1] + error_code = codes.NAME_DEFINED + else: + message = 'Type variable "{}" is unbound' + short = name.split(".")[-1] + notes.append( + ( + '(Hint: Use "Generic[{}]" or "Protocol[{}]" base class' + ' to bind "{}" inside a class)' + ).format(short, short, short) + ) + notes.append( + '(Hint: Use "{}" in function signature to bind "{}"' + " inside a function)".format(short, short) + ) else: message = 'Cannot interpret reference "{}" as a type' if not defining_literal: # Literal check already gives a custom error. Avoid duplicating errors. - self.fail(message.format(name), t, code=codes.VALID_TYPE) + self.fail(message.format(name), t, code=error_code) for note in notes: - self.note(note, t, code=codes.VALID_TYPE) + self.note(note, t, code=error_code) # TODO: Would it be better to always return Any instead of UnboundType # in case of an error? On one hand, UnboundType has a name so error messages @@ -877,20 +964,21 @@ def visit_deleted_type(self, t: DeletedType) -> Type: return t def visit_type_list(self, t: TypeList) -> Type: - # paramspec literal (Z[[int, str, Whatever]]) + # Parameters literal (Z[[int, str, Whatever]]) if self.allow_param_spec_literals: params = self.analyze_callable_args(t) if params: ts, kinds, names = params # bind these types - return Parameters(self.anal_array(ts), kinds, names) + return Parameters(self.anal_array(ts), kinds, names, line=t.line, column=t.column) else: return AnyType(TypeOfAny.from_error) else: self.fail( 'Bracketed expression "[...]" is not valid as a type', t, code=codes.VALID_TYPE ) - self.note('Did you mean "List[...]"?', t) + if len(t.items) == 1: + self.note('Did you mean "List[...]"?', t) return AnyType(TypeOfAny.from_error) def visit_callable_argument(self, t: CallableArgument) -> Type: @@ -914,7 +1002,13 @@ def visit_type_var_tuple(self, t: TypeVarTupleType) -> Type: return t def visit_unpack_type(self, t: UnpackType) -> Type: - raise NotImplementedError + if not self.allow_unpack: + self.fail(message_registry.INVALID_UNPACK_POSITION, t.type, code=codes.VALID_TYPE) + return AnyType(TypeOfAny.from_error) + self.allow_type_var_tuple = True + result = UnpackType(self.anal_type(t.type), from_star_syntax=t.from_star_syntax) + self.allow_type_var_tuple = False + return result def visit_parameters(self, t: Parameters) -> Type: raise NotImplementedError("ParamSpec literals cannot have unbound TypeVars") @@ -922,31 +1016,75 @@ def visit_parameters(self, t: Parameters) -> Type: def visit_callable_type(self, t: CallableType, nested: bool = True) -> Type: # Every Callable can bind its own type variables, if they're not in the outer scope with self.tvar_scope_frame(): + unpacked_kwargs = False if self.defining_alias: variables = t.variables else: variables, _ = self.bind_function_type_variables(t, t) - special = self.anal_type_guard(t.ret_type) + type_guard = self.anal_type_guard(t.ret_type) + type_is = self.anal_type_is(t.ret_type) arg_kinds = t.arg_kinds if len(arg_kinds) >= 2 and arg_kinds[-2] == ARG_STAR and arg_kinds[-1] == ARG_STAR2: arg_types = self.anal_array(t.arg_types[:-2], nested=nested) + [ self.anal_star_arg_type(t.arg_types[-2], ARG_STAR, nested=nested), self.anal_star_arg_type(t.arg_types[-1], ARG_STAR2, nested=nested), ] + # If nested is True, it means we are analyzing a Callable[...] type, rather + # than a function definition type. We need to "unpack" ** TypedDict annotation + # here (for function definitions it is done in semanal). + if nested and isinstance(arg_types[-1], UnpackType): + # TODO: it would be better to avoid this get_proper_type() call. + unpacked = get_proper_type(arg_types[-1].type) + if isinstance(unpacked, TypedDictType): + arg_types[-1] = unpacked + unpacked_kwargs = True + arg_types = self.check_unpacks_in_list(arg_types) else: - arg_types = self.anal_array(t.arg_types, nested=nested) + star_index = None + if ARG_STAR in arg_kinds: + star_index = arg_kinds.index(ARG_STAR) + star2_index = None + if ARG_STAR2 in arg_kinds: + star2_index = arg_kinds.index(ARG_STAR2) + arg_types = [] + for i, ut in enumerate(t.arg_types): + at = self.anal_type( + ut, nested=nested, allow_unpack=i in (star_index, star2_index) + ) + if nested and isinstance(at, UnpackType) and i == star_index: + # TODO: it would be better to avoid this get_proper_type() call. + p_at = get_proper_type(at.type) + if isinstance(p_at, TypedDictType) and not at.from_star_syntax: + # Automatically detect Unpack[Foo] in Callable as backwards + # compatible syntax for **Foo, if Foo is a TypedDict. + at = p_at + arg_kinds[i] = ARG_STAR2 + unpacked_kwargs = True + arg_types.append(at) + if nested: + arg_types = self.check_unpacks_in_list(arg_types) + # If there were multiple (invalid) unpacks, the arg types list will become shorter, + # we need to trim the kinds/names as well to avoid crashes. + arg_kinds = t.arg_kinds[: len(arg_types)] + arg_names = t.arg_names[: len(arg_types)] + ret = t.copy_modified( arg_types=arg_types, + arg_kinds=arg_kinds, + arg_names=arg_names, ret_type=self.anal_type(t.ret_type, nested=nested), # If the fallback isn't filled in yet, # its type will be the falsey FakeInfo fallback=(t.fallback if t.fallback.type else self.named_type("builtins.function")), variables=self.anal_var_defs(variables), - type_guard=special, + type_guard=type_guard, + type_is=type_is, + unpack_kwargs=unpacked_kwargs, ) return ret def anal_type_guard(self, t: Type) -> Type | None: + t = t.resolve_string_annotation() if isinstance(t, UnboundType): sym = self.lookup_qualified(t.name, t) if sym is not None and sym.node is not None: @@ -964,8 +1102,26 @@ def anal_type_guard_arg(self, t: UnboundType, fullname: str) -> Type | None: return self.anal_type(t.args[0]) return None + def anal_type_is(self, t: Type) -> Type | None: + t = t.resolve_string_annotation() + if isinstance(t, UnboundType): + sym = self.lookup_qualified(t.name, t) + if sym is not None and sym.node is not None: + return self.anal_type_is_arg(t, sym.node.fullname) + # TODO: What if it's an Instance? Then use t.type.fullname? + return None + + def anal_type_is_arg(self, t: UnboundType, fullname: str) -> Type | None: + if fullname in ("typing_extensions.TypeIs", "typing.TypeIs"): + if len(t.args) != 1: + self.fail("TypeIs must have exactly one type argument", t, code=codes.VALID_TYPE) + return AnyType(TypeOfAny.from_error) + return self.anal_type(t.args[0]) + return None + def anal_star_arg_type(self, t: Type, kind: ArgKind, nested: bool) -> Type: """Analyze signature argument type for *args and **kwargs argument.""" + t = t.resolve_string_annotation() if isinstance(t, UnboundType) and t.name and "." in t.name and not t.args: components = t.name.split(".") tvar_name = ".".join(components[:-1]) @@ -999,7 +1155,7 @@ def anal_star_arg_type(self, t: Type, kind: ArgKind, nested: bool) -> Type: line=t.line, column=t.column, ) - return self.anal_type(t, nested=nested) + return self.anal_type(t, nested=nested, allow_unpack=True) def visit_overloaded(self, t: Overloaded) -> Type: # Overloaded types are manually constructed in semanal.py by analyzing the @@ -1038,7 +1194,7 @@ def visit_tuple_type(self, t: TupleType) -> Type: if t.partial_fallback.type else self.named_type("builtins.tuple", [any_type]) ) - return TupleType(self.anal_array(t.items), fallback, t.line) + return TupleType(self.anal_array(t.items, allow_unpack=True), fallback, t.line) def visit_typeddict_type(self, t: TypedDictType) -> Type: items = { @@ -1056,6 +1212,8 @@ def visit_raw_expression_type(self, t: RawExpressionType) -> Type: # make signatures like "foo(x: 20) -> None" legal, we can change # this method so it generates and returns an actual LiteralType # instead. + if t.node is not None: + return t.node.accept(self) if self.report_invalid_types: if t.base_type_name in ("builtins.int", "builtins.bool"): @@ -1096,7 +1254,7 @@ def visit_partial_type(self, t: PartialType) -> Type: assert False, "Internal error: Unexpected partial type" def visit_ellipsis_type(self, t: EllipsisType) -> Type: - if self.allow_param_spec_literals: + if self.allow_ellipsis or self.allow_param_spec_literals: any_type = AnyType(TypeOfAny.explicit) return Parameters( [any_type, any_type], [ARG_STAR, ARG_STAR2], [None, None], is_ellipsis_args=True @@ -1109,14 +1267,20 @@ def visit_type_type(self, t: TypeType) -> Type: return TypeType.make_normalized(self.anal_type(t.item), line=t.line) def visit_placeholder_type(self, t: PlaceholderType) -> Type: - n = None if not t.fullname else self.api.lookup_fully_qualified(t.fullname) + n = ( + None + # No dot in fullname indicates we are at function scope, and recursive + # types are not supported there anyway, so we just give up. + if not t.fullname or "." not in t.fullname + else self.api.lookup_fully_qualified(t.fullname) + ) if not n or isinstance(n.node, PlaceholderNode): self.api.defer() # Still incomplete return t else: # TODO: Handle non-TypeInfo assert isinstance(n.node, TypeInfo) - return self.analyze_type_with_type_info(n.node, t.args, t) + return self.analyze_type_with_type_info(n.node, t.args, t, False) def analyze_callable_args_for_paramspec( self, callable_args: Type, ret_type: Type, fallback: Instance @@ -1140,6 +1304,19 @@ def analyze_callable_args_for_paramspec( AnyType(TypeOfAny.explicit), ret_type=ret_type, fallback=fallback ) return None + elif ( + self.defining_alias + and self.not_declared_in_type_params(tvar_def.name) + and tvar_def not in self.allowed_alias_tvars + ): + self.fail( + f'ParamSpec "{tvar_def.name}" is not included in type_params', + callable_args, + code=codes.VALID_TYPE, + ) + return callable_with_ellipsis( + AnyType(TypeOfAny.special_form), ret_type=ret_type, fallback=fallback + ) return CallableType( [ @@ -1158,7 +1335,7 @@ def analyze_callable_args_for_paramspec( def analyze_callable_args_for_concatenate( self, callable_args: Type, ret_type: Type, fallback: Instance - ) -> CallableType | None: + ) -> CallableType | AnyType | None: """Construct a 'Callable[C, RET]', where C is Concatenate[..., P], returning None if we cannot. """ @@ -1173,7 +1350,7 @@ def analyze_callable_args_for_concatenate( return None tvar_def = self.anal_type(callable_args, allow_param_spec=True) - if not isinstance(tvar_def, ParamSpecType): + if not isinstance(tvar_def, (ParamSpecType, Parameters)): if self.allow_unbound_tvars and isinstance(tvar_def, UnboundType): sym = self.lookup_qualified(tvar_def.name, callable_args) if sym is not None and isinstance(sym.node, ParamSpecExpr): @@ -1182,7 +1359,18 @@ def analyze_callable_args_for_concatenate( return callable_with_ellipsis( AnyType(TypeOfAny.explicit), ret_type=ret_type, fallback=fallback ) - return None + # Error was already given, so prevent further errors. + return AnyType(TypeOfAny.from_error) + if isinstance(tvar_def, Parameters): + # This comes from Concatenate[int, ...] + return CallableType( + arg_types=tvar_def.arg_types, + arg_names=tvar_def.arg_names, + arg_kinds=tvar_def.arg_kinds, + ret_type=ret_type, + fallback=fallback, + from_concatenate=True, + ) # ick, CallableType should take ParamSpecType prefix = tvar_def.prefix @@ -1228,9 +1416,21 @@ def analyze_callable_type(self, t: UnboundType) -> Type: ) else: # Callable[P, RET] (where P is ParamSpec) - maybe_ret = self.analyze_callable_args_for_paramspec( - callable_args, ret_type, fallback - ) or self.analyze_callable_args_for_concatenate(callable_args, ret_type, fallback) + with self.tvar_scope_frame(): + # Temporarily bind ParamSpecs to allow code like this: + # my_fun: Callable[Q, Foo[Q]] + # We usually do this later in visit_callable_type(), but the analysis + # below happens at very early stage. + variables = [] + for name, tvar_expr in self.find_type_var_likes(callable_args): + variables.append(self.tvar_scope.bind_new(name, tvar_expr)) + maybe_ret = self.analyze_callable_args_for_paramspec( + callable_args, ret_type, fallback + ) or self.analyze_callable_args_for_concatenate( + callable_args, ret_type, fallback + ) + if isinstance(maybe_ret, CallableType): + maybe_ret = maybe_ret.copy_modified(variables=variables) if maybe_ret is None: # Callable[?, RET] (where ? is something invalid) self.fail( @@ -1240,10 +1440,12 @@ def analyze_callable_type(self, t: UnboundType) -> Type: code=codes.VALID_TYPE, ) self.note( - "See https://mypy.readthedocs.io/en/stable/kinds_of_types.html#callable-types-and-lambdas", # noqa: E501 + "See https://mypy.readthedocs.io/en/stable/kinds_of_types.html#callable-types-and-lambdas", t, ) return AnyType(TypeOfAny.from_error) + elif isinstance(maybe_ret, AnyType): + return maybe_ret ret = maybe_ret else: if self.options.disallow_any_generics: @@ -1254,14 +1456,25 @@ def analyze_callable_type(self, t: UnboundType) -> Type: assert isinstance(ret, CallableType) return ret.accept(self) + def refers_to_full_names(self, arg: UnboundType, names: Sequence[str]) -> bool: + sym = self.lookup_qualified(arg.name, arg) + if sym is not None: + if sym.fullname in names: + return True + return False + def analyze_callable_args( self, arglist: TypeList ) -> tuple[list[Type], list[ArgKind], list[str | None]] | None: args: list[Type] = [] kinds: list[ArgKind] = [] names: list[str | None] = [] - found_unpack = False - for arg in arglist.items: + seen_unpack = False + unpack_types: list[Type] = [] + invalid_unpacks: list[Type] = [] + second_unpack_last = False + for i, arg in enumerate(arglist.items): + arg = arg.resolve_string_annotation() if isinstance(arg, CallableArgument): args.append(arg.typ) names.append(arg.name) @@ -1281,23 +1494,47 @@ def analyze_callable_args( if arg.name is not None and kind.is_star(): self.fail(f"{arg.constructor} arguments should not have names", arg) return None - elif isinstance(arg, UnboundType): - kind = ARG_POS - # Potentially a unpack. - sym = self.lookup_qualified(arg.name, arg) - if sym is not None: - if sym.fullname == "typing_extensions.Unpack": - if found_unpack: - self.fail("Callables can only have a single unpack", arg) - found_unpack = True - kind = ARG_STAR - args.append(arg) - kinds.append(kind) - names.append(None) + elif ( + isinstance(arg, UnboundType) + and self.refers_to_full_names(arg, ("typing_extensions.Unpack", "typing.Unpack")) + or isinstance(arg, UnpackType) + ): + if seen_unpack: + # Multiple unpacks, preserve them, so we can give an error later. + if i == len(arglist.items) - 1 and not invalid_unpacks: + # Special case: if there are just two unpacks, and the second one appears + # as last type argument, it can be still valid, if the second unpacked type + # is a TypedDict. This should be checked by the caller. + second_unpack_last = True + invalid_unpacks.append(arg) + continue + seen_unpack = True + unpack_types.append(arg) + else: + if seen_unpack: + unpack_types.append(arg) + else: + args.append(arg) + kinds.append(ARG_POS) + names.append(None) + if seen_unpack: + if len(unpack_types) == 1: + args.append(unpack_types[0]) else: - args.append(arg) - kinds.append(ARG_POS) - names.append(None) + first = unpack_types[0] + if isinstance(first, UnpackType): + # UnpackType doesn't have its own line/column numbers, + # so use the unpacked type for error messages. + first = first.type + args.append( + UnpackType(self.tuple_type(unpack_types, line=first.line, column=first.column)) + ) + kinds.append(ARG_STAR) + names.append(None) + for arg in invalid_unpacks: + args.append(arg) + kinds.append(ARG_STAR2 if second_unpack_last else ARG_STAR) + names.append(None) # Note that arglist below is only used for error context. check_arg_names(names, [arglist] * len(args), self.fail, "Callable") check_arg_kinds(kinds, [arglist] * len(args), self.fail) @@ -1318,18 +1555,6 @@ def analyze_literal_type(self, t: UnboundType) -> Type: return UnionType.make_union(output, line=t.line) def analyze_literal_param(self, idx: int, arg: Type, ctx: Context) -> list[Type] | None: - # This UnboundType was originally defined as a string. - if isinstance(arg, UnboundType) and arg.original_str_expr is not None: - assert arg.original_str_fallback is not None - return [ - LiteralType( - value=arg.original_str_expr, - fallback=self.named_type(arg.original_str_fallback), - line=arg.line, - column=arg.column, - ) - ] - # If arg is an UnboundType that was *not* originally defined as # a string, try expanding it in case it's a type alias or something. if isinstance(arg, UnboundType): @@ -1413,32 +1638,32 @@ def tvar_scope_frame(self) -> Iterator[None]: yield self.tvar_scope = old_scope - def find_type_var_likes(self, t: Type, include_callables: bool = True) -> TypeVarLikeList: - return t.accept( - TypeVarLikeQuery(self.api, self.tvar_scope, include_callables=include_callables) - ) + def find_type_var_likes(self, t: Type) -> TypeVarLikeList: + visitor = FindTypeVarVisitor(self.api, self.tvar_scope) + t.accept(visitor) + return visitor.type_var_likes - def infer_type_variables(self, type: CallableType) -> list[tuple[str, TypeVarLikeExpr]]: - """Return list of unique type variables referred to in a callable.""" - names: list[str] = [] - tvars: list[TypeVarLikeExpr] = [] + def infer_type_variables( + self, type: CallableType + ) -> tuple[list[tuple[str, TypeVarLikeExpr]], bool]: + """Infer type variables from a callable. + + Return tuple with these items: + - list of unique type variables referred to in a callable + - whether there is a reference to the Self type + """ + visitor = FindTypeVarVisitor(self.api, self.tvar_scope) for arg in type.arg_types: - for name, tvar_expr in self.find_type_var_likes(arg): - if name not in names: - names.append(name) - tvars.append(tvar_expr) + arg.accept(visitor) + # When finding type variables in the return type of a function, don't # look inside Callable types. Type variables only appearing in # functions in the return type belong to those functions, not the # function we're currently analyzing. - for name, tvar_expr in self.find_type_var_likes(type.ret_type, include_callables=False): - if name not in names: - names.append(name) - tvars.append(tvar_expr) + visitor.include_callables = False + type.ret_type.accept(visitor) - if not names: - return [] # Fast path - return list(zip(names, tvars)) + return visitor.type_var_likes, visitor.has_self_type def bind_function_type_variables( self, fun_type: CallableType, defn: Context @@ -1458,10 +1683,7 @@ def bind_function_type_variables( binding = self.tvar_scope.bind_new(var.name, var_expr) defs.append(binding) return defs, has_self_type - typevars = self.infer_type_variables(fun_type) - has_self_type = find_self_type( - fun_type, lambda name: self.api.lookup_qualified(name, defn, suppress_errors=True) - ) + typevars, has_self_type = self.infer_type_variables(fun_type) # Do not define a new type variable if already defined in scope. typevars = [ (name, tvar) for name, tvar in typevars if not self.is_defined_type_var(name, defn) @@ -1492,26 +1714,45 @@ def anal_array( *, allow_param_spec: bool = False, allow_param_spec_literals: bool = False, + allow_unpack: bool = False, ) -> list[Type]: old_allow_param_spec_literals = self.allow_param_spec_literals self.allow_param_spec_literals = allow_param_spec_literals res: list[Type] = [] for t in a: - res.append(self.anal_type(t, nested, allow_param_spec=allow_param_spec)) + res.append( + self.anal_type( + t, nested, allow_param_spec=allow_param_spec, allow_unpack=allow_unpack + ) + ) self.allow_param_spec_literals = old_allow_param_spec_literals return self.check_unpacks_in_list(res) - def anal_type(self, t: Type, nested: bool = True, *, allow_param_spec: bool = False) -> Type: + def anal_type( + self, + t: Type, + nested: bool = True, + *, + allow_param_spec: bool = False, + allow_unpack: bool = False, + allow_ellipsis: bool = False, + ) -> Type: if nested: self.nesting_level += 1 old_allow_required = self.allow_required self.allow_required = False + old_allow_ellipsis = self.allow_ellipsis + self.allow_ellipsis = allow_ellipsis + old_allow_unpack = self.allow_unpack + self.allow_unpack = allow_unpack try: analyzed = t.accept(self) finally: if nested: self.nesting_level -= 1 self.allow_required = old_allow_required + self.allow_ellipsis = old_allow_ellipsis + self.allow_unpack = old_allow_unpack if ( not allow_param_spec and isinstance(analyzed, ParamSpecType) @@ -1520,6 +1761,7 @@ def anal_type(self, t: Type, nested: bool = True, *, allow_param_spec: bool = Fa if analyzed.prefix.arg_types: self.fail("Invalid location for Concatenate", t, code=codes.VALID_TYPE) self.note("You can use Concatenate as the first argument to Callable", t) + analyzed = AnyType(TypeOfAny.from_error) else: self.fail( f'Invalid location for ParamSpec "{analyzed.name}"', t, code=codes.VALID_TYPE @@ -1529,18 +1771,21 @@ def anal_type(self, t: Type, nested: bool = True, *, allow_param_spec: bool = Fa "'Callable[{}, int]'".format(analyzed.name), t, ) + analyzed = AnyType(TypeOfAny.from_error) return analyzed def anal_var_def(self, var_def: TypeVarLikeType) -> TypeVarLikeType: if isinstance(var_def, TypeVarType): return TypeVarType( - var_def.name, - var_def.fullname, - var_def.id.raw_id, - self.anal_array(var_def.values), - var_def.upper_bound.accept(self), - var_def.variance, - var_def.line, + name=var_def.name, + fullname=var_def.fullname, + id=var_def.id.raw_id, + values=self.anal_array(var_def.values), + upper_bound=var_def.upper_bound.accept(self), + default=var_def.default.accept(self), + variance=var_def.variance, + line=var_def.line, + column=var_def.column, ) else: return var_def @@ -1569,7 +1814,10 @@ def check_unpacks_in_list(self, items: list[Type]) -> list[Type]: num_unpacks = 0 final_unpack = None for item in items: - if isinstance(item, UnpackType): + # TODO: handle forward references here, they appear as Unpack[Any]. + if isinstance(item, UnpackType) and not isinstance( + get_proper_type(item.type), TupleType + ): if not num_unpacks: new_items.append(item) num_unpacks += 1 @@ -1582,17 +1830,18 @@ def check_unpacks_in_list(self, items: list[Type]) -> list[Type]: self.fail("More than one Unpack in a type is not allowed", final_unpack) return new_items - def tuple_type(self, items: list[Type]) -> TupleType: + def tuple_type(self, items: list[Type], line: int, column: int) -> TupleType: any_type = AnyType(TypeOfAny.special_form) - return TupleType(items, fallback=self.named_type("builtins.tuple", [any_type])) + return TupleType( + items, fallback=self.named_type("builtins.tuple", [any_type]), line=line, column=column + ) TypeVarLikeList = List[Tuple[str, TypeVarLikeExpr]] class MsgCallback(Protocol): - def __call__(self, __msg: str, __ctx: Context, *, code: ErrorCode | None = None) -> None: - ... + def __call__(self, __msg: str, __ctx: Context, *, code: ErrorCode | None = None) -> None: ... def get_omitted_any( @@ -1600,12 +1849,12 @@ def get_omitted_any( fail: MsgCallback, note: MsgCallback, orig_type: Type, - python_version: tuple[int, int], + options: Options, fullname: str | None = None, unexpanded_type: Type | None = None, ) -> AnyType: if disallow_any: - nongen_builtins = get_nongen_builtins(python_version) + nongen_builtins = get_nongen_builtins(options.python_version) if fullname in nongen_builtins: typ = orig_type # We use a dedicated error message for builtin generics (as the most common case). @@ -1617,7 +1866,7 @@ def get_omitted_any( ) else: typ = unexpanded_type or orig_type - type_str = typ.name if isinstance(typ, UnboundType) else format_type_bare(typ) + type_str = typ.name if isinstance(typ, UnboundType) else format_type_bare(typ, options) fail( message_registry.BARE_GENERIC.format(quote_type_string(type_str)), @@ -1630,7 +1879,10 @@ def get_omitted_any( ) # Ideally, we'd check whether the type is quoted or `from __future__ annotations` # is set before issuing this note - if python_version < (3, 9) and base_fullname in GENERIC_STUB_NOT_AT_RUNTIME_TYPES: + if ( + options.python_version < (3, 9) + and base_fullname in GENERIC_STUB_NOT_AT_RUNTIME_TYPES + ): # Recommend `from __future__ import annotations` or to put type in quotes # (string literal escaping) for classes not generic at runtime note( @@ -1649,76 +1901,123 @@ def get_omitted_any( return any_type +def fix_type_var_tuple_argument(t: Instance) -> None: + if t.type.has_type_var_tuple_type: + args = list(t.args) + assert t.type.type_var_tuple_prefix is not None + tvt = t.type.defn.type_vars[t.type.type_var_tuple_prefix] + assert isinstance(tvt, TypeVarTupleType) + args[t.type.type_var_tuple_prefix] = UnpackType( + Instance(tvt.tuple_fallback.type, [args[t.type.type_var_tuple_prefix]]) + ) + t.args = tuple(args) + + def fix_instance( t: Instance, fail: MsgCallback, note: MsgCallback, disallow_any: bool, - python_version: tuple[int, int], + options: Options, use_generic_error: bool = False, unexpanded_type: Type | None = None, ) -> None: - """Fix a malformed instance by replacing all type arguments with Any. + """Fix a malformed instance by replacing all type arguments with TypeVar default or Any. Also emit a suitable error if this is not due to implicit Any's. """ - if len(t.args) == 0: - if use_generic_error: - fullname: str | None = None - else: - fullname = t.type.fullname - any_type = get_omitted_any( - disallow_any, fail, note, t, python_version, fullname, unexpanded_type - ) - t.args = (any_type,) * len(t.type.type_vars) - return - # Invalid number of type parameters. - fail( - wrong_type_arg_count(len(t.type.type_vars), str(len(t.args)), t.type.name), - t, - code=codes.TYPE_ARG, - ) - # Construct the correct number of type arguments, as - # otherwise the type checker may crash as it expects - # things to be right. - t.args = tuple(AnyType(TypeOfAny.from_error) for _ in t.type.type_vars) - t.invalid = True - - -def expand_type_alias( + arg_count = len(t.args) + min_tv_count = sum(not tv.has_default() for tv in t.type.defn.type_vars) + max_tv_count = len(t.type.type_vars) + if arg_count < min_tv_count or arg_count > max_tv_count: + # Don't use existing args if arg_count doesn't match + if arg_count > max_tv_count: + # Already wrong arg count error, don't emit missing type parameters error as well. + disallow_any = False + t.args = () + arg_count = 0 + + args: list[Type] = [*(t.args[:max_tv_count])] + any_type: AnyType | None = None + env: dict[TypeVarId, Type] = {} + + for tv, arg in itertools.zip_longest(t.type.defn.type_vars, t.args, fillvalue=None): + if tv is None: + continue + if arg is None: + if tv.has_default(): + arg = tv.default + else: + if any_type is None: + fullname = None if use_generic_error else t.type.fullname + any_type = get_omitted_any( + disallow_any, fail, note, t, options, fullname, unexpanded_type + ) + arg = any_type + args.append(arg) + env[tv.id] = arg + t.args = tuple(args) + fix_type_var_tuple_argument(t) + if not t.type.has_type_var_tuple_type: + with state.strict_optional_set(options.strict_optional): + fixed = expand_type(t, env) + assert isinstance(fixed, Instance) + t.args = fixed.args + + +def instantiate_type_alias( node: TypeAlias, args: list[Type], fail: MsgCallback, no_args: bool, ctx: Context, + options: Options, *, unexpanded_type: Type | None = None, disallow_any: bool = False, use_standard_error: bool = False, + empty_tuple_index: bool = False, ) -> Type: - """Expand a (generic) type alias target following the rules outlined in TypeAlias docstring. + """Create an instance of a (generic) type alias from alias node and type arguments. + We are following the rules outlined in TypeAlias docstring. Here: - target: original target type - args: types to be substituted in place of type variables + node: type alias node (definition) + args: type arguments (types to be substituted in place of type variables + when expanding the alias) fail: error reporter callback no_args: whether original definition used a bare generic `A = List` ctx: context where expansion happens unexpanded_type, disallow_any, use_standard_error: used to customize error messages """ - exp_len = len(node.alias_tvars) + # Type aliases are special, since they can be expanded during semantic analysis, + # so we need to normalize them as soon as possible. + # TODO: can this cause an infinite recursion? + args = flatten_nested_tuples(args) + if any(unknown_unpack(a) for a in args): + # This type is not ready to be validated, because of unknown total count. + # Note that we keep the kind of Any for consistency. + return set_any_tvars(node, [], ctx.line, ctx.column, options, special_form=True) + + max_tv_count = len(node.alias_tvars) act_len = len(args) - if exp_len > 0 and act_len == 0: + if ( + max_tv_count > 0 + and act_len == 0 + and not (empty_tuple_index and node.tvar_tuple_index is not None) + ): # Interpret bare Alias same as normal generic, i.e., Alias[Any, Any, ...] return set_any_tvars( node, + args, ctx.line, ctx.column, + options, disallow_any=disallow_any, fail=fail, unexpanded_type=unexpanded_type, ) - if exp_len == 0 and act_len == 0: + if max_tv_count == 0 and act_len == 0: if no_args: assert isinstance(node.target, Instance) # type: ignore[misc] # Note: this is the only case where we use an eager expansion. See more info about @@ -1726,7 +2025,7 @@ def expand_type_alias( return Instance(node.target.type, [], line=ctx.line, column=ctx.column) return TypeAliasType(node, [], line=ctx.line, column=ctx.column) if ( - exp_len == 0 + max_tv_count == 0 and act_len > 0 and isinstance(node.target, Instance) # type: ignore[misc] and no_args @@ -1735,15 +2034,66 @@ def expand_type_alias( tp.line = ctx.line tp.column = ctx.column return tp - if act_len != exp_len: - if use_standard_error: - # This is used if type alias is an internal representation of another type, - # for example a generic TypedDict or NamedTuple. - msg = wrong_type_arg_count(exp_len, str(act_len), node.name) - else: - msg = f"Bad number of arguments for type alias, expected: {exp_len}, given: {act_len}" - fail(msg, ctx, code=codes.TYPE_ARG) - return set_any_tvars(node, ctx.line, ctx.column, from_error=True) + if node.tvar_tuple_index is None: + if any(isinstance(a, UnpackType) for a in args): + # A variadic unpack in fixed size alias (fixed unpacks must be flattened by the caller) + fail(message_registry.INVALID_UNPACK_POSITION, ctx, code=codes.VALID_TYPE) + return set_any_tvars(node, [], ctx.line, ctx.column, options, from_error=True) + min_tv_count = sum(not tv.has_default() for tv in node.alias_tvars) + fill_typevars = act_len != max_tv_count + correct = min_tv_count <= act_len <= max_tv_count + else: + min_tv_count = sum( + not tv.has_default() and not isinstance(tv, TypeVarTupleType) + for tv in node.alias_tvars + ) + correct = act_len >= min_tv_count + for a in args: + if isinstance(a, UnpackType): + unpacked = get_proper_type(a.type) + if isinstance(unpacked, Instance) and unpacked.type.fullname == "builtins.tuple": + # Variadic tuple is always correct. + correct = True + fill_typevars = not correct + if fill_typevars: + if not correct: + if use_standard_error: + # This is used if type alias is an internal representation of another type, + # for example a generic TypedDict or NamedTuple. + msg = wrong_type_arg_count(max_tv_count, max_tv_count, str(act_len), node.name) + else: + if node.tvar_tuple_index is not None: + msg = ( + "Bad number of arguments for type alias," + f" expected at least {min_tv_count}, given {act_len}" + ) + elif min_tv_count != max_tv_count: + msg = ( + "Bad number of arguments for type alias," + f" expected between {min_tv_count} and {max_tv_count}, given {act_len}" + ) + else: + msg = ( + "Bad number of arguments for type alias," + f" expected {min_tv_count}, given {act_len}" + ) + fail(msg, ctx, code=codes.TYPE_ARG) + args = [] + return set_any_tvars(node, args, ctx.line, ctx.column, options, from_error=True) + elif node.tvar_tuple_index is not None: + # We also need to check if we are not performing a type variable tuple split. + unpack = find_unpack_in_list(args) + if unpack is not None: + unpack_arg = args[unpack] + assert isinstance(unpack_arg, UnpackType) + if isinstance(unpack_arg.type, TypeVarTupleType): + exp_prefix = node.tvar_tuple_index + act_prefix = unpack + exp_suffix = len(node.alias_tvars) - node.tvar_tuple_index - 1 + act_suffix = len(args) - unpack - 1 + if act_prefix < exp_prefix or act_suffix < exp_suffix: + fail("TypeVarTuple cannot be split", ctx, code=codes.TYPE_ARG) + return set_any_tvars(node, [], ctx.line, ctx.column, options, from_error=True) # TODO: we need to check args validity w.r.t alias.alias_tvars. # Otherwise invalid instantiations will be allowed in runtime context. # Note: in type context, these will be still caught by semanal_typeargs. @@ -1762,25 +2112,56 @@ def expand_type_alias( def set_any_tvars( node: TypeAlias, + args: list[Type], newline: int, newcolumn: int, + options: Options, *, from_error: bool = False, disallow_any: bool = False, + special_form: bool = False, fail: MsgCallback | None = None, unexpanded_type: Type | None = None, -) -> Type: +) -> TypeAliasType: if from_error or disallow_any: type_of_any = TypeOfAny.from_error + elif special_form: + type_of_any = TypeOfAny.special_form else: type_of_any = TypeOfAny.from_omitted_generics - if disallow_any and node.alias_tvars: + any_type = AnyType(type_of_any, line=newline, column=newcolumn) + + env: dict[TypeVarId, Type] = {} + used_any_type = False + has_type_var_tuple_type = False + for tv, arg in itertools.zip_longest(node.alias_tvars, args, fillvalue=None): + if tv is None: + continue + if arg is None: + if tv.has_default(): + arg = tv.default + else: + arg = any_type + used_any_type = True + if isinstance(tv, TypeVarTupleType): + # TODO Handle TypeVarTuple defaults + has_type_var_tuple_type = True + arg = UnpackType(Instance(tv.tuple_fallback.type, [any_type])) + args.append(arg) + env[tv.id] = arg + t = TypeAliasType(node, args, newline, newcolumn) + if not has_type_var_tuple_type: + fixed = expand_type(t, env) + assert isinstance(fixed, TypeAliasType) + t.args = fixed.args + + if used_any_type and disallow_any and node.alias_tvars: assert fail is not None if unexpanded_type: type_str = ( unexpanded_type.name if isinstance(unexpanded_type, UnboundType) - else format_type_bare(unexpanded_type) + else format_type_bare(unexpanded_type, options) ) else: type_str = node.name @@ -1790,21 +2171,7 @@ def set_any_tvars( Context(newline, newcolumn), code=codes.TYPE_ARG, ) - any_type = AnyType(type_of_any, line=newline, column=newcolumn) - return TypeAliasType(node, [any_type] * len(node.alias_tvars), newline, newcolumn) - - -def remove_dups(tvars: list[T]) -> list[T]: - if len(tvars) <= 1: - return tvars - # Get unique elements in order of appearance - all_tvars: set[T] = set() - new_tvars: list[T] = [] - for t in tvars: - if t not in all_tvars: - new_tvars.append(t) - all_tvars.add(t) - return new_tvars + return t def flatten_tvars(lists: list[list[T]]) -> list[T]: @@ -1816,67 +2183,6 @@ def flatten_tvars(lists: list[list[T]]) -> list[T]: return result -class TypeVarLikeQuery(TypeQuery[TypeVarLikeList]): - """Find TypeVar and ParamSpec references in an unbound type.""" - - def __init__( - self, - api: SemanticAnalyzerCoreInterface, - scope: TypeVarLikeScope, - *, - include_callables: bool = True, - ) -> None: - super().__init__(flatten_tvars) - self.api = api - self.scope = scope - self.include_callables = include_callables - # Only include type variables in type aliases args. This would be anyway - # that case if we expand (as target variables would be overridden with args) - # and it may cause infinite recursion on invalid (diverging) recursive aliases. - self.skip_alias_target = True - - def _seems_like_callable(self, type: UnboundType) -> bool: - if not type.args: - return False - return isinstance(type.args[0], (EllipsisType, TypeList, ParamSpecType)) - - def visit_unbound_type(self, t: UnboundType) -> TypeVarLikeList: - name = t.name - node = None - # Special case P.args and P.kwargs for ParamSpecs only. - if name.endswith("args"): - if name.endswith(".args") or name.endswith(".kwargs"): - base = ".".join(name.split(".")[:-1]) - n = self.api.lookup_qualified(base, t) - if n is not None and isinstance(n.node, ParamSpecExpr): - node = n - name = base - if node is None: - node = self.api.lookup_qualified(name, t) - if ( - node - and isinstance(node.node, TypeVarLikeExpr) - and self.scope.get_binding(node) is None - ): - assert isinstance(node.node, TypeVarLikeExpr) - return [(name, node.node)] - elif not self.include_callables and self._seems_like_callable(t): - return [] - elif node and node.fullname in LITERAL_TYPE_NAMES: - return [] - elif node and node.fullname in ANNOTATED_TYPE_NAMES and t.args: - # Don't query the second argument to Annotated for TypeVars - return self.query_types([t.args[0]]) - else: - return super().visit_unbound_type(t) - - def visit_callable_type(self, t: CallableType) -> TypeVarLikeList: - if self.include_callables: - return super().visit_callable_type(t) - else: - return [] - - class DivergingAliasDetector(TrivialSyntheticTypeTranslator): """See docstring of detect_diverging_alias() for details.""" @@ -1896,7 +2202,11 @@ def visit_type_alias_type(self, t: TypeAliasType) -> Type: assert t.alias is not None, f"Unfixed type alias {t.type_ref}" if t.alias in self.seen_nodes: for arg in t.args: - if not isinstance(arg, TypeVarLikeType) and has_type_vars(arg): + if not ( + isinstance(arg, TypeVarLikeType) + or isinstance(arg, UnpackType) + and isinstance(arg.type, TypeVarLikeType) + ) and has_type_vars(arg): self.diverging = True return t # All clear for this expansion chain. @@ -2023,36 +2333,70 @@ def make_optional_type(t: Type) -> Type: return UnionType([t, NoneType()], t.line, t.column) -def fix_instance_types( - t: Type, fail: MsgCallback, note: MsgCallback, python_version: tuple[int, int] -) -> None: - """Recursively fix all instance types (type argument count) in a given type. - - For example 'Union[Dict, List[str, int]]' will be transformed into - 'Union[Dict[Any, Any], List[Any]]' in place. - """ - t.accept(InstanceFixer(fail, note, python_version)) - - -class InstanceFixer(TypeTraverserVisitor): - def __init__( - self, fail: MsgCallback, note: MsgCallback, python_version: tuple[int, int] - ) -> None: - self.fail = fail - self.note = note - self.python_version = python_version - - def visit_instance(self, typ: Instance) -> None: - super().visit_instance(typ) - if len(typ.args) != len(typ.type.type_vars): - fix_instance( - typ, - self.fail, - self.note, - disallow_any=False, - python_version=self.python_version, - use_generic_error=True, +def validate_instance(t: Instance, fail: MsgCallback, empty_tuple_index: bool) -> bool: + """Check if this is a well-formed instance with respect to argument count/positions.""" + # TODO: combine logic with instantiate_type_alias(). + if any(unknown_unpack(a) for a in t.args): + # This type is not ready to be validated, because of unknown total count. + # TODO: is it OK to fill with TypeOfAny.from_error instead of special form? + return False + if t.type.has_type_var_tuple_type: + min_tv_count = sum( + not tv.has_default() and not isinstance(tv, TypeVarTupleType) + for tv in t.type.defn.type_vars + ) + correct = len(t.args) >= min_tv_count + if any( + isinstance(a, UnpackType) and isinstance(get_proper_type(a.type), Instance) + for a in t.args + ): + correct = True + if not t.args: + if not (empty_tuple_index and len(t.type.type_vars) == 1): + # The Any arguments should be set by the caller. + return False + elif not correct: + fail( + f"Bad number of arguments, expected: at least {min_tv_count}, given: {len(t.args)}", + t, + code=codes.TYPE_ARG, + ) + return False + else: + # We also need to check if we are not performing a type variable tuple split. + unpack = find_unpack_in_list(t.args) + if unpack is not None: + unpack_arg = t.args[unpack] + assert isinstance(unpack_arg, UnpackType) + if isinstance(unpack_arg.type, TypeVarTupleType): + assert t.type.type_var_tuple_prefix is not None + assert t.type.type_var_tuple_suffix is not None + exp_prefix = t.type.type_var_tuple_prefix + act_prefix = unpack + exp_suffix = t.type.type_var_tuple_suffix + act_suffix = len(t.args) - unpack - 1 + if act_prefix < exp_prefix or act_suffix < exp_suffix: + fail("TypeVarTuple cannot be split", t, code=codes.TYPE_ARG) + return False + elif any(isinstance(a, UnpackType) for a in t.args): + # A variadic unpack in fixed size instance (fixed unpacks must be flattened by the caller) + fail(message_registry.INVALID_UNPACK_POSITION, t, code=codes.VALID_TYPE) + t.args = () + return False + elif len(t.args) != len(t.type.type_vars): + # Invalid number of type parameters. + arg_count = len(t.args) + min_tv_count = sum(not tv.has_default() for tv in t.type.defn.type_vars) + max_tv_count = len(t.type.type_vars) + if arg_count and (arg_count < min_tv_count or arg_count > max_tv_count): + fail( + wrong_type_arg_count(min_tv_count, max_tv_count, str(arg_count), t.type.name), + t, + code=codes.TYPE_ARG, ) + t.invalid = True + return False + return True def find_self_type(typ: Type, lookup: Callable[[str], SymbolTableNode | None]) -> bool: @@ -2069,3 +2413,193 @@ def visit_unbound_type(self, t: UnboundType) -> bool: if sym and sym.fullname in SELF_TYPE_NAMES: return True return super().visit_unbound_type(t) + + +def unknown_unpack(t: Type) -> bool: + """Check if a given type is an unpack of an unknown type. + + Unfortunately, there is no robust way to distinguish forward references from + genuine undefined names here. But this worked well so far, although it looks + quite fragile. + """ + if isinstance(t, UnpackType): + unpacked = get_proper_type(t.type) + if isinstance(unpacked, AnyType) and unpacked.type_of_any == TypeOfAny.special_form: + return True + return False + + +class FindTypeVarVisitor(SyntheticTypeVisitor[None]): + """Type visitor that looks for type variable types and self types.""" + + def __init__(self, api: SemanticAnalyzerCoreInterface, scope: TypeVarLikeScope) -> None: + self.api = api + self.scope = scope + self.type_var_likes: list[tuple[str, TypeVarLikeExpr]] = [] + self.has_self_type = False + self.seen_aliases: set[TypeAliasType] | None = None + self.include_callables = True + + def _seems_like_callable(self, type: UnboundType) -> bool: + if not type.args: + return False + return isinstance(type.args[0], (EllipsisType, TypeList, ParamSpecType)) + + def visit_unbound_type(self, t: UnboundType) -> None: + name = t.name + node = None + + # Special case P.args and P.kwargs for ParamSpecs only. + if name.endswith("args"): + if name.endswith((".args", ".kwargs")): + base = ".".join(name.split(".")[:-1]) + n = self.api.lookup_qualified(base, t) + if n is not None and isinstance(n.node, ParamSpecExpr): + node = n + name = base + if node is None: + node = self.api.lookup_qualified(name, t) + if node and node.fullname in SELF_TYPE_NAMES: + self.has_self_type = True + if ( + node + and isinstance(node.node, TypeVarLikeExpr) + and self.scope.get_binding(node) is None + ): + if (name, node.node) not in self.type_var_likes: + self.type_var_likes.append((name, node.node)) + elif not self.include_callables and self._seems_like_callable(t): + if find_self_type( + t, lambda name: self.api.lookup_qualified(name, t, suppress_errors=True) + ): + self.has_self_type = True + return + elif node and node.fullname in LITERAL_TYPE_NAMES: + return + elif node and node.fullname in ANNOTATED_TYPE_NAMES and t.args: + # Don't query the second argument to Annotated for TypeVars + self.process_types([t.args[0]]) + elif t.args: + self.process_types(t.args) + + def visit_type_list(self, t: TypeList) -> None: + self.process_types(t.items) + + def visit_callable_argument(self, t: CallableArgument) -> None: + t.typ.accept(self) + + def visit_any(self, t: AnyType) -> None: + pass + + def visit_uninhabited_type(self, t: UninhabitedType) -> None: + pass + + def visit_none_type(self, t: NoneType) -> None: + pass + + def visit_erased_type(self, t: ErasedType) -> None: + pass + + def visit_deleted_type(self, t: DeletedType) -> None: + pass + + def visit_type_var(self, t: TypeVarType) -> None: + self.process_types([t.upper_bound, t.default] + t.values) + + def visit_param_spec(self, t: ParamSpecType) -> None: + self.process_types([t.upper_bound, t.default]) + + def visit_type_var_tuple(self, t: TypeVarTupleType) -> None: + self.process_types([t.upper_bound, t.default]) + + def visit_unpack_type(self, t: UnpackType) -> None: + self.process_types([t.type]) + + def visit_parameters(self, t: Parameters) -> None: + self.process_types(t.arg_types) + + def visit_partial_type(self, t: PartialType) -> None: + pass + + def visit_instance(self, t: Instance) -> None: + self.process_types(t.args) + + def visit_callable_type(self, t: CallableType) -> None: + # FIX generics + self.process_types(t.arg_types) + t.ret_type.accept(self) + + def visit_tuple_type(self, t: TupleType) -> None: + self.process_types(t.items) + + def visit_typeddict_type(self, t: TypedDictType) -> None: + self.process_types(list(t.items.values())) + + def visit_raw_expression_type(self, t: RawExpressionType) -> None: + if t.node is not None: + t.node.accept(self) + + def visit_literal_type(self, t: LiteralType) -> None: + pass + + def visit_union_type(self, t: UnionType) -> None: + self.process_types(t.items) + + def visit_overloaded(self, t: Overloaded) -> None: + self.process_types(t.items) # type: ignore[arg-type] + + def visit_type_type(self, t: TypeType) -> None: + t.item.accept(self) + + def visit_ellipsis_type(self, t: EllipsisType) -> None: + pass + + def visit_placeholder_type(self, t: PlaceholderType) -> None: + return self.process_types(t.args) + + def visit_type_alias_type(self, t: TypeAliasType) -> None: + # Skip type aliases in already visited types to avoid infinite recursion. + if self.seen_aliases is None: + self.seen_aliases = set() + elif t in self.seen_aliases: + return + self.seen_aliases.add(t) + self.process_types(t.args) + + def process_types(self, types: list[Type] | tuple[Type, ...]) -> None: + # Redundant type check helps mypyc. + if isinstance(types, list): + for t in types: + t.accept(self) + else: + for t in types: + t.accept(self) + + +class TypeVarDefaultTranslator(TrivialSyntheticTypeTranslator): + """Type translate visitor that replaces UnboundTypes with in-scope TypeVars.""" + + def __init__( + self, api: SemanticAnalyzerInterface, tvar_expr_name: str, context: Context + ) -> None: + self.api = api + self.tvar_expr_name = tvar_expr_name + self.context = context + + def visit_unbound_type(self, t: UnboundType) -> Type: + sym = self.api.lookup_qualified(t.name, t, suppress_errors=True) + if sym is not None: + if type_var := self.api.tvar_scope.get_binding(sym): + return type_var + if isinstance(sym.node, TypeVarLikeExpr): + self.api.fail( + f'Type parameter "{self.tvar_expr_name}" has a default type ' + "that refers to one or more type variables that are out of scope", + self.context, + ) + return AnyType(TypeOfAny.from_error) + return super().visit_unbound_type(t) + + def visit_type_alias_type(self, t: TypeAliasType) -> Type: + # TypeAliasTypes are analyzed separately already, just return it + return t diff --git a/mypy/typeops.py b/mypy/typeops.py index 8c01fb118076..a59bd3739562 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -39,6 +39,7 @@ Instance, LiteralType, NoneType, + NormalizedCallableType, Overloaded, Parameters, ParamSpecType, @@ -76,12 +77,18 @@ def is_recursive_pair(s: Type, t: Type) -> bool: isinstance(get_proper_type(t), (Instance, UnionType)) or isinstance(t, TypeAliasType) and t.is_recursive + # Tuple types are special, they can cause an infinite recursion even if + # the other type is not recursive, because of the tuple fallback that is + # calculated "on the fly". + or isinstance(get_proper_type(s), TupleType) ) if isinstance(t, TypeAliasType) and t.is_recursive: return ( isinstance(get_proper_type(s), (Instance, UnionType)) or isinstance(s, TypeAliasType) and s.is_recursive + # Same as above. + or isinstance(get_proper_type(t), TupleType) ) return False @@ -98,15 +105,17 @@ def tuple_fallback(typ: TupleType) -> Instance: if isinstance(item, UnpackType): unpacked_type = get_proper_type(item.type) if isinstance(unpacked_type, TypeVarTupleType): - items.append(unpacked_type.upper_bound) - elif isinstance(unpacked_type, TupleType): - # TODO: might make sense to do recursion here to support nested unpacks - # of tuple constants - items.extend(unpacked_type.items) + unpacked_type = get_proper_type(unpacked_type.upper_bound) + if ( + isinstance(unpacked_type, Instance) + and unpacked_type.type.fullname == "builtins.tuple" + ): + items.append(unpacked_type.args[0]) else: raise NotImplementedError else: items.append(item) + # TODO: we should really use a union here, tuple types are special. return Instance(info, [join_type_list(items)], extra_attrs=typ.partial_fallback.extra_attrs) @@ -235,14 +244,18 @@ class C(D[E[T]], Generic[T]): ... return expand_type_by_instance(typ, inst_type) -def supported_self_type(typ: ProperType) -> bool: +def supported_self_type(typ: ProperType, allow_callable: bool = True) -> bool: """Is this a supported kind of explicit self-types? - Currently, this means a X or Type[X], where X is an instance or + Currently, this means an X or Type[X], where X is an instance or a type variable with an instance upper bound. """ if isinstance(typ, TypeType): return supported_self_type(typ.item) + if allow_callable and isinstance(typ, CallableType): + # Special case: allow class callable instead of Type[...] as cls annotation, + # as well as callable self for callback protocols. + return True return isinstance(typ, TypeVarType) or ( isinstance(typ, Instance) and typ != fill_typevars(typ.type) ) @@ -292,8 +305,12 @@ class B(A): pass return cast(F, func) self_param_type = get_proper_type(func.arg_types[0]) - variables: Sequence[TypeVarLikeType] = [] - if func.variables and supported_self_type(self_param_type): + variables: Sequence[TypeVarLikeType] + # Having a def __call__(self: Callable[...], ...) can cause infinite recursion. Although + # this special-casing looks not very principled, there is nothing meaningful we can infer + # from such definition, since it is inherently indefinitely recursive. + allow_callable = func.name is None or not func.name.startswith("__call__ of") + if func.variables and supported_self_type(self_param_type, allow_callable=allow_callable): from mypy.infer import infer_type_arguments if original_type is None: @@ -301,44 +318,40 @@ class B(A): pass original_type = erase_to_bound(self_param_type) original_type = get_proper_type(original_type) - all_ids = func.type_var_ids() - typeargs = infer_type_arguments(all_ids, self_param_type, original_type, is_supertype=True) + # Find which of method type variables appear in the type of "self". + self_ids = {tv.id for tv in get_all_type_vars(self_param_type)} + self_vars = [tv for tv in func.variables if tv.id in self_ids] + + # Solve for these type arguments using the actual class or instance type. + typeargs = infer_type_arguments( + self_vars, self_param_type, original_type, is_supertype=True + ) if ( is_classmethod - # TODO: why do we need the extra guards here? and any(isinstance(get_proper_type(t), UninhabitedType) for t in typeargs) and isinstance(original_type, (Instance, TypeVarType, TupleType)) ): - # In case we call a classmethod through an instance x, fallback to type(x) + # In case we call a classmethod through an instance x, fallback to type(x). typeargs = infer_type_arguments( - all_ids, self_param_type, TypeType(original_type), is_supertype=True + self_vars, self_param_type, TypeType(original_type), is_supertype=True ) - ids = [tid for tid in all_ids if any(tid == t.id for t in get_type_vars(self_param_type))] - - # Technically, some constrains might be unsolvable, make them . + # Update the method signature with the solutions found. + # Technically, some constraints might be unsolvable, make them Never. to_apply = [t if t is not None else UninhabitedType() for t in typeargs] - - def expand(target: Type) -> Type: - return expand_type(target, {id: to_apply[all_ids.index(id)] for id in ids}) - - arg_types = [expand(x) for x in func.arg_types[1:]] - ret_type = expand(func.ret_type) - variables = [v for v in func.variables if v.id not in ids] + func = expand_type(func, {tv.id: arg for tv, arg in zip(self_vars, to_apply)}) + variables = [v for v in func.variables if v not in self_vars] else: - arg_types = func.arg_types[1:] - ret_type = func.ret_type variables = func.variables original_type = get_proper_type(original_type) if isinstance(original_type, CallableType) and original_type.is_type_obj(): original_type = TypeType.make_normalized(original_type.ret_type) res = func.copy_modified( - arg_types=arg_types, + arg_types=func.arg_types[1:], arg_kinds=func.arg_kinds[1:], arg_names=func.arg_names[1:], variables=variables, - ret_type=ret_type, bound_args=[original_type], ) return cast(F, res) @@ -356,7 +369,7 @@ def erase_to_bound(t: Type) -> Type: def callable_corresponding_argument( - typ: CallableType | Parameters, model: FormalArgument + typ: NormalizedCallableType | Parameters, model: FormalArgument ) -> FormalArgument | None: """Return the argument a function that corresponds to `model`""" @@ -385,25 +398,6 @@ def callable_corresponding_argument( return by_name if by_name is not None else by_pos -def simple_literal_value_key(t: ProperType) -> tuple[str, ...] | None: - """Return a hashable description of simple literal type. - - Return None if not a simple literal type. - - The return value can be used to simplify away duplicate types in - unions by comparing keys for equality. For now enum, string or - Instance with string last_known_value are supported. - """ - if isinstance(t, LiteralType): - if t.fallback.type.is_enum or t.fallback.type.fullname == "builtins.str": - assert isinstance(t.value, str) - return "literal", t.value, t.fallback.type.fullname - if isinstance(t, Instance): - if t.last_known_value is not None and isinstance(t.last_known_value.value, str): - return "instance", t.last_known_value.value, t.type.fullname - return None - - def simple_literal_type(t: ProperType | None) -> Instance | None: """Extract the underlying fallback Instance type for a simple Literal""" if isinstance(t, Instance) and t.last_known_value is not None: @@ -414,7 +408,6 @@ def simple_literal_type(t: ProperType | None) -> Instance | None: def is_simple_literal(t: ProperType) -> bool: - """Fast way to check if simple_literal_value_key() would return a non-None value.""" if isinstance(t, LiteralType): return t.fallback.type.is_enum or t.fallback.type.fullname == "builtins.str" if isinstance(t, Instance): @@ -500,79 +493,91 @@ def make_simplified_union( def _remove_redundant_union_items(items: list[Type], keep_erased: bool) -> list[Type]: from mypy.subtypes import is_proper_subtype - removed: set[int] = set() - seen: set[tuple[str, ...]] = set() - - # NB: having a separate fast path for Union of Literal and slow path for other things - # would arguably be cleaner, however it breaks down when simplifying the Union of two - # different enum types as try_expanding_sum_type_to_union works recursively and will - # trigger intermediate simplifications that would render the fast path useless - for i, item in enumerate(items): - proper_item = get_proper_type(item) - if i in removed: - continue - # Avoid slow nested for loop for Union of Literal of strings/enums (issue #9169) - k = simple_literal_value_key(proper_item) - if k is not None: - if k in seen: - removed.add(i) - continue - - # NB: one would naively expect that it would be safe to skip the slow path - # always for literals. One would be sorely mistaken. Indeed, some simplifications - # such as that of None/Optional when strict optional is false, do require that we - # proceed with the slow path. Thankfully, all literals will have the same subtype - # relationship to non-literal types, so we only need to do that walk for the first - # literal, which keeps the fast path fast even in the presence of a mixture of - # literals and other types. - safe_skip = len(seen) > 0 - seen.add(k) - if safe_skip: + # The first pass through this loop, we check if later items are subtypes of earlier items. + # The second pass through this loop, we check if earlier items are subtypes of later items + # (by reversing the remaining items) + for _direction in range(2): + new_items: list[Type] = [] + # seen is a map from a type to its index in new_items + seen: dict[ProperType, int] = {} + unduplicated_literal_fallbacks: set[Instance] | None = None + for ti in items: + proper_ti = get_proper_type(ti) + + # UninhabitedType is always redundant + if isinstance(proper_ti, UninhabitedType): continue - # Keep track of the truthiness info for deleted subtypes which can be relevant - cbt = cbf = False - for j, tj in enumerate(items): - proper_tj = get_proper_type(tj) - if ( - i == j - # avoid further checks if this item was already marked redundant. - or j in removed - # if the current item is a simple literal then this simplification loop can - # safely skip all other simple literals as two literals will only ever be - # subtypes of each other if they are equal, which is already handled above. - # However, if the current item is not a literal, it might plausibly be a - # supertype of other literals in the union, so we must check them again. - # This is an important optimization as is_proper_subtype is pretty expensive. - or (k is not None and is_simple_literal(proper_tj)) - ): - continue - # actual redundancy checks (XXX?) - if is_redundant_literal_instance(proper_item, proper_tj) and is_proper_subtype( - tj, item, keep_erased_types=keep_erased, ignore_promotions=True + duplicate_index = -1 + # Quickly check if we've seen this type + if proper_ti in seen: + duplicate_index = seen[proper_ti] + elif ( + isinstance(proper_ti, LiteralType) + and unduplicated_literal_fallbacks is not None + and proper_ti.fallback in unduplicated_literal_fallbacks ): - # We found a redundant item in the union. - removed.add(j) - cbt = cbt or tj.can_be_true - cbf = cbf or tj.can_be_false - # if deleted subtypes had more general truthiness, use that - if not item.can_be_true and cbt: - items[i] = true_or_false(item) - elif not item.can_be_false and cbf: - items[i] = true_or_false(item) + # This is an optimisation for unions with many LiteralType + # We've already checked for exact duplicates. This means that any super type of + # the LiteralType must be a super type of its fallback. If we've gone through + # the expensive loop below and found no super type for a previous LiteralType + # with the same fallback, we can skip doing that work again and just add the type + # to new_items + pass + else: + # If not, check if we've seen a supertype of this type + for j, tj in enumerate(new_items): + tj = get_proper_type(tj) + # If tj is an Instance with a last_known_value, do not remove proper_ti + # (unless it's an instance with the same last_known_value) + if ( + isinstance(tj, Instance) + and tj.last_known_value is not None + and not ( + isinstance(proper_ti, Instance) + and tj.last_known_value == proper_ti.last_known_value + ) + ): + continue + + if is_proper_subtype( + ti, tj, keep_erased_types=keep_erased, ignore_promotions=True + ): + duplicate_index = j + break + if duplicate_index != -1: + # If deleted subtypes had more general truthiness, use that + orig_item = new_items[duplicate_index] + if not orig_item.can_be_true and ti.can_be_true: + new_items[duplicate_index] = true_or_false(orig_item) + elif not orig_item.can_be_false and ti.can_be_false: + new_items[duplicate_index] = true_or_false(orig_item) + else: + # We have a non-duplicate item, add it to new_items + seen[proper_ti] = len(new_items) + new_items.append(ti) + if isinstance(proper_ti, LiteralType): + if unduplicated_literal_fallbacks is None: + unduplicated_literal_fallbacks = set() + unduplicated_literal_fallbacks.add(proper_ti.fallback) - return [items[i] for i in range(len(items)) if i not in removed] + items = new_items + if len(items) <= 1: + break + items.reverse() + return items -def _get_type_special_method_bool_ret_type(t: Type) -> Type | None: + +def _get_type_method_ret_type(t: Type, *, name: str) -> Type | None: t = get_proper_type(t) if isinstance(t, Instance): - bool_method = t.type.get("__bool__") - if bool_method: - callee = get_proper_type(bool_method.type) - if isinstance(callee, CallableType): - return callee.ret_type + sym = t.type.get(name) + if sym: + sym_type = get_proper_type(sym.type) + if isinstance(sym_type, CallableType): + return sym_type.ret_type return None @@ -595,12 +600,12 @@ def true_only(t: Type) -> ProperType: can_be_true_items = [item for item in new_items if item.can_be_true] return make_simplified_union(can_be_true_items, line=t.line, column=t.column) else: - ret_type = _get_type_special_method_bool_ret_type(t) + ret_type = _get_type_method_ret_type(t, name="__bool__") or _get_type_method_ret_type( + t, name="__len__" + ) - if ret_type and ret_type.can_be_false and not ret_type.can_be_true: - new_t = copy_type(t) - new_t.can_be_true = False - return new_t + if ret_type and not ret_type.can_be_true: + return UninhabitedType(line=t.line, column=t.column) new_t = copy_type(t) new_t.can_be_false = False @@ -630,12 +635,15 @@ def false_only(t: Type) -> ProperType: can_be_false_items = [item for item in new_items if item.can_be_false] return make_simplified_union(can_be_false_items, line=t.line, column=t.column) else: - ret_type = _get_type_special_method_bool_ret_type(t) + ret_type = _get_type_method_ret_type(t, name="__bool__") or _get_type_method_ret_type( + t, name="__len__" + ) - if ret_type and ret_type.can_be_true and not ret_type.can_be_false: - new_t = copy_type(t) - new_t.can_be_false = False - return new_t + if ret_type: + if not ret_type.can_be_false: + return UninhabitedType(line=t.line) + elif isinstance(t, Instance) and t.type.is_final: + return UninhabitedType(line=t.line) new_t = copy_type(t) new_t.can_be_true = False @@ -662,8 +670,7 @@ def erase_def_to_union_or_bound(tdef: TypeVarLikeType) -> Type: # TODO(PEP612): fix for ParamSpecType if isinstance(tdef, ParamSpecType): return AnyType(TypeOfAny.from_error) - assert isinstance(tdef, TypeVarType) - if tdef.values: + if isinstance(tdef, TypeVarType) and tdef.values: return make_simplified_union(tdef.values) else: return tdef.upper_bound @@ -707,7 +714,7 @@ def callable_type( fdef: FuncItem, fallback: Instance, ret_type: Type | None = None ) -> CallableType: # TODO: somewhat unfortunate duplication with prepare_method_signature in semanal - if fdef.info and not fdef.is_static and fdef.arg_names: + if fdef.info and (not fdef.is_static or fdef.name == "__new__") and fdef.arg_names: self_type: Type = fill_typevars(fdef.info) if fdef.is_class or fdef.name == "__new__": self_type = TypeType.make_normalized(self_type) @@ -878,6 +885,9 @@ class Status(Enum): # Skip these since Enum will remove it if name in ENUM_REMOVED_PROPS: continue + # Skip private attributes + if name.startswith("__"): + continue new_items.append(LiteralType(name, typ)) return make_simplified_union(new_items, contract_literals=False) elif typ.type.fullname == "builtins.bool": @@ -908,9 +918,11 @@ def try_contracting_literals_in_union(types: Sequence[Type]) -> list[ProperType] if typ.fallback.type.is_enum or isinstance(typ.value, bool): if fullname not in sum_types: sum_types[fullname] = ( - set(typ.fallback.get_enum_values()) - if typ.fallback.type.is_enum - else {True, False}, + ( + set(typ.fallback.get_enum_values()) + if typ.fallback.type.is_enum + else {True, False} + ), [], ) literals, indexes = sum_types[fullname] @@ -947,22 +959,34 @@ def coerce_to_literal(typ: Type) -> Type: def get_type_vars(tp: Type) -> list[TypeVarType]: - return tp.accept(TypeVarExtractor()) + return cast("list[TypeVarType]", tp.accept(TypeVarExtractor())) + +def get_all_type_vars(tp: Type) -> list[TypeVarLikeType]: + # TODO: should we always use this function instead of get_type_vars() above? + return tp.accept(TypeVarExtractor(include_all=True)) -class TypeVarExtractor(TypeQuery[List[TypeVarType]]): - def __init__(self) -> None: + +class TypeVarExtractor(TypeQuery[List[TypeVarLikeType]]): + def __init__(self, include_all: bool = False) -> None: super().__init__(self._merge) + self.include_all = include_all - def _merge(self, iter: Iterable[list[TypeVarType]]) -> list[TypeVarType]: + def _merge(self, iter: Iterable[list[TypeVarLikeType]]) -> list[TypeVarLikeType]: out = [] for item in iter: out.extend(item) return out - def visit_type_var(self, t: TypeVarType) -> list[TypeVarType]: + def visit_type_var(self, t: TypeVarType) -> list[TypeVarLikeType]: return [t] + def visit_param_spec(self, t: ParamSpecType) -> list[TypeVarLikeType]: + return [t] if self.include_all else [] + + def visit_type_var_tuple(self, t: TypeVarTupleType) -> list[TypeVarLikeType]: + return [t] if self.include_all else [] + def custom_special_method(typ: Type, name: str, check_all: bool = False) -> bool: """Does this type have a custom special method such as __format__() or __eq__()? @@ -974,7 +998,7 @@ def custom_special_method(typ: Type, name: str, check_all: bool = False) -> bool method = typ.type.get(name) if method and isinstance(method.node, (SYMBOL_FUNCBASE_TYPES, Decorator, Var)): if method.node.info: - return not method.node.info.fullname.startswith("builtins.") + return not method.node.info.fullname.startswith(("builtins.", "typing.")) return False if isinstance(typ, UnionType): if check_all: @@ -982,7 +1006,7 @@ def custom_special_method(typ: Type, name: str, check_all: bool = False) -> bool return any(custom_special_method(t, name) for t in typ.items) if isinstance(typ, TupleType): return custom_special_method(tuple_fallback(typ), name, check_all) - if isinstance(typ, CallableType) and typ.is_type_obj(): + if isinstance(typ, FunctionLike) and typ.is_type_obj(): # Look up __method__ on the metaclass for class objects. return custom_special_method(typ.fallback, name, check_all) if isinstance(typ, AnyType): @@ -992,17 +1016,6 @@ def custom_special_method(typ: Type, name: str, check_all: bool = False) -> bool return False -def is_redundant_literal_instance(general: ProperType, specific: ProperType) -> bool: - if not isinstance(general, Instance) or general.last_known_value is None: - return True - if isinstance(specific, Instance) and specific.last_known_value == general.last_known_value: - return True - if isinstance(specific, UninhabitedType): - return True - - return False - - def separate_union_literals(t: UnionType) -> tuple[Sequence[LiteralType], Sequence[Type]]: """Separate literals from other members in a union type.""" literal_items = [] @@ -1050,3 +1063,23 @@ def fixup_partial_type(typ: Type) -> Type: return UnionType.make_union([AnyType(TypeOfAny.unannotated), NoneType()]) else: return Instance(typ.type, [AnyType(TypeOfAny.unannotated)] * len(typ.type.type_vars)) + + +def get_protocol_member(left: Instance, member: str, class_obj: bool) -> ProperType | None: + if member == "__call__" and class_obj: + # Special case: class objects always have __call__ that is just the constructor. + from mypy.checkmember import type_object_type + + def named_type(fullname: str) -> Instance: + return Instance(left.type.mro[-1], []) + + return type_object_type(left.type, named_type) + + if member == "__call__" and left.type.is_metaclass(): + # Special case: we want to avoid falling back to metaclass __call__ + # if constructor signature didn't match, this can cause many false negatives. + return None + + from mypy.subtypes import find_member + + return get_proper_type(find_member(member, left, left, class_obj=class_obj)) diff --git a/mypy/types.py b/mypy/types.py index 9858559ad5c1..5573dc9efe0e 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -7,9 +7,9 @@ from typing import ( TYPE_CHECKING, Any, - Callable, ClassVar, Dict, + Final, Iterable, NamedTuple, NewType, @@ -18,7 +18,7 @@ Union, cast, ) -from typing_extensions import Final, TypeAlias as _TypeAlias, TypeGuard, overload +from typing_extensions import Self, TypeAlias as _TypeAlias, TypeGuard, overload import mypy.nodes from mypy.bogus_type import Bogus @@ -30,9 +30,9 @@ ArgKind, FakeInfo, FuncDef, - FuncItem, SymbolNode, ) +from mypy.options import Options from mypy.state import state from mypy.util import IdMapper @@ -56,7 +56,7 @@ # 1. types.LiteralType's serialize and deserialize methods: this method # needs to make sure it can convert the below types into JSON and back. # -# 2. types.LiteralType's 'alue_repr` method: this method is ultimately used +# 2. types.LiteralType's 'value_repr` method: this method is ultimately used # by TypeStrVisitor's visit_literal_type to generate a reasonable # repr-able output. # @@ -85,6 +85,15 @@ TypeVisitor as TypeVisitor, ) +TYPE_VAR_LIKE_NAMES: Final = ( + "typing.TypeVar", + "typing_extensions.TypeVar", + "typing.ParamSpec", + "typing_extensions.ParamSpec", + "typing.TypeVarTuple", + "typing_extensions.TypeVarTuple", +) + TYPED_NAMEDTUPLE_NAMES: Final = ("typing.NamedTuple", "typing_extensions.NamedTuple") # Supported names of TypedDict type constructors. @@ -113,12 +122,18 @@ # Supported @final decorator names. FINAL_DECORATOR_NAMES: Final = ("typing.final", "typing_extensions.final") +# Supported @type_check_only names. +TYPE_CHECK_ONLY_NAMES: Final = ("typing.type_check_only", "typing_extensions.type_check_only") + # Supported Literal type names. LITERAL_TYPE_NAMES: Final = ("typing.Literal", "typing_extensions.Literal") # Supported Annotated type names. ANNOTATED_TYPE_NAMES: Final = ("typing.Annotated", "typing_extensions.Annotated") +# Supported @deprecated type names +DEPRECATED_TYPE_NAMES: Final = ("warnings.deprecated", "typing_extensions.deprecated") + # We use this constant in various places when checking `tuple` subtyping: TUPLE_LIKE_INSTANCE_NAMES: Final = ( "builtins.tuple", @@ -128,11 +143,8 @@ "typing.Reversible", ) -REVEAL_TYPE_NAMES: Final = ( - "builtins.reveal_type", - "typing.reveal_type", - "typing_extensions.reveal_type", -) +IMPORTED_REVEAL_TYPE_NAMES: Final = ("typing.reveal_type", "typing_extensions.reveal_type") +REVEAL_TYPE_NAMES: Final = ("builtins.reveal_type", *IMPORTED_REVEAL_TYPE_NAMES) ASSERT_TYPE_NAMES: Final = ("typing.assert_type", "typing_extensions.assert_type") @@ -151,12 +163,19 @@ ) # Mypyc fixed-width native int types (compatible with builtins.int) -MYPYC_NATIVE_INT_NAMES: Final = ("mypy_extensions.i64", "mypy_extensions.i32") +MYPYC_NATIVE_INT_NAMES: Final = ( + "mypy_extensions.i64", + "mypy_extensions.i32", + "mypy_extensions.i16", + "mypy_extensions.u8", +) DATACLASS_TRANSFORM_NAMES: Final = ( "typing.dataclass_transform", "typing_extensions.dataclass_transform", ) +# Supported @override decorator names. +OVERRIDE_DECORATOR_NAMES: Final = ("typing.override", "typing_extensions.override") # A placeholder used for Bogus[...] parameters _dummy: Final[Any] = object() @@ -183,7 +202,7 @@ class TypeOfAny: # Does this Any come from an error? from_error: Final = 5 # Is this a type that can't be represented in mypy's type system? For instance, type of - # call to NewType...). Even though these types aren't real Anys, we treat them as such. + # call to NewType(...). Even though these types aren't real Anys, we treat them as such. # Also used for variables named '_'. special_form: Final = 6 # Does this Any come from interaction with another Any? @@ -252,11 +271,17 @@ def can_be_true_default(self) -> bool: def can_be_false_default(self) -> bool: return True + def resolve_string_annotation(self) -> Type: + return self + def accept(self, visitor: TypeVisitor[T]) -> T: - raise RuntimeError("Not implemented") + raise RuntimeError("Not implemented", type(self)) def __repr__(self) -> str: - return self.accept(TypeStrVisitor()) + return self.accept(TypeStrVisitor(options=Options())) + + def str_with_options(self, options: Options) -> str: + return self.accept(TypeStrVisitor(options=options)) def serialize(self) -> JsonDict | str: raise NotImplementedError(f"Cannot serialize {self.__class__.__name__} instance") @@ -311,9 +336,27 @@ def _expand_once(self) -> Type: # as their target. assert isinstance(self.alias.target, Instance) # type: ignore[misc] return self.alias.target.copy_modified(args=self.args) - return replace_alias_tvars( - self.alias.target, self.alias.alias_tvars, self.args, self.line, self.column - ) + + # TODO: this logic duplicates the one in expand_type_by_instance(). + if self.alias.tvar_tuple_index is None: + mapping = {v.id: s for (v, s) in zip(self.alias.alias_tvars, self.args)} + else: + prefix = self.alias.tvar_tuple_index + suffix = len(self.alias.alias_tvars) - self.alias.tvar_tuple_index - 1 + start, middle, end = split_with_prefix_and_suffix(tuple(self.args), prefix, suffix) + tvar = self.alias.alias_tvars[prefix] + assert isinstance(tvar, TypeVarTupleType) + mapping = {tvar.id: TupleType(list(middle), tvar.tuple_fallback)} + for tvar, sub in zip( + self.alias.alias_tvars[:prefix] + self.alias.alias_tvars[prefix + 1 :], start + end + ): + mapping[tvar.id] = sub + + new_tp = self.alias.target.accept(InstantiateAliasVisitor(mapping)) + new_tp.accept(LocationSetter(self.line, self.column)) + new_tp.line = self.line + new_tp.column = self.column + return new_tp def _partial_expansion(self, nothing_args: bool = False) -> tuple[ProperType, bool]: # Private method mostly for debugging and testing. @@ -410,7 +453,7 @@ class TypeGuardedType(Type): __slots__ = ("type_guard",) - def __init__(self, type_guard: Type): + def __init__(self, type_guard: Type) -> None: super().__init__(line=type_guard.line, column=type_guard.column) self.type_guard = type_guard @@ -505,13 +548,13 @@ def is_meta_var(self) -> bool: class TypeVarLikeType(ProperType): - - __slots__ = ("name", "fullname", "id", "upper_bound") + __slots__ = ("name", "fullname", "id", "upper_bound", "default") name: str # Name (may be qualified) fullname: str # Fully qualified name id: TypeVarId upper_bound: Type + default: Type def __init__( self, @@ -519,6 +562,7 @@ def __init__( fullname: str, id: TypeVarId | int, upper_bound: Type, + default: Type, line: int = -1, column: int = -1, ) -> None: @@ -529,6 +573,7 @@ def __init__( id = TypeVarId(id) self.id = id self.upper_bound = upper_bound + self.default = default def serialize(self) -> JsonDict: raise NotImplementedError @@ -537,6 +582,18 @@ def serialize(self) -> JsonDict: def deserialize(cls, data: JsonDict) -> TypeVarLikeType: raise NotImplementedError + def copy_modified(self, *, id: TypeVarId, **kwargs: Any) -> Self: + raise NotImplementedError + + @classmethod + def new_unification_variable(cls, old: Self) -> Self: + new_id = TypeVarId.new(meta_level=1) + return old.copy_modified(id=new_id) + + def has_default(self) -> bool: + t = get_proper_type(self.default) + return not (isinstance(t, AnyType) and t.type_of_any == TypeOfAny.from_omitted_generics) + class TypeVarType(TypeVarLikeType): """Type that refers to a type variable.""" @@ -553,37 +610,37 @@ def __init__( id: TypeVarId | int, values: list[Type], upper_bound: Type, + default: Type, variance: int = INVARIANT, line: int = -1, column: int = -1, ) -> None: - super().__init__(name, fullname, id, upper_bound, line, column) + super().__init__(name, fullname, id, upper_bound, default, line, column) assert values is not None, "No restrictions must be represented by empty list" self.values = values self.variance = variance - @staticmethod - def new_unification_variable(old: TypeVarType) -> TypeVarType: - new_id = TypeVarId.new(meta_level=1) - return old.copy_modified(id=new_id) - def copy_modified( self, + *, values: Bogus[list[Type]] = _dummy, upper_bound: Bogus[Type] = _dummy, + default: Bogus[Type] = _dummy, id: Bogus[TypeVarId | int] = _dummy, line: int = _dummy_int, column: int = _dummy_int, + **kwargs: Any, ) -> TypeVarType: return TypeVarType( - self.name, - self.fullname, - self.id if id is _dummy else id, - self.values if values is _dummy else values, - self.upper_bound if upper_bound is _dummy else upper_bound, - self.variance, - self.line if line == _dummy_int else line, - self.column if column == _dummy_int else column, + name=self.name, + fullname=self.fullname, + id=self.id if id is _dummy else id, + values=self.values if values is _dummy else values, + upper_bound=self.upper_bound if upper_bound is _dummy else upper_bound, + default=self.default if default is _dummy else default, + variance=self.variance, + line=self.line if line == _dummy_int else line, + column=self.column if column == _dummy_int else column, ) def accept(self, visitor: TypeVisitor[T]) -> T: @@ -611,6 +668,7 @@ def serialize(self) -> JsonDict: "namespace": self.id.namespace, "values": [v.serialize() for v in self.values], "upper_bound": self.upper_bound.serialize(), + "default": self.default.serialize(), "variance": self.variance, } @@ -618,12 +676,13 @@ def serialize(self) -> JsonDict: def deserialize(cls, data: JsonDict) -> TypeVarType: assert data[".class"] == "TypeVarType" return TypeVarType( - data["name"], - data["fullname"], - TypeVarId(data["id"], namespace=data["namespace"]), - [deserialize_type(v) for v in data["values"]], - deserialize_type(data["upper_bound"]), - data["variance"], + name=data["name"], + fullname=data["fullname"], + id=TypeVarId(data["id"], namespace=data["namespace"]), + values=[deserialize_type(v) for v in data["values"]], + upper_bound=deserialize_type(data["upper_bound"]), + default=deserialize_type(data["default"]), + variance=data["variance"], ) @@ -666,20 +725,16 @@ def __init__( id: TypeVarId | int, flavor: int, upper_bound: Type, + default: Type, *, line: int = -1, column: int = -1, prefix: Parameters | None = None, ) -> None: - super().__init__(name, fullname, id, upper_bound, line=line, column=column) + super().__init__(name, fullname, id, upper_bound, default, line=line, column=column) self.flavor = flavor self.prefix = prefix or Parameters([], [], []) - @staticmethod - def new_unification_variable(old: ParamSpecType) -> ParamSpecType: - new_id = TypeVarId.new(meta_level=1) - return old.copy_modified(id=new_id) - def with_flavor(self, flavor: int) -> ParamSpecType: return ParamSpecType( self.name, @@ -687,6 +742,7 @@ def with_flavor(self, flavor: int) -> ParamSpecType: self.id, flavor, upper_bound=self.upper_bound, + default=self.default, prefix=self.prefix, ) @@ -696,6 +752,8 @@ def copy_modified( id: Bogus[TypeVarId | int] = _dummy, flavor: int = _dummy_int, prefix: Bogus[Parameters] = _dummy, + default: Bogus[Type] = _dummy, + **kwargs: Any, ) -> ParamSpecType: return ParamSpecType( self.name, @@ -703,6 +761,7 @@ def copy_modified( id if id is not _dummy else self.id, flavor if flavor != _dummy_int else self.flavor, self.upper_bound, + default=default if default is not _dummy else self.default, line=self.line, column=self.column, prefix=prefix if prefix is not _dummy else self.prefix, @@ -737,6 +796,7 @@ def serialize(self) -> JsonDict: "id": self.id.raw_id, "flavor": self.flavor, "upper_bound": self.upper_bound.serialize(), + "default": self.default.serialize(), "prefix": self.prefix.serialize(), } @@ -749,6 +809,7 @@ def deserialize(cls, data: JsonDict) -> ParamSpecType: data["id"], data["flavor"], deserialize_type(data["upper_bound"]), + deserialize_type(data["default"]), prefix=Parameters.deserialize(data["prefix"]), ) @@ -759,6 +820,8 @@ class TypeVarTupleType(TypeVarLikeType): See PEP646 for more information. """ + __slots__ = ("tuple_fallback", "min_len") + def __init__( self, name: str, @@ -766,12 +829,17 @@ def __init__( id: TypeVarId | int, upper_bound: Type, tuple_fallback: Instance, + default: Type, *, line: int = -1, column: int = -1, + min_len: int = 0, ) -> None: - super().__init__(name, fullname, id, upper_bound, line=line, column=column) + super().__init__(name, fullname, id, upper_bound, default, line=line, column=column) self.tuple_fallback = tuple_fallback + # This value is not settable by a user. It is an internal-only thing to support + # len()-narrowing of variadic tuples. + self.min_len = min_len def serialize(self) -> JsonDict: assert not self.id.is_meta_var() @@ -782,6 +850,8 @@ def serialize(self) -> JsonDict: "id": self.id.raw_id, "upper_bound": self.upper_bound.serialize(), "tuple_fallback": self.tuple_fallback.serialize(), + "default": self.default.serialize(), + "min_len": self.min_len, } @classmethod @@ -793,47 +863,47 @@ def deserialize(cls, data: JsonDict) -> TypeVarTupleType: data["id"], deserialize_type(data["upper_bound"]), Instance.deserialize(data["tuple_fallback"]), + deserialize_type(data["default"]), + min_len=data["min_len"], ) def accept(self, visitor: TypeVisitor[T]) -> T: return visitor.visit_type_var_tuple(self) def __hash__(self) -> int: - return hash(self.id) + return hash((self.id, self.min_len)) def __eq__(self, other: object) -> bool: if not isinstance(other, TypeVarTupleType): return NotImplemented - return self.id == other.id - - @staticmethod - def new_unification_variable(old: TypeVarTupleType) -> TypeVarTupleType: - new_id = TypeVarId.new(meta_level=1) - return old.copy_modified(id=new_id) + return self.id == other.id and self.min_len == other.min_len - def copy_modified(self, id: Bogus[TypeVarId | int] = _dummy) -> TypeVarTupleType: + def copy_modified( + self, + *, + id: Bogus[TypeVarId | int] = _dummy, + upper_bound: Bogus[Type] = _dummy, + default: Bogus[Type] = _dummy, + min_len: Bogus[int] = _dummy, + **kwargs: Any, + ) -> TypeVarTupleType: return TypeVarTupleType( self.name, self.fullname, self.id if id is _dummy else id, - self.upper_bound, + self.upper_bound if upper_bound is _dummy else upper_bound, self.tuple_fallback, + self.default if default is _dummy else default, line=self.line, column=self.column, + min_len=self.min_len if min_len is _dummy else min_len, ) class UnboundType(ProperType): """Instance type that has not been bound during semantic analysis.""" - __slots__ = ( - "name", - "args", - "optional", - "empty_tuple_index", - "original_str_expr", - "original_str_fallback", - ) + __slots__ = ("name", "args", "optional", "empty_tuple_index") def __init__( self, @@ -843,8 +913,6 @@ def __init__( column: int = -1, optional: bool = False, empty_tuple_index: bool = False, - original_str_expr: str | None = None, - original_str_fallback: str | None = None, ) -> None: super().__init__(line, column) if not args: @@ -856,21 +924,6 @@ def __init__( self.optional = optional # Special case for X[()] self.empty_tuple_index = empty_tuple_index - # If this UnboundType was originally defined as a str or bytes, keep track of - # the original contents of that string-like thing. This way, if this UnboundExpr - # ever shows up inside of a LiteralType, we can determine whether that - # Literal[...] is valid or not. E.g. Literal[foo] is most likely invalid - # (unless 'foo' is an alias for another literal or something) and - # Literal["foo"] most likely is. - # - # We keep track of the entire string instead of just using a boolean flag - # so we can distinguish between things like Literal["foo"] vs - # Literal[" foo "]. - # - # We also keep track of what the original base fallback type was supposed to be - # so we don't have to try and recompute it later - self.original_str_expr = original_str_expr - self.original_str_fallback = original_str_fallback def copy_modified(self, args: Bogus[Sequence[Type] | None] = _dummy) -> UnboundType: if args is _dummy: @@ -882,25 +935,19 @@ def copy_modified(self, args: Bogus[Sequence[Type] | None] = _dummy) -> UnboundT column=self.column, optional=self.optional, empty_tuple_index=self.empty_tuple_index, - original_str_expr=self.original_str_expr, - original_str_fallback=self.original_str_fallback, ) def accept(self, visitor: TypeVisitor[T]) -> T: return visitor.visit_unbound_type(self) def __hash__(self) -> int: - return hash((self.name, self.optional, tuple(self.args), self.original_str_expr)) + return hash((self.name, self.optional, tuple(self.args))) def __eq__(self, other: object) -> bool: if not isinstance(other, UnboundType): return NotImplemented return ( - self.name == other.name - and self.optional == other.optional - and self.args == other.args - and self.original_str_expr == other.original_str_expr - and self.original_str_fallback == other.original_str_fallback + self.name == other.name and self.optional == other.optional and self.args == other.args ) def serialize(self) -> JsonDict: @@ -908,19 +955,12 @@ def serialize(self) -> JsonDict: ".class": "UnboundType", "name": self.name, "args": [a.serialize() for a in self.args], - "expr": self.original_str_expr, - "expr_fallback": self.original_str_fallback, } @classmethod def deserialize(cls, data: JsonDict) -> UnboundType: assert data[".class"] == "UnboundType" - return UnboundType( - data["name"], - [deserialize_type(a) for a in data["args"]], - original_str_expr=data["expr"], - original_str_fallback=data["expr_fallback"], - ) + return UnboundType(data["name"], [deserialize_type(a) for a in data["args"]]) class CallableArgument(ProperType): @@ -950,7 +990,8 @@ def __init__( def accept(self, visitor: TypeVisitor[T]) -> T: assert isinstance(visitor, SyntheticTypeVisitor) - return cast(T, visitor.visit_callable_argument(self)) + ret: T = visitor.visit_callable_argument(self) + return ret def serialize(self) -> JsonDict: assert False, "Synthetic types don't serialize" @@ -975,7 +1016,8 @@ def __init__(self, items: list[Type], line: int = -1, column: int = -1) -> None: def accept(self, visitor: TypeVisitor[T]) -> T: assert isinstance(visitor, SyntheticTypeVisitor) - return cast(T, visitor.visit_type_list(self)) + ret: T = visitor.visit_type_list(self) + return ret def serialize(self) -> JsonDict: assert False, "Synthetic types don't serialize" @@ -991,15 +1033,22 @@ class UnpackType(ProperType): """Type operator Unpack from PEP646. Can be either with Unpack[] or unpacking * syntax. - The inner type should be either a TypeVarTuple, a constant size - tuple, or a variable length tuple, or a union of one of those. + The inner type should be either a TypeVarTuple, or a variable length tuple. + In an exceptional case of callable star argument it can be a fixed length tuple. + + Note: the above restrictions are only guaranteed by normalizations after semantic + analysis, if your code needs to handle UnpackType *during* semantic analysis, it is + wild west, technically anything can be present in the wrapped type. """ - __slots__ = ["type"] + __slots__ = ["type", "from_star_syntax"] - def __init__(self, typ: Type, line: int = -1, column: int = -1) -> None: + def __init__( + self, typ: Type, line: int = -1, column: int = -1, from_star_syntax: bool = False + ) -> None: super().__init__(line, column) self.type = typ + self.from_star_syntax = from_star_syntax def accept(self, visitor: TypeVisitor[T]) -> T: return visitor.visit_unpack_type(self) @@ -1013,6 +1062,12 @@ def deserialize(cls, data: JsonDict) -> UnpackType: typ = data["type"] return UnpackType(deserialize_type(typ)) + def __hash__(self) -> int: + return hash(self.type) + + def __eq__(self, other: object) -> bool: + return isinstance(other, UnpackType) and self.type == other.type + class AnyType(ProperType): """The type 'Any'.""" @@ -1410,9 +1465,9 @@ def copy_modified( args if args is not _dummy else self.args, self.line, self.column, - last_known_value=last_known_value - if last_known_value is not _dummy - else self.last_known_value, + last_known_value=( + last_known_value if last_known_value is not _dummy else self.last_known_value + ), ) # We intentionally don't copy the extra_attrs here, so they will be erased. new.can_be_true = self.can_be_true @@ -1429,16 +1484,13 @@ def copy_with_extra_attr(self, name: str, typ: Type) -> Instance: new.extra_attrs = existing_attrs return new - def has_readable_member(self, name: str) -> bool: - return self.type.has_readable_member(name) - def is_singleton_type(self) -> bool: # TODO: # Also make this return True if the type corresponds to NotImplemented? return ( self.type.is_enum and len(self.get_enum_values()) == 1 - or self.type.fullname == "builtins.ellipsis" + or self.type.fullname in {"builtins.ellipsis", "types.EllipsisType"} ) def get_enum_values(self) -> list[str]: @@ -1488,13 +1540,13 @@ class FormalArgument(NamedTuple): required: bool -# TODO: should this take bound typevars too? what would this take? -# ex: class Z(Generic[P, T]): ...; Z[[V], V] -# What does a typevar even mean in this context? class Parameters(ProperType): """Type that represents the parameters to a function. - Used for ParamSpec analysis.""" + Used for ParamSpec analysis. Note that by convention we handle this + type as a Callable without return type, not as a "tuple with names", + so that it behaves contravariantly, in particular [x: int] <: [int]. + """ __slots__ = ( "arg_types", @@ -1502,7 +1554,10 @@ class Parameters(ProperType): "arg_names", "min_args", "is_ellipsis_args", + # TODO: variables don't really belong here, but they are used to allow hacky support + # for forall . Foo[[x: T], T] by capturing generic callable with ParamSpec, see #15909 "variables", + "imprecise_arg_kinds", ) def __init__( @@ -1513,6 +1568,7 @@ def __init__( *, variables: Sequence[TypeVarLikeType] | None = None, is_ellipsis_args: bool = False, + imprecise_arg_kinds: bool = False, line: int = -1, column: int = -1, ) -> None: @@ -1521,9 +1577,11 @@ def __init__( self.arg_kinds = arg_kinds self.arg_names = list(arg_names) assert len(arg_types) == len(arg_kinds) == len(arg_names) + assert not any(isinstance(t, Parameters) for t in arg_types) self.min_args = arg_kinds.count(ARG_POS) self.is_ellipsis_args = is_ellipsis_args self.variables = variables or [] + self.imprecise_arg_kinds = imprecise_arg_kinds def copy_modified( self, @@ -1533,6 +1591,7 @@ def copy_modified( *, variables: Bogus[Sequence[TypeVarLikeType]] = _dummy, is_ellipsis_args: Bogus[bool] = _dummy, + imprecise_arg_kinds: Bogus[bool] = _dummy, ) -> Parameters: return Parameters( arg_types=arg_types if arg_types is not _dummy else self.arg_types, @@ -1542,9 +1601,14 @@ def copy_modified( is_ellipsis_args if is_ellipsis_args is not _dummy else self.is_ellipsis_args ), variables=variables if variables is not _dummy else self.variables, + imprecise_arg_kinds=( + imprecise_arg_kinds + if imprecise_arg_kinds is not _dummy + else self.imprecise_arg_kinds + ), ) - # the following are copied from CallableType. Is there a way to decrease code duplication? + # TODO: here is a lot of code duplication with Callable type, fix this. def var_arg(self) -> FormalArgument | None: """The formal argument for *args.""" for position, (type, kind) in enumerate(zip(self.arg_types, self.arg_kinds)): @@ -1638,6 +1702,7 @@ def serialize(self) -> JsonDict: "arg_kinds": [int(x.value) for x in self.arg_kinds], "arg_names": self.arg_names, "variables": [tv.serialize() for tv in self.variables], + "imprecise_arg_kinds": self.imprecise_arg_kinds, } @classmethod @@ -1648,6 +1713,7 @@ def deserialize(cls, data: JsonDict) -> Parameters: [ArgKind(x) for x in data["arg_kinds"]], data["arg_names"], variables=[cast(TypeVarLikeType, deserialize_type(v)) for v in data["variables"]], + imprecise_arg_kinds=data["imprecise_arg_kinds"], ) def __hash__(self) -> int: @@ -1661,7 +1727,7 @@ def __hash__(self) -> int: ) def __eq__(self, other: object) -> bool: - if isinstance(other, Parameters) or isinstance(other, CallableType): + if isinstance(other, (Parameters, CallableType)): return ( self.arg_types == other.arg_types and self.arg_names == other.arg_names @@ -1688,8 +1754,6 @@ class CallableType(FunctionLike): "definition", # For error messages. May be None. "variables", # Type variables for a generic function "is_ellipsis_args", # Is this Callable[..., t] (with literal '...')? - "is_classmethod_class", # Is this callable constructed for the benefit - # of a classmethod's 'cls' argument? "implicit", # Was this type implicitly generated instead of explicitly # specified by the user? "special_sig", # Non-None for signatures that require special handling @@ -1702,8 +1766,10 @@ class CallableType(FunctionLike): "def_extras", # Information about original definition we want to serialize. # This is used for more detailed error messages. "type_guard", # T, if -> TypeGuard[T] (ret_type is bool in this case). + "type_is", # T, if -> TypeIs[T] (ret_type is bool in this case). "from_concatenate", # whether this callable is from a concatenate object # (this is used for error messages) + "imprecise_arg_kinds", "unpack_kwargs", # Was an Unpack[...] with **kwargs used to define this callable? ) @@ -1727,11 +1793,18 @@ def __init__( bound_args: Sequence[Type | None] = (), def_extras: dict[str, Any] | None = None, type_guard: Type | None = None, + type_is: Type | None = None, from_concatenate: bool = False, + imprecise_arg_kinds: bool = False, unpack_kwargs: bool = False, ) -> None: super().__init__(line, column) assert len(arg_types) == len(arg_kinds) == len(arg_names) + for t, k in zip(arg_types, arg_kinds): + if isinstance(t, ParamSpecType): + assert not t.prefix.arg_types + # TODO: should we assert that only ARG_STAR contain ParamSpecType? + # See testParamSpecJoin, that relies on passing e.g `P.args` as plain argument. if variables is None: variables = [] self.arg_types = list(arg_types) @@ -1749,6 +1822,7 @@ def __init__( self.special_sig = special_sig self.from_type_type = from_type_type self.from_concatenate = from_concatenate + self.imprecise_arg_kinds = imprecise_arg_kinds if not bound_args: bound_args = () self.bound_args = bound_args @@ -1769,13 +1843,14 @@ def __init__( else: self.def_extras = {} self.type_guard = type_guard + self.type_is = type_is self.unpack_kwargs = unpack_kwargs def copy_modified( self: CT, arg_types: Bogus[Sequence[Type]] = _dummy, arg_kinds: Bogus[list[ArgKind]] = _dummy, - arg_names: Bogus[list[str | None]] = _dummy, + arg_names: Bogus[Sequence[str | None]] = _dummy, ret_type: Bogus[Type] = _dummy, fallback: Bogus[Instance] = _dummy, name: Bogus[str | None] = _dummy, @@ -1790,7 +1865,9 @@ def copy_modified( bound_args: Bogus[list[Type | None]] = _dummy, def_extras: Bogus[dict[str, Any]] = _dummy, type_guard: Bogus[Type | None] = _dummy, + type_is: Bogus[Type | None] = _dummy, from_concatenate: Bogus[bool] = _dummy, + imprecise_arg_kinds: Bogus[bool] = _dummy, unpack_kwargs: Bogus[bool] = _dummy, ) -> CT: modified = CallableType( @@ -1813,9 +1890,15 @@ def copy_modified( bound_args=bound_args if bound_args is not _dummy else self.bound_args, def_extras=def_extras if def_extras is not _dummy else dict(self.def_extras), type_guard=type_guard if type_guard is not _dummy else self.type_guard, + type_is=type_is if type_is is not _dummy else self.type_is, from_concatenate=( from_concatenate if from_concatenate is not _dummy else self.from_concatenate ), + imprecise_arg_kinds=( + imprecise_arg_kinds + if imprecise_arg_kinds is not _dummy + else self.imprecise_arg_kinds + ), unpack_kwargs=unpack_kwargs if unpack_kwargs is not _dummy else self.unpack_kwargs, ) # Optimization: Only NewTypes are supported as subtypes since @@ -1976,42 +2059,11 @@ def param_spec(self) -> ParamSpecType | None: arg_type = self.arg_types[-2] if not isinstance(arg_type, ParamSpecType): return None - # sometimes paramspectypes are analyzed in from mysterious places, - # e.g. def f(prefix..., *args: P.args, **kwargs: P.kwargs) -> ...: ... - prefix = arg_type.prefix - if not prefix.arg_types: - # TODO: confirm that all arg kinds are positional - prefix = Parameters(self.arg_types[:-2], self.arg_kinds[:-2], self.arg_names[:-2]) - return ParamSpecType( - arg_type.name, - arg_type.fullname, - arg_type.id, - ParamSpecFlavor.BARE, - arg_type.upper_bound, - prefix=prefix, - ) - def expand_param_spec( - self, c: CallableType | Parameters, no_prefix: bool = False - ) -> CallableType: - variables = c.variables - - if no_prefix: - return self.copy_modified( - arg_types=c.arg_types, - arg_kinds=c.arg_kinds, - arg_names=c.arg_names, - is_ellipsis_args=c.is_ellipsis_args, - variables=[*variables, *self.variables], - ) - else: - return self.copy_modified( - arg_types=self.arg_types[:-2] + c.arg_types, - arg_kinds=self.arg_kinds[:-2] + c.arg_kinds, - arg_names=self.arg_names[:-2] + c.arg_names, - is_ellipsis_args=c.is_ellipsis_args, - variables=[*variables, *self.variables], - ) + # Prepend prefix for def f(prefix..., *args: P.args, **kwargs: P.kwargs) -> ... + # TODO: confirm that all arg kinds are positional + prefix = Parameters(self.arg_types[:-2], self.arg_kinds[:-2], self.arg_names[:-2]) + return arg_type.copy_modified(flavor=ParamSpecFlavor.BARE, prefix=prefix) def with_unpacked_kwargs(self) -> NormalizedCallableType: if not self.unpack_kwargs: @@ -2034,6 +2086,72 @@ def with_unpacked_kwargs(self) -> NormalizedCallableType: ) ) + def with_normalized_var_args(self) -> Self: + var_arg = self.var_arg() + if not var_arg or not isinstance(var_arg.typ, UnpackType): + return self + unpacked = get_proper_type(var_arg.typ.type) + if not isinstance(unpacked, TupleType): + # Note that we don't normalize *args: *tuple[X, ...] -> *args: X, + # this should be done once in semanal_typeargs.py for user-defined types, + # and we ourselves should never construct such type. + return self + unpack_index = find_unpack_in_list(unpacked.items) + if unpack_index == 0 and len(unpacked.items) > 1: + # Already normalized. + return self + + # Boilerplate: + var_arg_index = self.arg_kinds.index(ARG_STAR) + types_prefix = self.arg_types[:var_arg_index] + kinds_prefix = self.arg_kinds[:var_arg_index] + names_prefix = self.arg_names[:var_arg_index] + types_suffix = self.arg_types[var_arg_index + 1 :] + kinds_suffix = self.arg_kinds[var_arg_index + 1 :] + names_suffix = self.arg_names[var_arg_index + 1 :] + no_name: str | None = None # to silence mypy + + # Now we have something non-trivial to do. + if unpack_index is None: + # Plain *Tuple[X, Y, Z] -> replace with ARG_POS completely + types_middle = unpacked.items + kinds_middle = [ARG_POS] * len(unpacked.items) + names_middle = [no_name] * len(unpacked.items) + else: + # *Tuple[X, *Ts, Y, Z] or *Tuple[X, *tuple[T, ...], X, Z], here + # we replace the prefix by ARG_POS (this is how some places expect + # Callables to be represented) + nested_unpack = unpacked.items[unpack_index] + assert isinstance(nested_unpack, UnpackType) + nested_unpacked = get_proper_type(nested_unpack.type) + if unpack_index == len(unpacked.items) - 1: + # Normalize also single item tuples like + # *args: *Tuple[*tuple[X, ...]] -> *args: X + # *args: *Tuple[*Ts] -> *args: *Ts + # This may be not strictly necessary, but these are very verbose. + if isinstance(nested_unpacked, Instance): + assert nested_unpacked.type.fullname == "builtins.tuple" + new_unpack = nested_unpacked.args[0] + else: + if not isinstance(nested_unpacked, TypeVarTupleType): + # We found a non-nomralized tuple type, this means this method + # is called during semantic analysis (e.g. from get_proper_type()) + # there is no point in normalizing callables at this stage. + return self + new_unpack = nested_unpack + else: + new_unpack = UnpackType( + unpacked.copy_modified(items=unpacked.items[unpack_index:]) + ) + types_middle = unpacked.items[:unpack_index] + [new_unpack] + kinds_middle = [ARG_POS] * unpack_index + [ARG_STAR] + names_middle = [no_name] * unpack_index + [self.arg_names[var_arg_index]] + return self.copy_modified( + arg_types=types_prefix + types_middle + types_suffix, + arg_kinds=kinds_prefix + kinds_middle + kinds_suffix, + arg_names=names_prefix + names_middle + names_suffix, + ) + def __hash__(self) -> int: # self.is_type_obj() will fail if self.fallback.type is a FakeInfo if isinstance(self.fallback.type, FakeInfo): @@ -2086,7 +2204,9 @@ def serialize(self) -> JsonDict: "bound_args": [(None if t is None else t.serialize()) for t in self.bound_args], "def_extras": dict(self.def_extras), "type_guard": self.type_guard.serialize() if self.type_guard is not None else None, + "type_is": (self.type_is.serialize() if self.type_is is not None else None), "from_concatenate": self.from_concatenate, + "imprecise_arg_kinds": self.imprecise_arg_kinds, "unpack_kwargs": self.unpack_kwargs, } @@ -2109,7 +2229,9 @@ def deserialize(cls, data: JsonDict) -> CallableType: type_guard=( deserialize_type(data["type_guard"]) if data["type_guard"] is not None else None ), + type_is=(deserialize_type(data["type_is"]) if data["type_is"] is not None else None), from_concatenate=data["from_concatenate"], + imprecise_arg_kinds=data["imprecise_arg_kinds"], unpack_kwargs=data["unpack_kwargs"], ) @@ -2233,7 +2355,18 @@ def can_be_false_default(self) -> bool: # Corner case: it is a `NamedTuple` with `__bool__` method defined. # It can be anything: both `True` and `False`. return True - return self.length() == 0 + if self.length() == 0: + return True + if self.length() > 1: + return False + # Special case tuple[*Ts] may or may not be false. + item = self.items[0] + if not isinstance(item, UnpackType): + return False + if not isinstance(item.type, TypeVarTupleType): + # Non-normalized tuple[int, ...] can be false. + return True + return item.type.min_len == 0 def can_be_any_bool(self) -> bool: return bool( @@ -2282,14 +2415,58 @@ def copy_modified( items = self.items return TupleType(items, fallback, self.line, self.column) - def slice(self, begin: int | None, end: int | None, stride: int | None) -> TupleType: - return TupleType( - self.items[begin:end:stride], - self.partial_fallback, - self.line, - self.column, - self.implicit, - ) + def slice( + self, begin: int | None, end: int | None, stride: int | None, *, fallback: Instance | None + ) -> TupleType | None: + if fallback is None: + fallback = self.partial_fallback + + if any(isinstance(t, UnpackType) for t in self.items): + total = len(self.items) + unpack_index = find_unpack_in_list(self.items) + assert unpack_index is not None + if begin is None and end is None: + # We special-case this to support reversing variadic tuples. + # General support for slicing is tricky, so we handle only simple cases. + if stride == -1: + slice_items = self.items[::-1] + elif stride is None or stride == 1: + slice_items = self.items + else: + return None + elif (begin is None or unpack_index >= begin >= 0) and ( + end is not None and unpack_index >= end >= 0 + ): + # Start and end are in the prefix, everything works in this case. + slice_items = self.items[begin:end:stride] + elif (begin is not None and unpack_index - total < begin < 0) and ( + end is None or unpack_index - total < end < 0 + ): + # Start and end are in the suffix, everything works in this case. + slice_items = self.items[begin:end:stride] + elif (begin is None or unpack_index >= begin >= 0) and ( + end is None or unpack_index - total < end < 0 + ): + # Start in the prefix, end in the suffix, we can support only trivial strides. + if stride is None or stride == 1: + slice_items = self.items[begin:end:stride] + else: + return None + elif (begin is not None and unpack_index - total < begin < 0) and ( + end is not None and unpack_index >= end >= 0 + ): + # Start in the suffix, end in the prefix, we can support only trivial strides. + if stride is None or stride == -1: + slice_items = self.items[begin:end:stride] + else: + return None + else: + # TODO: there some additional cases we can support for homogeneous variadic + # items, we can "eat away" finite number of items. + return None + else: + slice_items = self.items[begin:end:stride] + return TupleType(slice_items, fallback, self.line, self.column, self.implicit) class TypedDictType(ProperType): @@ -2388,6 +2565,7 @@ def copy_modified( *, fallback: Instance | None = None, item_types: list[Type] | None = None, + item_names: list[str] | None = None, required_keys: set[str] | None = None, ) -> TypedDictType: if fallback is None: @@ -2398,6 +2576,9 @@ def copy_modified( items = dict(zip(self.items, item_types)) if required_keys is None: required_keys = self.required_keys + if item_names is not None: + items = {k: v for (k, v) in items.items() if k in item_names} + required_keys &= set(item_names) return TypedDictType(items, required_keys, fallback, self.line, self.column) def create_anonymous_fallback(self) -> Instance: @@ -2409,17 +2590,17 @@ def names_are_wider_than(self, other: TypedDictType) -> bool: def zip(self, right: TypedDictType) -> Iterable[tuple[str, Type, Type]]: left = self - for (item_name, left_item_type) in left.items.items(): + for item_name, left_item_type in left.items.items(): right_item_type = right.items.get(item_name) if right_item_type is not None: yield (item_name, left_item_type, right_item_type) def zipall(self, right: TypedDictType) -> Iterable[tuple[str, Type | None, Type | None]]: left = self - for (item_name, left_item_type) in left.items.items(): + for item_name, left_item_type in left.items.items(): right_item_type = right.items.get(item_name) yield (item_name, left_item_type, right_item_type) - for (item_name, right_item_type) in right.items.items(): + for item_name, right_item_type in right.items.items(): if item_name in left.items: continue yield (item_name, None, right_item_type) @@ -2431,7 +2612,7 @@ class RawExpressionType(ProperType): This synthetic type is only used at the beginning stages of semantic analysis and should be completely removing during the process for mapping UnboundTypes to - actual types: we either turn it into a LiteralType or an AnyType. + actual types: we turn it into its "node" argument, a LiteralType, or an AnyType. For example, suppose `Foo[1]` is initially represented as the following: @@ -2469,7 +2650,7 @@ class RawExpressionType(ProperType): ) """ - __slots__ = ("literal_value", "base_type_name", "note") + __slots__ = ("literal_value", "base_type_name", "note", "node") def __init__( self, @@ -2478,18 +2659,36 @@ def __init__( line: int = -1, column: int = -1, note: str | None = None, + node: Type | None = None, ) -> None: super().__init__(line, column) self.literal_value = literal_value self.base_type_name = base_type_name self.note = note + self.node = node def simple_name(self) -> str: return self.base_type_name.replace("builtins.", "") def accept(self, visitor: TypeVisitor[T]) -> T: assert isinstance(visitor, SyntheticTypeVisitor) - return cast(T, visitor.visit_raw_expression_type(self)) + ret: T = visitor.visit_raw_expression_type(self) + return ret + + def copy_modified(self, node: Type | None) -> RawExpressionType: + return RawExpressionType( + literal_value=self.literal_value, + base_type_name=self.base_type_name, + line=self.line, + column=self.column, + note=self.note, + node=node, + ) + + def resolve_string_annotation(self) -> Type: + if self.node is not None: + return self.node.resolve_string_annotation() + return self def serialize(self) -> JsonDict: assert False, "Synthetic types don't serialize" @@ -2502,6 +2701,7 @@ def __eq__(self, other: object) -> bool: return ( self.base_type_name == other.base_type_name and self.literal_value == other.literal_value + and self.node == other.node ) else: return NotImplemented @@ -2634,13 +2834,13 @@ def __eq__(self, other: object) -> bool: @overload @staticmethod - def make_union(items: Sequence[ProperType], line: int = -1, column: int = -1) -> ProperType: - ... + def make_union( + items: Sequence[ProperType], line: int = -1, column: int = -1 + ) -> ProperType: ... @overload @staticmethod - def make_union(items: Sequence[Type], line: int = -1, column: int = -1) -> Type: - ... + def make_union(items: Sequence[Type], line: int = -1, column: int = -1) -> Type: ... @staticmethod def make_union(items: Sequence[Type], line: int = -1, column: int = -1) -> Type: @@ -2657,18 +2857,6 @@ def length(self) -> int: def accept(self, visitor: TypeVisitor[T]) -> T: return visitor.visit_union_type(self) - def has_readable_member(self, name: str) -> bool: - """For a tree of unions of instances, check whether all instances have a given member. - - TODO: Deal with attributes of TupleType etc. - TODO: This should probably be refactored to go elsewhere. - """ - return all( - (isinstance(x, UnionType) and x.has_readable_member(name)) - or (isinstance(x, Instance) and x.type.has_readable_member(name)) - for x in get_proper_types(self.relevant_items()) - ) - def relevant_items(self) -> list[Type]: """Removes NoneTypes from Unions when strict Optional checking is off.""" if state.strict_optional: @@ -2736,7 +2924,8 @@ class EllipsisType(ProperType): def accept(self, visitor: TypeVisitor[T]) -> T: assert isinstance(visitor, SyntheticTypeVisitor) - return cast(T, visitor.visit_ellipsis_type(self)) + ret: T = visitor.visit_ellipsis_type(self) + return ret def serialize(self) -> JsonDict: assert False, "Synthetic types don't serialize" @@ -2845,7 +3034,8 @@ def __init__(self, fullname: str | None, args: list[Type], line: int) -> None: def accept(self, visitor: TypeVisitor[T]) -> T: assert isinstance(visitor, SyntheticTypeVisitor) - return cast(T, visitor.visit_placeholder_type(self)) + ret: T = visitor.visit_placeholder_type(self) + return ret def __hash__(self) -> int: return hash((self.fullname, tuple(self.args))) @@ -2862,13 +3052,11 @@ def serialize(self) -> str: @overload -def get_proper_type(typ: None) -> None: - ... +def get_proper_type(typ: None) -> None: ... @overload -def get_proper_type(typ: Type) -> ProperType: - ... +def get_proper_type(typ: Type) -> ProperType: ... def get_proper_type(typ: Type | None) -> ProperType | None: @@ -2891,15 +3079,14 @@ def get_proper_type(typ: Type | None) -> ProperType | None: @overload -def get_proper_types(types: list[Type] | tuple[Type, ...]) -> list[ProperType]: # type: ignore[misc] +def get_proper_types(types: list[Type] | tuple[Type, ...]) -> list[ProperType]: # type: ignore[overload-overlap] ... @overload def get_proper_types( types: list[Type | None] | tuple[Type | None, ...] -) -> list[ProperType | None]: - ... +) -> list[ProperType | None]: ... def get_proper_types( @@ -2921,7 +3108,7 @@ def get_proper_types( # to make it easier to gradually get modules working with mypyc. # Import them here, after the types are defined. # This is intended as a re-export also. -from mypy.type_visitor import ( # noqa: F811,F401 +from mypy.type_visitor import ( ALL_STRATEGY as ALL_STRATEGY, ANY_STRATEGY as ANY_STRATEGY, BoolTypeQuery as BoolTypeQuery, @@ -2945,9 +3132,10 @@ class TypeStrVisitor(SyntheticTypeVisitor[str]): - Represent the NoneType type as None. """ - def __init__(self, id_mapper: IdMapper | None = None) -> None: + def __init__(self, id_mapper: IdMapper | None = None, *, options: Options) -> None: self.id_mapper = id_mapper self.any_as_dots = False + self.options = options def visit_unbound_type(self, t: UnboundType) -> str: s = t.name + "?" @@ -2974,7 +3162,7 @@ def visit_none_type(self, t: NoneType) -> str: return "None" def visit_uninhabited_type(self, t: UninhabitedType) -> str: - return "" + return "Never" def visit_erased_type(self, t: ErasedType) -> str: return "" @@ -2989,7 +3177,7 @@ def visit_instance(self, t: Instance) -> str: if t.last_known_value and not t.args: # Instances with a literal fallback should never be generic. If they are, # something went wrong so we fall back to showing the full Instance repr. - s = f"{t.last_known_value}?" + s = f"{t.last_known_value.accept(self)}?" else: s = t.type.fullname or t.type.name or "" @@ -2999,6 +3187,8 @@ def visit_instance(self, t: Instance) -> str: s += f"[{self.list_str(t.args)}, ...]" else: s += f"[{self.list_str(t.args)}]" + elif t.type.has_type_var_tuple_type and len(t.type.type_vars) == 1: + s += "[()]" if self.id_mapper: s += f"<{self.id_mapper.id(t.type)}>" return s @@ -3012,6 +3202,8 @@ def visit_type_var(self, t: TypeVarType) -> str: s = f"{t.name}`{t.id}" if self.id_mapper and t.upper_bound: s += f"(upper_bound={t.upper_bound.accept(self)})" + if t.has_default(): + s += f" = {t.default.accept(self)}" return s def visit_param_spec(self, t: ParamSpecType) -> str: @@ -3027,6 +3219,8 @@ def visit_param_spec(self, t: ParamSpecType) -> str: s += f"{t.name_with_suffix()}`{t.id}" if t.prefix.arg_types: s += "]" + if t.has_default(): + s += f" = {t.default.accept(self)}" return s def visit_parameters(self, t: Parameters) -> str: @@ -3065,6 +3259,8 @@ def visit_type_var_tuple(self, t: TypeVarTupleType) -> str: else: # Named type variable type. s = f"{t.name}`{t.id}" + if t.has_default(): + s += f" = {t.default.accept(self)}" return s def visit_callable_type(self, t: CallableType) -> str: @@ -3075,15 +3271,16 @@ def visit_callable_type(self, t: CallableType) -> str: num_skip = 0 s = "" - bare_asterisk = False + asterisk = False for i in range(len(t.arg_types) - num_skip): if s != "": s += ", " - if t.arg_kinds[i].is_named() and not bare_asterisk: + if t.arg_kinds[i].is_named() and not asterisk: s += "*, " - bare_asterisk = True + asterisk = True if t.arg_kinds[i] == ARG_STAR: s += "*" + asterisk = True if t.arg_kinds[i] == ARG_STAR2: s += "**" name = t.arg_names[i] @@ -3101,12 +3298,16 @@ def visit_callable_type(self, t: CallableType) -> str: if s: s += ", " s += f"*{n}.args, **{n}.kwargs" + if param_spec.has_default(): + s += f" = {param_spec.default.accept(self)}" s = f"({s})" if not isinstance(get_proper_type(t.ret_type), NoneType): if t.type_guard is not None: s += f" -> TypeGuard[{t.type_guard.accept(self)}]" + elif t.type_is is not None: + s += f" -> TypeIs[{t.type_is.accept(self)}]" else: s += f" -> {t.ret_type.accept(self)}" @@ -3119,12 +3320,18 @@ def visit_callable_type(self, t: CallableType) -> str: vals = f"({', '.join(val.accept(self) for val in var.values)})" vs.append(f"{var.name} in {vals}") elif not is_named_instance(var.upper_bound, "builtins.object"): - vs.append(f"{var.name} <: {var.upper_bound.accept(self)}") + vs.append( + f"{var.name} <: {var.upper_bound.accept(self)}{f' = {var.default.accept(self)}' if var.has_default() else ''}" + ) else: - vs.append(var.name) + vs.append( + f"{var.name}{f' = {var.default.accept(self)}' if var.has_default() else ''}" + ) else: - # For other TypeVarLikeTypes, just use the name - vs.append(var.name) + # For other TypeVarLikeTypes, use the name and default + vs.append( + f"{var.name}{f' = {var.default.accept(self)}' if var.has_default() else ''}" + ) s = f"[{', '.join(vs)}] {s}" return f"def {s}" @@ -3136,12 +3343,13 @@ def visit_overloaded(self, t: Overloaded) -> str: return f"Overload({', '.join(a)})" def visit_tuple_type(self, t: TupleType) -> str: - s = self.list_str(t.items) + s = self.list_str(t.items) or "()" + tuple_name = "tuple" if self.options.use_lowercase_names() else "Tuple" if t.partial_fallback and t.partial_fallback.type: fallback_name = t.partial_fallback.type.fullname if fallback_name != "builtins.tuple": - return f"Tuple[{s}, fallback={t.partial_fallback.accept(self)}]" - return f"Tuple[{s}]" + return f"{tuple_name}[{s}, fallback={t.partial_fallback.accept(self)}]" + return f"{tuple_name}[{s}]" def visit_typeddict_type(self, t: TypedDictType) -> str: def item_str(name: str, typ: str) -> str: @@ -3162,6 +3370,8 @@ def item_str(name: str, typ: str) -> str: return f"TypedDict({prefix}{s})" def visit_raw_expression_type(self, t: RawExpressionType) -> str: + if t.node is not None: + return t.node.accept(self) return repr(t.literal_value) def visit_literal_type(self, t: LiteralType) -> str: @@ -3181,7 +3391,11 @@ def visit_ellipsis_type(self, t: EllipsisType) -> str: return "..." def visit_type_type(self, t: TypeType) -> str: - return f"Type[{t.item.accept(self)}]" + if self.options.use_lowercase_names(): + type_name = "type" + else: + type_name = "Type" + return f"{type_name}[{t.item.accept(self)}]" def visit_placeholder_type(self, t: PlaceholderType) -> str: return f"" @@ -3221,6 +3435,9 @@ def visit_ellipsis_type(self, t: EllipsisType) -> Type: return t def visit_raw_expression_type(self, t: RawExpressionType) -> Type: + if t.node is not None: + node = t.node.accept(self) + return t.copy_modified(node=node) return t def visit_type_list(self, t: TypeList) -> Type: @@ -3247,18 +3464,6 @@ def visit_type_alias_type(self, t: TypeAliasType) -> Type: return result -def strip_type(typ: Type) -> Type: - """Make a copy of type without 'debugging info' (function name).""" - orig_typ = typ - typ = get_proper_type(typ) - if isinstance(typ, CallableType): - return typ.copy_modified(name=None) - elif isinstance(typ, Overloaded): - return Overloaded([cast(CallableType, strip_type(item)) for item in typ.items]) - else: - return orig_typ - - def is_named_instance(t: Type, fullnames: str | tuple[str, ...]) -> TypeGuard[Instance]: if not isinstance(fullnames, tuple): fullnames = (fullnames,) @@ -3267,49 +3472,6 @@ def is_named_instance(t: Type, fullnames: str | tuple[str, ...]) -> TypeGuard[In return isinstance(t, Instance) and t.type.fullname in fullnames -class InstantiateAliasVisitor(TrivialSyntheticTypeTranslator): - def __init__(self, vars: list[TypeVarLikeType], subs: list[Type]) -> None: - self.replacements = {v.id: s for (v, s) in zip(vars, subs)} - - def visit_type_alias_type(self, typ: TypeAliasType) -> Type: - return typ.copy_modified(args=[t.accept(self) for t in typ.args]) - - def visit_type_var(self, typ: TypeVarType) -> Type: - if typ.id in self.replacements: - return self.replacements[typ.id] - return typ - - def visit_callable_type(self, t: CallableType) -> Type: - param_spec = t.param_spec() - if param_spec is not None: - # TODO: this branch duplicates the one in expand_type(), find a way to reuse it - # without import cycle types <-> typeanal <-> expandtype. - repl = get_proper_type(self.replacements.get(param_spec.id)) - if isinstance(repl, CallableType) or isinstance(repl, Parameters): - prefix = param_spec.prefix - t = t.expand_param_spec(repl, no_prefix=True) - return t.copy_modified( - arg_types=[t.accept(self) for t in prefix.arg_types] + t.arg_types, - arg_kinds=prefix.arg_kinds + t.arg_kinds, - arg_names=prefix.arg_names + t.arg_names, - ret_type=t.ret_type.accept(self), - type_guard=(t.type_guard.accept(self) if t.type_guard is not None else None), - ) - return super().visit_callable_type(t) - - def visit_param_spec(self, typ: ParamSpecType) -> Type: - if typ.id in self.replacements: - repl = get_proper_type(self.replacements[typ.id]) - # TODO: all the TODOs from same logic in expand_type() apply here. - if isinstance(repl, Instance): - return repl - elif isinstance(repl, (ParamSpecType, Parameters, CallableType)): - return expand_param_spec(typ, repl) - else: - return repl - return typ - - class LocationSetter(TypeTraverserVisitor): # TODO: Should we update locations of other Type subclasses? def __init__(self, line: int, column: int) -> None: @@ -3322,20 +3484,6 @@ def visit_instance(self, typ: Instance) -> None: super().visit_instance(typ) -def replace_alias_tvars( - tp: Type, vars: list[TypeVarLikeType], subs: list[Type], newline: int, newcolumn: int -) -> Type: - """Replace type variables in a generic type alias tp with substitutions subs - resetting context. Length of subs should be already checked. - """ - replacer = InstantiateAliasVisitor(vars, subs) - new_tp = tp.accept(replacer) - new_tp.accept(LocationSetter(newline, newcolumn)) - new_tp.line = newline - new_tp.column = newcolumn - return new_tp - - class HasTypeVars(BoolTypeQuery): def __init__(self) -> None: super().__init__(ANY_STRATEGY) @@ -3374,13 +3522,43 @@ def has_recursive_types(typ: Type) -> bool: return typ.accept(_has_recursive_type) -def _flattened(types: Iterable[Type]) -> Iterable[Type]: - for t in types: - tp = get_proper_type(t) - if isinstance(tp, UnionType): - yield from _flattened(tp.items) - else: - yield t +def split_with_prefix_and_suffix( + types: tuple[Type, ...], prefix: int, suffix: int +) -> tuple[tuple[Type, ...], tuple[Type, ...], tuple[Type, ...]]: + if len(types) <= prefix + suffix: + types = extend_args_for_prefix_and_suffix(types, prefix, suffix) + if suffix: + return types[:prefix], types[prefix:-suffix], types[-suffix:] + else: + return types[:prefix], types[prefix:], () + + +def extend_args_for_prefix_and_suffix( + types: tuple[Type, ...], prefix: int, suffix: int +) -> tuple[Type, ...]: + """Extend list of types by eating out from variadic tuple to satisfy prefix and suffix.""" + idx = None + item = None + for i, t in enumerate(types): + if isinstance(t, UnpackType): + p_type = get_proper_type(t.type) + if isinstance(p_type, Instance) and p_type.type.fullname == "builtins.tuple": + item = p_type.args[0] + idx = i + break + + if idx is None: + return types + assert item is not None + if idx < prefix: + start = (item,) * (prefix - idx) + else: + start = () + if len(types) - idx - 1 < suffix: + end = (item,) * (suffix - len(types) + idx + 1) + else: + end = () + return types[:idx] + start + (types[idx],) + end + types[idx + 1 :] def flatten_nested_unions( @@ -3409,69 +3587,39 @@ def flatten_nested_unions( return flat_items -def invalid_recursive_alias(seen_nodes: set[mypy.nodes.TypeAlias], target: Type) -> bool: - """Flag aliases like A = Union[int, A] (and similar mutual aliases). - - Such aliases don't make much sense, and cause problems in later phases. - """ - if isinstance(target, TypeAliasType): - if target.alias in seen_nodes: - return True - assert target.alias, f"Unfixed type alias {target.type_ref}" - return invalid_recursive_alias(seen_nodes | {target.alias}, get_proper_type(target)) - assert isinstance(target, ProperType) - if not isinstance(target, UnionType): - return False - return any(invalid_recursive_alias(seen_nodes, item) for item in target.items) +def find_unpack_in_list(items: Sequence[Type]) -> int | None: + unpack_index: int | None = None + for i, item in enumerate(items): + if isinstance(item, UnpackType): + # We cannot fail here, so we must check this in an earlier + # semanal phase. + # Funky code here avoids mypyc narrowing the type of unpack_index. + old_index = unpack_index + assert old_index is None + # Don't return so that we can also sanity check there is only one. + unpack_index = i + return unpack_index -def bad_type_type_item(item: Type) -> bool: - """Prohibit types like Type[Type[...]]. +def flatten_nested_tuples(types: Sequence[Type]) -> list[Type]: + """Recursively flatten TupleTypes nested with Unpack. - Such types are explicitly prohibited by PEP 484. Also they cause problems - with recursive types like T = Type[T], because internal representation of - TypeType item is normalized (i.e. always a proper type). + For example this will transform + Tuple[A, Unpack[Tuple[B, Unpack[Tuple[C, D]]]]] + into + Tuple[A, B, C, D] """ - item = get_proper_type(item) - if isinstance(item, TypeType): - return True - if isinstance(item, UnionType): - return any( - isinstance(get_proper_type(i), TypeType) for i in flatten_nested_unions(item.items) - ) - return False - - -def is_union_with_any(tp: Type) -> bool: - """Is this a union with Any or a plain Any type?""" - tp = get_proper_type(tp) - if isinstance(tp, AnyType): - return True - if not isinstance(tp, UnionType): - return False - return any(is_union_with_any(t) for t in get_proper_types(tp.items)) - - -def is_generic_instance(tp: Type) -> bool: - tp = get_proper_type(tp) - return isinstance(tp, Instance) and bool(tp.args) - - -def is_optional(t: Type) -> bool: - t = get_proper_type(t) - return isinstance(t, UnionType) and any( - isinstance(get_proper_type(e), NoneType) for e in t.items - ) - - -def remove_optional(typ: Type) -> Type: - typ = get_proper_type(typ) - if isinstance(typ, UnionType): - return UnionType.make_union( - [t for t in typ.items if not isinstance(get_proper_type(t), NoneType)] - ) - else: - return typ + res = [] + for typ in types: + if not isinstance(typ, UnpackType): + res.append(typ) + continue + p_type = get_proper_type(typ.type) + if not isinstance(p_type, TupleType): + res.append(typ) + continue + res.extend(flatten_nested_tuples(p_type.items)) + return res def is_literal_type(typ: ProperType, fallback_fullname: str, value: LiteralValue) -> bool: @@ -3485,16 +3633,6 @@ def is_literal_type(typ: ProperType, fallback_fullname: str, value: LiteralValue ) -def is_self_type_like(typ: Type, *, is_classmethod: bool) -> bool: - """Does this look like a self-type annotation?""" - typ = get_proper_type(typ) - if not is_classmethod: - return isinstance(typ, TypeVarType) - if not isinstance(typ, TypeType): - return False - return isinstance(typ.item, TypeVarType) - - names: Final = globals().copy() names.pop("NOT_READY", None) deserialize_map: Final = { @@ -3516,98 +3654,44 @@ def callable_with_ellipsis(any_type: AnyType, ret_type: Type, fallback: Instance ) -def expand_param_spec( - t: ParamSpecType, repl: ParamSpecType | Parameters | CallableType -) -> ProperType: - """This is shared part of the logic w.r.t. ParamSpec instantiation. - - It is shared between type aliases and proper types, that currently use somewhat different - logic for instantiation.""" - if isinstance(repl, ParamSpecType): - return repl.copy_modified( - flavor=t.flavor, - prefix=t.prefix.copy_modified( - arg_types=t.prefix.arg_types + repl.prefix.arg_types, - arg_kinds=t.prefix.arg_kinds + repl.prefix.arg_kinds, - arg_names=t.prefix.arg_names + repl.prefix.arg_names, - ), - ) - else: - # if the paramspec is *P.args or **P.kwargs: - if t.flavor != ParamSpecFlavor.BARE: - assert isinstance(repl, CallableType), "Should not be able to get here." - # Is this always the right thing to do? - param_spec = repl.param_spec() - if param_spec: - return param_spec.with_flavor(t.flavor) - else: - return repl - else: - return Parameters( - t.prefix.arg_types + repl.arg_types, - t.prefix.arg_kinds + repl.arg_kinds, - t.prefix.arg_names + repl.arg_names, - variables=[*t.prefix.variables, *repl.variables], - ) +def remove_dups(types: list[T]) -> list[T]: + if len(types) <= 1: + return types + # Get unique elements in order of appearance + all_types: set[T] = set() + new_types: list[T] = [] + for t in types: + if t not in all_types: + new_types.append(t) + all_types.add(t) + return new_types -def store_argument_type( - defn: FuncItem, i: int, typ: CallableType, named_type: Callable[[str, list[Type]], Instance] -) -> None: - arg_type = typ.arg_types[i] - if typ.arg_kinds[i] == ARG_STAR: - if isinstance(arg_type, ParamSpecType): - pass - elif isinstance(arg_type, UnpackType): - unpacked_type = get_proper_type(arg_type.type) - if isinstance(unpacked_type, TupleType): - # Instead of using Tuple[Unpack[Tuple[...]]], just use - # Tuple[...] - arg_type = unpacked_type - elif ( - isinstance(unpacked_type, Instance) - and unpacked_type.type.fullname == "builtins.tuple" - ): - arg_type = unpacked_type - else: - arg_type = TupleType( - [arg_type], - fallback=named_type("builtins.tuple", [named_type("builtins.object", [])]), - ) +def type_vars_as_args(type_vars: Sequence[TypeVarLikeType]) -> tuple[Type, ...]: + """Represent type variables as they would appear in a type argument list.""" + args: list[Type] = [] + for tv in type_vars: + if isinstance(tv, TypeVarTupleType): + args.append(UnpackType(tv)) else: - # builtins.tuple[T] is typing.Tuple[T, ...] - arg_type = named_type("builtins.tuple", [arg_type]) - elif typ.arg_kinds[i] == ARG_STAR2: - if not isinstance(arg_type, ParamSpecType) and not typ.unpack_kwargs: - arg_type = named_type("builtins.dict", [named_type("builtins.str", []), arg_type]) - defn.arguments[i].variable.type = arg_type - - -def remove_trivial(types: Iterable[Type]) -> list[Type]: - """Make trivial simplifications on a list of types without calling is_subtype(). - - This makes following simplifications: - * Remove bottom types (taking into account strict optional setting) - * Remove everything else if there is an `object` - * Remove strict duplicate types - """ - removed_none = False - new_types = [] - all_types = set() - for t in types: - p_t = get_proper_type(t) - if isinstance(p_t, UninhabitedType): - continue - if isinstance(p_t, NoneType) and not state.strict_optional: - removed_none = True - continue - if isinstance(p_t, Instance) and p_t.type.fullname == "builtins.object": - return [p_t] - if p_t not in all_types: - new_types.append(t) - all_types.add(p_t) - if new_types: - return new_types - if removed_none: - return [NoneType()] - return [UninhabitedType()] + args.append(tv) + return tuple(args) + + +# This cyclic import is unfortunate, but to avoid it we would need to move away all uses +# of get_proper_type() from types.py. Majority of them have been removed, but few remaining +# are quite tricky to get rid of, but ultimately we want to do it at some point. +from mypy.expandtype import ExpandTypeVisitor + + +class InstantiateAliasVisitor(ExpandTypeVisitor): + def visit_union_type(self, t: UnionType) -> Type: + # Unlike regular expand_type(), we don't do any simplification for unions, + # not even removing strict duplicates. There are three reasons for this: + # * get_proper_type() is a very hot function, even slightest slow down will + # cause a perf regression + # * We want to preserve this historical behaviour, to avoid possible + # regressions + # * Simplifying unions may (indirectly) call get_proper_type(), causing + # infinite recursion. + return TypeTranslator.visit_union_type(self, t) diff --git a/mypy/types_utils.py b/mypy/types_utils.py new file mode 100644 index 000000000000..1cd56eae5835 --- /dev/null +++ b/mypy/types_utils.py @@ -0,0 +1,166 @@ +""" +This module is for (more basic) type operations that should not depend on is_subtype(), +meet_types(), join_types() etc. We don't want to keep them in mypy/types.py for two reasons: +* Reduce the size of that module. +* Reduce use of get_proper_type() in types.py to avoid cyclic imports + expand_type <-> types, if we move get_proper_type() to the former. +""" + +from __future__ import annotations + +from typing import Callable, Iterable, cast + +from mypy.nodes import ARG_STAR, ARG_STAR2, FuncItem, TypeAlias +from mypy.types import ( + AnyType, + CallableType, + Instance, + NoneType, + Overloaded, + ParamSpecType, + ProperType, + TupleType, + Type, + TypeAliasType, + TypeType, + TypeVarType, + UnionType, + UnpackType, + flatten_nested_unions, + get_proper_type, + get_proper_types, +) + + +def flatten_types(types: Iterable[Type]) -> Iterable[Type]: + for t in types: + tp = get_proper_type(t) + if isinstance(tp, UnionType): + yield from flatten_types(tp.items) + else: + yield t + + +def strip_type(typ: Type) -> Type: + """Make a copy of type without 'debugging info' (function name).""" + orig_typ = typ + typ = get_proper_type(typ) + if isinstance(typ, CallableType): + return typ.copy_modified(name=None) + elif isinstance(typ, Overloaded): + return Overloaded([cast(CallableType, strip_type(item)) for item in typ.items]) + else: + return orig_typ + + +def is_invalid_recursive_alias(seen_nodes: set[TypeAlias], target: Type) -> bool: + """Flag aliases like A = Union[int, A], T = tuple[int, *T] (and similar mutual aliases). + + Such aliases don't make much sense, and cause problems in later phases. + """ + if isinstance(target, TypeAliasType): + if target.alias in seen_nodes: + return True + assert target.alias, f"Unfixed type alias {target.type_ref}" + return is_invalid_recursive_alias(seen_nodes | {target.alias}, get_proper_type(target)) + assert isinstance(target, ProperType) + if not isinstance(target, (UnionType, TupleType)): + return False + if isinstance(target, UnionType): + return any(is_invalid_recursive_alias(seen_nodes, item) for item in target.items) + for item in target.items: + if isinstance(item, UnpackType): + if is_invalid_recursive_alias(seen_nodes, item.type): + return True + return False + + +def is_bad_type_type_item(item: Type) -> bool: + """Prohibit types like Type[Type[...]]. + + Such types are explicitly prohibited by PEP 484. Also, they cause problems + with recursive types like T = Type[T], because internal representation of + TypeType item is normalized (i.e. always a proper type). + """ + item = get_proper_type(item) + if isinstance(item, TypeType): + return True + if isinstance(item, UnionType): + return any( + isinstance(get_proper_type(i), TypeType) for i in flatten_nested_unions(item.items) + ) + return False + + +def is_union_with_any(tp: Type) -> bool: + """Is this a union with Any or a plain Any type?""" + tp = get_proper_type(tp) + if isinstance(tp, AnyType): + return True + if not isinstance(tp, UnionType): + return False + return any(is_union_with_any(t) for t in get_proper_types(tp.items)) + + +def is_generic_instance(tp: Type) -> bool: + tp = get_proper_type(tp) + return isinstance(tp, Instance) and bool(tp.args) + + +def is_overlapping_none(t: Type) -> bool: + t = get_proper_type(t) + return isinstance(t, NoneType) or ( + isinstance(t, UnionType) and any(isinstance(get_proper_type(e), NoneType) for e in t.items) + ) + + +def remove_optional(typ: Type) -> Type: + typ = get_proper_type(typ) + if isinstance(typ, UnionType): + return UnionType.make_union( + [t for t in typ.items if not isinstance(get_proper_type(t), NoneType)] + ) + else: + return typ + + +def is_self_type_like(typ: Type, *, is_classmethod: bool) -> bool: + """Does this look like a self-type annotation?""" + typ = get_proper_type(typ) + if not is_classmethod: + return isinstance(typ, TypeVarType) + if not isinstance(typ, TypeType): + return False + return isinstance(typ.item, TypeVarType) + + +def store_argument_type( + defn: FuncItem, i: int, typ: CallableType, named_type: Callable[[str, list[Type]], Instance] +) -> None: + arg_type = typ.arg_types[i] + if typ.arg_kinds[i] == ARG_STAR: + if isinstance(arg_type, ParamSpecType): + pass + elif isinstance(arg_type, UnpackType): + unpacked_type = get_proper_type(arg_type.type) + if isinstance(unpacked_type, TupleType): + # Instead of using Tuple[Unpack[Tuple[...]]], just use Tuple[...] + arg_type = unpacked_type + elif ( + isinstance(unpacked_type, Instance) + and unpacked_type.type.fullname == "builtins.tuple" + ): + arg_type = unpacked_type + else: + # TODO: verify that we can only have a TypeVarTuple here. + arg_type = TupleType( + [arg_type], + fallback=named_type("builtins.tuple", [named_type("builtins.object", [])]), + ) + else: + # builtins.tuple[T] is typing.Tuple[T, ...] + arg_type = named_type("builtins.tuple", [arg_type]) + elif typ.arg_kinds[i] == ARG_STAR2: + if not isinstance(arg_type, ParamSpecType) and not typ.unpack_kwargs: + arg_type = named_type("builtins.dict", [named_type("builtins.str", []), arg_type]) + defn.arguments[i].variable.type = arg_type diff --git a/mypy/typeshed/stdlib/VERSIONS b/mypy/typeshed/stdlib/VERSIONS index bd1abd204885..deb940395e1e 100644 --- a/mypy/typeshed/stdlib/VERSIONS +++ b/mypy/typeshed/stdlib/VERSIONS @@ -1,7 +1,7 @@ # The structure of this file is as follows: # - Blank lines and comments starting with `#` are ignored. # - Lines contain the name of a module, followed by a colon, -# a space, and a version range (for example: `symbol: 2.7-3.9`). +# a space, and a version range (for example: `symbol: 3.0-3.9`). # # Version ranges may be of the form "X.Y-A.B" or "X.Y-". The # first form means that a module was introduced in version X.Y and last @@ -12,56 +12,58 @@ # If a submodule is not listed separately, it has the same lifetime as # its parent module. # -# Python versions before 2.7 are ignored, so any module that was already -# present in 2.7 will have "2.7" as its minimum version. Version ranges +# Python versions before 3.0 are ignored, so any module that was already +# present in 3.0 will have "3.0" as its minimum version. Version ranges # for unsupported versions of Python 3 are generally accurate but we do # not guarantee their correctness. -__future__: 2.7- -__main__: 2.7- -_ast: 2.7- -_bisect: 2.7- +__future__: 3.0- +__main__: 3.0- +_ast: 3.0- +_bisect: 3.0- _bootlocale: 3.4-3.9 -_codecs: 2.7- +_codecs: 3.0- _collections_abc: 3.3- _compat_pickle: 3.1- _compression: 3.5- -_csv: 2.7- -_ctypes: 2.7- -_curses: 2.7- +_csv: 3.0- +_ctypes: 3.0- +_curses: 3.0- _decimal: 3.3- _dummy_thread: 3.0-3.8 -_dummy_threading: 2.7-3.8 -_heapq: 2.7- +_dummy_threading: 3.0-3.8 +_heapq: 3.0- _imp: 3.0- -_json: 2.7- -_markupbase: 2.7- -_msi: 2.7- +_json: 3.0- +_locale: 3.0- +_lsprof: 3.0- +_markupbase: 3.0- +_msi: 3.0- _operator: 3.4- -_osx_support: 2.7- +_osx_support: 3.0- _posixsubprocess: 3.2- _py_abc: 3.7- _pydecimal: 3.5- -_random: 2.7- +_random: 3.0- _sitebuiltins: 3.4- -_socket: 3.0- # present in 2.7 at runtime, but not in typeshed +_socket: 3.0- # present in 3.0 at runtime, but not in typeshed _stat: 3.4- -_thread: 2.7- -_threading_local: 2.7- -_tkinter: 2.7- +_thread: 3.0- +_threading_local: 3.0- +_tkinter: 3.0- _tracemalloc: 3.4- -_typeshed: 2.7- # not present at runtime, only for type checking -_warnings: 2.7- -_weakref: 2.7- -_weakrefset: 2.7- +_typeshed: 3.0- # not present at runtime, only for type checking +_warnings: 3.0- +_weakref: 3.0- +_weakrefset: 3.0- _winapi: 3.3- -abc: 2.7- -aifc: 2.7- -antigravity: 2.7- -argparse: 2.7- -array: 2.7- -ast: 2.7- -asynchat: 2.7- +abc: 3.0- +aifc: 3.0-3.12 +antigravity: 3.0- +argparse: 3.0- +array: 3.0- +ast: 3.0- +asynchat: 3.0-3.11 asyncio: 3.4- asyncio.mixins: 3.10- asyncio.exceptions: 3.8- @@ -72,228 +74,236 @@ asyncio.taskgroups: 3.11- asyncio.threads: 3.9- asyncio.timeouts: 3.11- asyncio.trsock: 3.8- -asyncore: 2.7- -atexit: 2.7- -audioop: 2.7- -base64: 2.7- -bdb: 2.7- -binascii: 2.7- -binhex: 2.7-3.10 -bisect: 2.7- +asyncore: 3.0-3.11 +atexit: 3.0- +audioop: 3.0-3.12 +base64: 3.0- +bdb: 3.0- +binascii: 3.0- +binhex: 3.0-3.10 +bisect: 3.0- builtins: 3.0- -bz2: 2.7- -cProfile: 2.7- -calendar: 2.7- -cgi: 2.7- -cgitb: 2.7- -chunk: 2.7- -cmath: 2.7- -cmd: 2.7- -code: 2.7- -codecs: 2.7- -codeop: 2.7- -collections: 2.7- +bz2: 3.0- +cProfile: 3.0- +calendar: 3.0- +cgi: 3.0-3.12 +cgitb: 3.0-3.12 +chunk: 3.0-3.12 +cmath: 3.0- +cmd: 3.0- +code: 3.0- +codecs: 3.0- +codeop: 3.0- +collections: 3.0- collections.abc: 3.3- -colorsys: 2.7- -compileall: 2.7- +colorsys: 3.0- +compileall: 3.0- concurrent: 3.2- configparser: 3.0- -contextlib: 2.7- +contextlib: 3.0- contextvars: 3.7- -copy: 2.7- -copyreg: 2.7- -crypt: 2.7- -csv: 2.7- -ctypes: 2.7- -curses: 2.7- +copy: 3.0- +copyreg: 3.0- +crypt: 3.0-3.12 +csv: 3.0- +ctypes: 3.0- +curses: 3.0- dataclasses: 3.7- -datetime: 2.7- -dbm: 2.7- -decimal: 2.7- -difflib: 2.7- -dis: 2.7- -distutils: 2.7- -distutils.command.bdist_msi: 2.7-3.10 -distutils.command.bdist_wininst: 2.7-3.9 -doctest: 2.7- -dummy_threading: 2.7-3.8 -email: 2.7- -encodings: 2.7- -ensurepip: 2.7- +datetime: 3.0- +dbm: 3.0- +decimal: 3.0- +difflib: 3.0- +dis: 3.0- +distutils: 3.0-3.11 +distutils.command.bdist_msi: 3.0-3.10 +distutils.command.bdist_wininst: 3.0-3.9 +doctest: 3.0- +dummy_threading: 3.0-3.8 +email: 3.0- +encodings: 3.0- +ensurepip: 3.0- enum: 3.4- -errno: 2.7- +errno: 3.0- faulthandler: 3.3- -fcntl: 2.7- -filecmp: 2.7- -fileinput: 2.7- -fnmatch: 2.7- -formatter: 2.7-3.9 -fractions: 2.7- -ftplib: 2.7- -functools: 2.7- -gc: 2.7- -genericpath: 2.7- -getopt: 2.7- -getpass: 2.7- -gettext: 2.7- -glob: 2.7- +fcntl: 3.0- +filecmp: 3.0- +fileinput: 3.0- +fnmatch: 3.0- +formatter: 3.0-3.9 +fractions: 3.0- +ftplib: 3.0- +functools: 3.0- +gc: 3.0- +genericpath: 3.0- +getopt: 3.0- +getpass: 3.0- +gettext: 3.0- +glob: 3.0- graphlib: 3.9- -grp: 2.7- -gzip: 2.7- -hashlib: 2.7- -heapq: 2.7- -hmac: 2.7- +grp: 3.0- +gzip: 3.0- +hashlib: 3.0- +heapq: 3.0- +hmac: 3.0- html: 3.0- http: 3.0- -imaplib: 2.7- -imghdr: 2.7- -imp: 2.7- -importlib: 2.7- +imaplib: 3.0- +imghdr: 3.0-3.12 +imp: 3.0-3.11 +importlib: 3.0- +importlib._abc: 3.10- importlib.metadata: 3.8- importlib.metadata._meta: 3.10- +importlib.readers: 3.10- importlib.resources: 3.7- -inspect: 2.7- -io: 2.7- +importlib.resources.abc: 3.11- +importlib.resources.readers: 3.11- +importlib.resources.simple: 3.11- +importlib.simple: 3.11- +inspect: 3.0- +io: 3.0- ipaddress: 3.3- -itertools: 2.7- -json: 2.7- -keyword: 2.7- -lib2to3: 2.7- -linecache: 2.7- -locale: 2.7- -logging: 2.7- +itertools: 3.0- +json: 3.0- +keyword: 3.0- +lib2to3: 3.0- +linecache: 3.0- +locale: 3.0- +logging: 3.0- lzma: 3.3- -macpath: 2.7-3.7 -mailbox: 2.7- -mailcap: 2.7- -marshal: 2.7- -math: 2.7- -mimetypes: 2.7- -mmap: 2.7- -modulefinder: 2.7- -msilib: 2.7- -msvcrt: 2.7- -multiprocessing: 2.7- +mailbox: 3.0- +mailcap: 3.0-3.12 +marshal: 3.0- +math: 3.0- +mimetypes: 3.0- +mmap: 3.0- +modulefinder: 3.0- +msilib: 3.0-3.12 +msvcrt: 3.0- +multiprocessing: 3.0- multiprocessing.resource_tracker: 3.8- multiprocessing.shared_memory: 3.8- -netrc: 2.7- -nis: 2.7- -nntplib: 2.7- -ntpath: 2.7- -nturl2path: 2.7- -numbers: 2.7- -opcode: 2.7- -operator: 2.7- -optparse: 2.7- -os: 2.7- -ossaudiodev: 2.7- -parser: 2.7-3.9 +netrc: 3.0- +nis: 3.0-3.12 +nntplib: 3.0-3.12 +nt: 3.0- +ntpath: 3.0- +nturl2path: 3.0- +numbers: 3.0- +opcode: 3.0- +operator: 3.0- +optparse: 3.0- +os: 3.0- +ossaudiodev: 3.0-3.12 +parser: 3.0-3.9 pathlib: 3.4- -pdb: 2.7- -pickle: 2.7- -pickletools: 2.7- -pipes: 2.7- -pkgutil: 2.7- -platform: 2.7- -plistlib: 2.7- -poplib: 2.7- -posix: 2.7- -posixpath: 2.7- -pprint: 2.7- -profile: 2.7- -pstats: 2.7- -pty: 2.7- -pwd: 2.7- -py_compile: 2.7- -pyclbr: 2.7- -pydoc: 2.7- -pydoc_data: 2.7- -pyexpat: 2.7- +pdb: 3.0- +pickle: 3.0- +pickletools: 3.0- +pipes: 3.0-3.12 +pkgutil: 3.0- +platform: 3.0- +plistlib: 3.0- +poplib: 3.0- +posix: 3.0- +posixpath: 3.0- +pprint: 3.0- +profile: 3.0- +pstats: 3.0- +pty: 3.0- +pwd: 3.0- +py_compile: 3.0- +pyclbr: 3.0- +pydoc: 3.0- +pydoc_data: 3.0- +pyexpat: 3.0- queue: 3.0- -quopri: 2.7- -random: 2.7- -re: 2.7- -readline: 2.7- +quopri: 3.0- +random: 3.0- +re: 3.0- +readline: 3.0- reprlib: 3.0- -resource: 2.7- -rlcompleter: 2.7- -runpy: 2.7- -sched: 2.7- +resource: 3.0- +rlcompleter: 3.0- +runpy: 3.0- +sched: 3.0- secrets: 3.6- -select: 2.7- +select: 3.0- selectors: 3.4- -shelve: 2.7- -shlex: 2.7- -shutil: 2.7- -signal: 2.7- -site: 2.7- -smtpd: 2.7- -smtplib: 2.7- -sndhdr: 2.7- -socket: 2.7- +shelve: 3.0- +shlex: 3.0- +shutil: 3.0- +signal: 3.0- +site: 3.0- +smtpd: 3.0-3.11 +smtplib: 3.0- +sndhdr: 3.0-3.12 +socket: 3.0- socketserver: 3.0- -spwd: 2.7- -sqlite3: 2.7- -sre_compile: 2.7- -sre_constants: 2.7- -sre_parse: 2.7- -ssl: 2.7- -stat: 2.7- +spwd: 3.0-3.12 +sqlite3: 3.0- +sre_compile: 3.0- +sre_constants: 3.0- +sre_parse: 3.0- +ssl: 3.0- +stat: 3.0- statistics: 3.4- -string: 2.7- -stringprep: 2.7- -struct: 2.7- -subprocess: 2.7- -sunau: 2.7- -symbol: 2.7-3.9 -symtable: 2.7- -sys: 2.7- -sysconfig: 2.7- -syslog: 2.7- -tabnanny: 2.7- -tarfile: 2.7- -telnetlib: 2.7- -tempfile: 2.7- -termios: 2.7- -textwrap: 2.7- -this: 2.7- -threading: 2.7- -time: 2.7- -timeit: 2.7- +string: 3.0- +stringprep: 3.0- +struct: 3.0- +subprocess: 3.0- +sunau: 3.0-3.12 +symbol: 3.0-3.9 +symtable: 3.0- +sys: 3.0- +sys._monitoring: 3.12- # Doesn't actually exist. See comments in the stub. +sysconfig: 3.0- +syslog: 3.0- +tabnanny: 3.0- +tarfile: 3.0- +telnetlib: 3.0-3.12 +tempfile: 3.0- +termios: 3.0- +textwrap: 3.0- +this: 3.0- +threading: 3.0- +time: 3.0- +timeit: 3.0- tkinter: 3.0- -token: 2.7- -tokenize: 2.7- +token: 3.0- +tokenize: 3.0- tomllib: 3.11- -trace: 2.7- -traceback: 2.7- +trace: 3.0- +traceback: 3.0- tracemalloc: 3.4- -tty: 2.7- -turtle: 2.7- -types: 2.7- +tty: 3.0- +turtle: 3.0- +types: 3.0- typing: 3.5- -typing_extensions: 2.7- -unicodedata: 2.7- -unittest: 2.7- +typing_extensions: 3.0- +unicodedata: 3.0- +unittest: 3.0- unittest._log: 3.9- unittest.async_case: 3.8- -urllib: 2.7- -uu: 2.7- -uuid: 2.7- +urllib: 3.0- +uu: 3.0-3.12 +uuid: 3.0- venv: 3.3- -warnings: 2.7- -wave: 2.7- -weakref: 2.7- -webbrowser: 2.7- +warnings: 3.0- +wave: 3.0- +weakref: 3.0- +webbrowser: 3.0- winreg: 3.0- -winsound: 2.7- -wsgiref: 2.7- +winsound: 3.0- +wsgiref: 3.0- wsgiref.types: 3.11- -xdrlib: 2.7- -xml: 2.7- +xdrlib: 3.0-3.12 +xml: 3.0- xmlrpc: 3.0- xxlimited: 3.2- zipapp: 3.5- -zipfile: 2.7- -zipimport: 2.7- -zlib: 2.7- +zipfile: 3.0- +zipfile._path: 3.12- +zipimport: 3.0- +zlib: 3.0- zoneinfo: 3.9- diff --git a/mypy/typeshed/stdlib/_ast.pyi b/mypy/typeshed/stdlib/_ast.pyi index 7bc47266d713..0758450dfa7c 100644 --- a/mypy/typeshed/stdlib/_ast.pyi +++ b/mypy/typeshed/stdlib/_ast.pyi @@ -1,13 +1,16 @@ import sys -from typing import Any, ClassVar -from typing_extensions import Literal, TypeAlias +import typing_extensions +from typing import Any, ClassVar, Literal PyCF_ONLY_AST: Literal[1024] -if sys.version_info >= (3, 8): - PyCF_TYPE_COMMENTS: Literal[4096] - PyCF_ALLOW_TOP_LEVEL_AWAIT: Literal[8192] +PyCF_TYPE_COMMENTS: Literal[4096] +PyCF_ALLOW_TOP_LEVEL_AWAIT: Literal[8192] -_Identifier: TypeAlias = str +# Alias used for fields that must always be valid identifiers +# A string `x` counts as a valid identifier if both the following are True +# (1) `x.isidentifier()` evaluates to `True` +# (2) `keyword.iskeyword(x)` evaluates to `False` +_Identifier: typing_extensions.TypeAlias = str class AST: if sys.version_info >= (3, 10): @@ -18,33 +21,29 @@ class AST: # TODO: Not all nodes have all of the following attributes lineno: int col_offset: int - if sys.version_info >= (3, 8): - end_lineno: int | None - end_col_offset: int | None - type_comment: str | None + end_lineno: int | None + end_col_offset: int | None + type_comment: str | None class mod(AST): ... +class type_ignore(AST): ... -if sys.version_info >= (3, 8): - class type_ignore(AST): ... - - class TypeIgnore(type_ignore): - if sys.version_info >= (3, 10): - __match_args__ = ("lineno", "tag") - tag: str +class TypeIgnore(type_ignore): + if sys.version_info >= (3, 10): + __match_args__ = ("lineno", "tag") + tag: str - class FunctionType(mod): - if sys.version_info >= (3, 10): - __match_args__ = ("argtypes", "returns") - argtypes: list[expr] - returns: expr +class FunctionType(mod): + if sys.version_info >= (3, 10): + __match_args__ = ("argtypes", "returns") + argtypes: list[expr] + returns: expr class Module(mod): if sys.version_info >= (3, 10): __match_args__ = ("body", "type_ignores") body: list[stmt] - if sys.version_info >= (3, 8): - type_ignores: list[TypeIgnore] + type_ignores: list[TypeIgnore] class Interactive(mod): if sys.version_info >= (3, 10): @@ -59,31 +58,43 @@ class Expression(mod): class stmt(AST): ... class FunctionDef(stmt): - if sys.version_info >= (3, 10): + if sys.version_info >= (3, 12): + __match_args__ = ("name", "args", "body", "decorator_list", "returns", "type_comment", "type_params") + elif sys.version_info >= (3, 10): __match_args__ = ("name", "args", "body", "decorator_list", "returns", "type_comment") name: _Identifier args: arguments body: list[stmt] decorator_list: list[expr] returns: expr | None + if sys.version_info >= (3, 12): + type_params: list[type_param] class AsyncFunctionDef(stmt): - if sys.version_info >= (3, 10): + if sys.version_info >= (3, 12): + __match_args__ = ("name", "args", "body", "decorator_list", "returns", "type_comment", "type_params") + elif sys.version_info >= (3, 10): __match_args__ = ("name", "args", "body", "decorator_list", "returns", "type_comment") name: _Identifier args: arguments body: list[stmt] decorator_list: list[expr] returns: expr | None + if sys.version_info >= (3, 12): + type_params: list[type_param] class ClassDef(stmt): - if sys.version_info >= (3, 10): + if sys.version_info >= (3, 12): + __match_args__ = ("name", "bases", "keywords", "body", "decorator_list", "type_params") + elif sys.version_info >= (3, 10): __match_args__ = ("name", "bases", "keywords", "body", "decorator_list") name: _Identifier bases: list[expr] keywords: list[keyword] body: list[stmt] decorator_list: list[expr] + if sys.version_info >= (3, 12): + type_params: list[type_param] class Return(stmt): if sys.version_info >= (3, 10): @@ -327,21 +338,6 @@ class JoinedStr(expr): __match_args__ = ("values",) values: list[expr] -if sys.version_info < (3, 8): - class Num(expr): # Deprecated in 3.8; use Constant - n: int | float | complex - - class Str(expr): # Deprecated in 3.8; use Constant - s: str - - class Bytes(expr): # Deprecated in 3.8; use Constant - s: bytes - - class NameConstant(expr): # Deprecated in 3.8; use Constant - value: Any - - class Ellipsis(expr): ... # Deprecated in 3.8; use Constant - class Constant(expr): if sys.version_info >= (3, 10): __match_args__ = ("value", "kind") @@ -351,12 +347,11 @@ class Constant(expr): s: Any n: int | float | complex -if sys.version_info >= (3, 8): - class NamedExpr(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("target", "value") - target: Name - value: expr +class NamedExpr(expr): + if sys.version_info >= (3, 10): + __match_args__ = ("target", "value") + target: Name + value: expr class Attribute(expr): if sys.version_info >= (3, 10): @@ -366,10 +361,10 @@ class Attribute(expr): ctx: expr_context if sys.version_info >= (3, 9): - _Slice: TypeAlias = expr + _Slice: typing_extensions.TypeAlias = expr else: class slice(AST): ... - _Slice: TypeAlias = slice + _Slice: typing_extensions.TypeAlias = slice class Slice(_Slice): if sys.version_info >= (3, 10): @@ -485,8 +480,7 @@ class ExceptHandler(excepthandler): class arguments(AST): if sys.version_info >= (3, 10): __match_args__ = ("posonlyargs", "args", "vararg", "kwonlyargs", "kw_defaults", "kwarg", "defaults") - if sys.version_info >= (3, 8): - posonlyargs: list[arg] + posonlyargs: list[arg] args: list[arg] vararg: arg | None kwonlyargs: list[arg] @@ -509,7 +503,7 @@ class keyword(AST): class alias(AST): if sys.version_info >= (3, 10): __match_args__ = ("name", "asname") - name: _Identifier + name: str asname: _Identifier | None class withitem(AST): @@ -526,7 +520,7 @@ if sys.version_info >= (3, 10): class pattern(AST): ... # Without the alias, Pyright complains variables named pattern are recursively defined - _Pattern: TypeAlias = pattern + _Pattern: typing_extensions.TypeAlias = pattern class match_case(AST): __match_args__ = ("pattern", "guard", "body") @@ -540,7 +534,7 @@ if sys.version_info >= (3, 10): class MatchSingleton(pattern): __match_args__ = ("value",) - value: Literal[True, False, None] + value: Literal[True, False] | None class MatchSequence(pattern): __match_args__ = ("patterns",) @@ -571,3 +565,27 @@ if sys.version_info >= (3, 10): class MatchOr(pattern): __match_args__ = ("patterns",) patterns: list[pattern] + +if sys.version_info >= (3, 12): + class type_param(AST): + end_lineno: int + end_col_offset: int + + class TypeVar(type_param): + __match_args__ = ("name", "bound") + name: _Identifier + bound: expr | None + + class ParamSpec(type_param): + __match_args__ = ("name",) + name: _Identifier + + class TypeVarTuple(type_param): + __match_args__ = ("name",) + name: _Identifier + + class TypeAlias(stmt): + __match_args__ = ("name", "type_params", "value") + name: Name + type_params: list[type_param] + value: expr diff --git a/mypy/typeshed/stdlib/_bisect.pyi b/mypy/typeshed/stdlib/_bisect.pyi index 4c79eec14d72..58488e3d15af 100644 --- a/mypy/typeshed/stdlib/_bisect.pyi +++ b/mypy/typeshed/stdlib/_bisect.pyi @@ -1,6 +1,6 @@ import sys -from _typeshed import SupportsRichComparisonT -from collections.abc import Callable, MutableSequence, Sequence +from _typeshed import SupportsLenAndGetItem, SupportsRichComparisonT +from collections.abc import Callable, MutableSequence from typing import TypeVar, overload _T = TypeVar("_T") @@ -8,11 +8,16 @@ _T = TypeVar("_T") if sys.version_info >= (3, 10): @overload def bisect_left( - a: Sequence[SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int = 0, hi: int | None = None, *, key: None = None + a: SupportsLenAndGetItem[SupportsRichComparisonT], + x: SupportsRichComparisonT, + lo: int = 0, + hi: int | None = None, + *, + key: None = None, ) -> int: ... @overload def bisect_left( - a: Sequence[_T], + a: SupportsLenAndGetItem[_T], x: SupportsRichComparisonT, lo: int = 0, hi: int | None = None, @@ -21,11 +26,16 @@ if sys.version_info >= (3, 10): ) -> int: ... @overload def bisect_right( - a: Sequence[SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int = 0, hi: int | None = None, *, key: None = None + a: SupportsLenAndGetItem[SupportsRichComparisonT], + x: SupportsRichComparisonT, + lo: int = 0, + hi: int | None = None, + *, + key: None = None, ) -> int: ... @overload def bisect_right( - a: Sequence[_T], + a: SupportsLenAndGetItem[_T], x: SupportsRichComparisonT, lo: int = 0, hi: int | None = None, @@ -61,10 +71,10 @@ if sys.version_info >= (3, 10): else: def bisect_left( - a: Sequence[SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int = 0, hi: int | None = None + a: SupportsLenAndGetItem[SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int = 0, hi: int | None = None ) -> int: ... def bisect_right( - a: Sequence[SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int = 0, hi: int | None = None + a: SupportsLenAndGetItem[SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int = 0, hi: int | None = None ) -> int: ... def insort_left( a: MutableSequence[SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int = 0, hi: int | None = None diff --git a/mypy/typeshed/stdlib/_codecs.pyi b/mypy/typeshed/stdlib/_codecs.pyi index 51f17f01ca71..ecf874d33ddd 100644 --- a/mypy/typeshed/stdlib/_codecs.pyi +++ b/mypy/typeshed/stdlib/_codecs.pyi @@ -2,8 +2,8 @@ import codecs import sys from _typeshed import ReadableBuffer from collections.abc import Callable -from typing import overload -from typing_extensions import Literal, TypeAlias +from typing import Literal, overload +from typing_extensions import TypeAlias # This type is not exposed; it is defined in unicodeobject.c class _EncodingMap: @@ -13,13 +13,13 @@ _CharMap: TypeAlias = dict[int, int] | _EncodingMap _Handler: TypeAlias = Callable[[UnicodeError], tuple[str | bytes, int]] _SearchFunction: TypeAlias = Callable[[str], codecs.CodecInfo | None] -def register(__search_function: _SearchFunction) -> None: ... +def register(search_function: _SearchFunction, /) -> None: ... if sys.version_info >= (3, 10): - def unregister(__search_function: _SearchFunction) -> None: ... + def unregister(search_function: _SearchFunction, /) -> None: ... -def register_error(__errors: str, __handler: _Handler) -> None: ... -def lookup_error(__name: str) -> _Handler: ... +def register_error(errors: str, handler: _Handler, /) -> None: ... +def lookup_error(name: str, /) -> _Handler: ... # The type ignore on `encode` and `decode` is to avoid issues with overlapping overloads, for more details, see #300 # https://docs.python.org/3/library/codecs.html#binary-transforms @@ -47,11 +47,11 @@ _StrToStrEncoding: TypeAlias = Literal["rot13", "rot_13"] @overload def encode(obj: ReadableBuffer, encoding: _BytesToBytesEncoding, errors: str = "strict") -> bytes: ... @overload -def encode(obj: str, encoding: _StrToStrEncoding, errors: str = "strict") -> str: ... # type: ignore[misc] +def encode(obj: str, encoding: _StrToStrEncoding, errors: str = "strict") -> str: ... # type: ignore[overload-overlap] @overload def encode(obj: str, encoding: str = "utf-8", errors: str = "strict") -> bytes: ... @overload -def decode(obj: ReadableBuffer, encoding: _BytesToBytesEncoding, errors: str = "strict") -> bytes: ... # type: ignore[misc] +def decode(obj: ReadableBuffer, encoding: _BytesToBytesEncoding, errors: str = "strict") -> bytes: ... # type: ignore[overload-overlap] @overload def decode(obj: str, encoding: _StrToStrEncoding, errors: str = "strict") -> str: ... @@ -68,71 +68,66 @@ def decode( def decode(obj: str, encoding: Literal["hex", "hex_codec"], errors: str = "strict") -> bytes: ... @overload def decode(obj: ReadableBuffer, encoding: str = "utf-8", errors: str = "strict") -> str: ... -def lookup(__encoding: str) -> codecs.CodecInfo: ... -def charmap_build(__map: str) -> _CharMap: ... -def ascii_decode(__data: ReadableBuffer, __errors: str | None = None) -> tuple[str, int]: ... -def ascii_encode(__str: str, __errors: str | None = None) -> tuple[bytes, int]: ... -def charmap_decode(__data: ReadableBuffer, __errors: str | None = None, __mapping: _CharMap | None = None) -> tuple[str, int]: ... -def charmap_encode(__str: str, __errors: str | None = None, __mapping: _CharMap | None = None) -> tuple[bytes, int]: ... -def escape_decode(__data: str | ReadableBuffer, __errors: str | None = None) -> tuple[str, int]: ... -def escape_encode(__data: bytes, __errors: str | None = None) -> tuple[bytes, int]: ... -def latin_1_decode(__data: ReadableBuffer, __errors: str | None = None) -> tuple[str, int]: ... -def latin_1_encode(__str: str, __errors: str | None = None) -> tuple[bytes, int]: ... +def lookup(encoding: str, /) -> codecs.CodecInfo: ... +def charmap_build(map: str, /) -> _CharMap: ... +def ascii_decode(data: ReadableBuffer, errors: str | None = None, /) -> tuple[str, int]: ... +def ascii_encode(str: str, errors: str | None = None, /) -> tuple[bytes, int]: ... +def charmap_decode(data: ReadableBuffer, errors: str | None = None, mapping: _CharMap | None = None, /) -> tuple[str, int]: ... +def charmap_encode(str: str, errors: str | None = None, mapping: _CharMap | None = None, /) -> tuple[bytes, int]: ... +def escape_decode(data: str | ReadableBuffer, errors: str | None = None, /) -> tuple[str, int]: ... +def escape_encode(data: bytes, errors: str | None = None, /) -> tuple[bytes, int]: ... +def latin_1_decode(data: ReadableBuffer, errors: str | None = None, /) -> tuple[str, int]: ... +def latin_1_encode(str: str, errors: str | None = None, /) -> tuple[bytes, int]: ... if sys.version_info >= (3, 9): def raw_unicode_escape_decode( - __data: str | ReadableBuffer, __errors: str | None = None, __final: bool = True + data: str | ReadableBuffer, errors: str | None = None, final: bool = True, / ) -> tuple[str, int]: ... else: - def raw_unicode_escape_decode(__data: str | ReadableBuffer, __errors: str | None = None) -> tuple[str, int]: ... + def raw_unicode_escape_decode(data: str | ReadableBuffer, errors: str | None = None, /) -> tuple[str, int]: ... -def raw_unicode_escape_encode(__str: str, __errors: str | None = None) -> tuple[bytes, int]: ... -def readbuffer_encode(__data: str | ReadableBuffer, __errors: str | None = None) -> tuple[bytes, int]: ... +def raw_unicode_escape_encode(str: str, errors: str | None = None, /) -> tuple[bytes, int]: ... +def readbuffer_encode(data: str | ReadableBuffer, errors: str | None = None, /) -> tuple[bytes, int]: ... if sys.version_info >= (3, 9): def unicode_escape_decode( - __data: str | ReadableBuffer, __errors: str | None = None, __final: bool = True + data: str | ReadableBuffer, errors: str | None = None, final: bool = True, / ) -> tuple[str, int]: ... else: - def unicode_escape_decode(__data: str | ReadableBuffer, __errors: str | None = None) -> tuple[str, int]: ... + def unicode_escape_decode(data: str | ReadableBuffer, errors: str | None = None, /) -> tuple[str, int]: ... -def unicode_escape_encode(__str: str, __errors: str | None = None) -> tuple[bytes, int]: ... - -if sys.version_info < (3, 8): - def unicode_internal_decode(__obj: str | ReadableBuffer, __errors: str | None = None) -> tuple[str, int]: ... - def unicode_internal_encode(__obj: str | ReadableBuffer, __errors: str | None = None) -> tuple[bytes, int]: ... - -def utf_16_be_decode(__data: ReadableBuffer, __errors: str | None = None, __final: bool = False) -> tuple[str, int]: ... -def utf_16_be_encode(__str: str, __errors: str | None = None) -> tuple[bytes, int]: ... -def utf_16_decode(__data: ReadableBuffer, __errors: str | None = None, __final: bool = False) -> tuple[str, int]: ... -def utf_16_encode(__str: str, __errors: str | None = None, __byteorder: int = 0) -> tuple[bytes, int]: ... +def unicode_escape_encode(str: str, errors: str | None = None, /) -> tuple[bytes, int]: ... +def utf_16_be_decode(data: ReadableBuffer, errors: str | None = None, final: bool = False, /) -> tuple[str, int]: ... +def utf_16_be_encode(str: str, errors: str | None = None, /) -> tuple[bytes, int]: ... +def utf_16_decode(data: ReadableBuffer, errors: str | None = None, final: bool = False, /) -> tuple[str, int]: ... +def utf_16_encode(str: str, errors: str | None = None, byteorder: int = 0, /) -> tuple[bytes, int]: ... def utf_16_ex_decode( - __data: ReadableBuffer, __errors: str | None = None, __byteorder: int = 0, __final: bool = False + data: ReadableBuffer, errors: str | None = None, byteorder: int = 0, final: bool = False, / ) -> tuple[str, int, int]: ... -def utf_16_le_decode(__data: ReadableBuffer, __errors: str | None = None, __final: bool = False) -> tuple[str, int]: ... -def utf_16_le_encode(__str: str, __errors: str | None = None) -> tuple[bytes, int]: ... -def utf_32_be_decode(__data: ReadableBuffer, __errors: str | None = None, __final: bool = False) -> tuple[str, int]: ... -def utf_32_be_encode(__str: str, __errors: str | None = None) -> tuple[bytes, int]: ... -def utf_32_decode(__data: ReadableBuffer, __errors: str | None = None, __final: bool = False) -> tuple[str, int]: ... -def utf_32_encode(__str: str, __errors: str | None = None, __byteorder: int = 0) -> tuple[bytes, int]: ... +def utf_16_le_decode(data: ReadableBuffer, errors: str | None = None, final: bool = False, /) -> tuple[str, int]: ... +def utf_16_le_encode(str: str, errors: str | None = None, /) -> tuple[bytes, int]: ... +def utf_32_be_decode(data: ReadableBuffer, errors: str | None = None, final: bool = False, /) -> tuple[str, int]: ... +def utf_32_be_encode(str: str, errors: str | None = None, /) -> tuple[bytes, int]: ... +def utf_32_decode(data: ReadableBuffer, errors: str | None = None, final: bool = False, /) -> tuple[str, int]: ... +def utf_32_encode(str: str, errors: str | None = None, byteorder: int = 0, /) -> tuple[bytes, int]: ... def utf_32_ex_decode( - __data: ReadableBuffer, __errors: str | None = None, __byteorder: int = 0, __final: bool = False + data: ReadableBuffer, errors: str | None = None, byteorder: int = 0, final: bool = False, / ) -> tuple[str, int, int]: ... -def utf_32_le_decode(__data: ReadableBuffer, __errors: str | None = None, __final: bool = False) -> tuple[str, int]: ... -def utf_32_le_encode(__str: str, __errors: str | None = None) -> tuple[bytes, int]: ... -def utf_7_decode(__data: ReadableBuffer, __errors: str | None = None, __final: bool = False) -> tuple[str, int]: ... -def utf_7_encode(__str: str, __errors: str | None = None) -> tuple[bytes, int]: ... -def utf_8_decode(__data: ReadableBuffer, __errors: str | None = None, __final: bool = False) -> tuple[str, int]: ... -def utf_8_encode(__str: str, __errors: str | None = None) -> tuple[bytes, int]: ... +def utf_32_le_decode(data: ReadableBuffer, errors: str | None = None, final: bool = False, /) -> tuple[str, int]: ... +def utf_32_le_encode(str: str, errors: str | None = None, /) -> tuple[bytes, int]: ... +def utf_7_decode(data: ReadableBuffer, errors: str | None = None, final: bool = False, /) -> tuple[str, int]: ... +def utf_7_encode(str: str, errors: str | None = None, /) -> tuple[bytes, int]: ... +def utf_8_decode(data: ReadableBuffer, errors: str | None = None, final: bool = False, /) -> tuple[str, int]: ... +def utf_8_encode(str: str, errors: str | None = None, /) -> tuple[bytes, int]: ... if sys.platform == "win32": - def mbcs_decode(__data: ReadableBuffer, __errors: str | None = None, __final: bool = False) -> tuple[str, int]: ... - def mbcs_encode(__str: str, __errors: str | None = None) -> tuple[bytes, int]: ... + def mbcs_decode(data: ReadableBuffer, errors: str | None = None, final: bool = False, /) -> tuple[str, int]: ... + def mbcs_encode(str: str, errors: str | None = None, /) -> tuple[bytes, int]: ... def code_page_decode( - __codepage: int, __data: ReadableBuffer, __errors: str | None = None, __final: bool = False + codepage: int, data: ReadableBuffer, errors: str | None = None, final: bool = False, / ) -> tuple[str, int]: ... - def code_page_encode(__code_page: int, __str: str, __errors: str | None = None) -> tuple[bytes, int]: ... - def oem_decode(__data: ReadableBuffer, __errors: str | None = None, __final: bool = False) -> tuple[str, int]: ... - def oem_encode(__str: str, __errors: str | None = None) -> tuple[bytes, int]: ... + def code_page_encode(code_page: int, str: str, errors: str | None = None, /) -> tuple[bytes, int]: ... + def oem_decode(data: ReadableBuffer, errors: str | None = None, final: bool = False, /) -> tuple[str, int]: ... + def oem_encode(str: str, errors: str | None = None, /) -> tuple[bytes, int]: ... diff --git a/mypy/typeshed/stdlib/_collections_abc.pyi b/mypy/typeshed/stdlib/_collections_abc.pyi index 352da6cfb331..e467d626e8a8 100644 --- a/mypy/typeshed/stdlib/_collections_abc.pyi +++ b/mypy/typeshed/stdlib/_collections_abc.pyi @@ -1,6 +1,7 @@ import sys +from abc import abstractmethod from types import MappingProxyType -from typing import ( # noqa: Y022,Y038 +from typing import ( # noqa: Y022,Y038,Y057 AbstractSet as Set, AsyncGenerator as AsyncGenerator, AsyncIterable as AsyncIterable, @@ -23,13 +24,15 @@ from typing import ( # noqa: Y022,Y038 MutableMapping as MutableMapping, MutableSequence as MutableSequence, MutableSet as MutableSet, + Protocol, Reversible as Reversible, Sequence as Sequence, Sized as Sized, TypeVar, ValuesView as ValuesView, + final, + runtime_checkable, ) -from typing_extensions import final __all__ = [ "Awaitable", @@ -58,12 +61,15 @@ __all__ = [ "MutableSequence", "ByteString", ] +if sys.version_info >= (3, 12): + __all__ += ["Buffer"] _KT_co = TypeVar("_KT_co", covariant=True) # Key type covariant containers. _VT_co = TypeVar("_VT_co", covariant=True) # Value type covariant containers. @final class dict_keys(KeysView[_KT_co], Generic[_KT_co, _VT_co]): # undocumented + def __eq__(self, value: object, /) -> bool: ... if sys.version_info >= (3, 10): @property def mapping(self) -> MappingProxyType[_KT_co, _VT_co]: ... @@ -75,7 +81,14 @@ class dict_values(ValuesView[_VT_co], Generic[_KT_co, _VT_co]): # undocumented def mapping(self) -> MappingProxyType[_KT_co, _VT_co]: ... @final -class dict_items(ItemsView[_KT_co, _VT_co], Generic[_KT_co, _VT_co]): # undocumented +class dict_items(ItemsView[_KT_co, _VT_co]): # undocumented + def __eq__(self, value: object, /) -> bool: ... if sys.version_info >= (3, 10): @property def mapping(self) -> MappingProxyType[_KT_co, _VT_co]: ... + +if sys.version_info >= (3, 12): + @runtime_checkable + class Buffer(Protocol): + @abstractmethod + def __buffer__(self, flags: int, /) -> memoryview: ... diff --git a/mypy/typeshed/stdlib/_compression.pyi b/mypy/typeshed/stdlib/_compression.pyi index 817f251586b2..a41a8142cc3a 100644 --- a/mypy/typeshed/stdlib/_compression.pyi +++ b/mypy/typeshed/stdlib/_compression.pyi @@ -6,9 +6,9 @@ from typing import Any, Protocol BUFFER_SIZE = DEFAULT_BUFFER_SIZE class _Reader(Protocol): - def read(self, __n: int) -> bytes: ... + def read(self, n: int, /) -> bytes: ... def seekable(self) -> bool: ... - def seek(self, __n: int) -> Any: ... + def seek(self, n: int, /) -> Any: ... class BaseStream(BufferedIOBase): ... @@ -17,7 +17,7 @@ class DecompressReader(RawIOBase): self, fp: _Reader, decomp_factory: Callable[..., object], - trailing_error: type[Exception] | tuple[type[Exception], ...] = ..., + trailing_error: type[Exception] | tuple[type[Exception], ...] = (), **decomp_args: Any, ) -> None: ... def readinto(self, b: WriteableBuffer) -> int: ... diff --git a/mypy/typeshed/stdlib/_csv.pyi b/mypy/typeshed/stdlib/_csv.pyi index 7e9b9e4e7a79..19f2dc9664b1 100644 --- a/mypy/typeshed/stdlib/_csv.pyi +++ b/mypy/typeshed/stdlib/_csv.pyi @@ -1,7 +1,8 @@ +import sys from _typeshed import SupportsWrite from collections.abc import Iterable, Iterator -from typing import Any -from typing_extensions import Final, Literal, TypeAlias +from typing import Any, Final, Literal +from typing_extensions import TypeAlias __version__: Final[str] @@ -9,6 +10,9 @@ QUOTE_ALL: Literal[1] QUOTE_MINIMAL: Literal[0] QUOTE_NONE: Literal[3] QUOTE_NONNUMERIC: Literal[2] +if sys.version_info >= (3, 12): + QUOTE_STRINGS: Literal[4] + QUOTE_NOTNULL: Literal[5] # Ideally this would be `QUOTE_ALL | QUOTE_MINIMAL | QUOTE_NONE | QUOTE_NONNUMERIC` # However, using literals in situations like these can cause false-positives (see #7258) @@ -43,42 +47,42 @@ class _writer: def writer( csvfile: SupportsWrite[str], - dialect: _DialectLike = ..., + dialect: _DialectLike = "excel", *, - delimiter: str = ..., - quotechar: str | None = ..., - escapechar: str | None = ..., - doublequote: bool = ..., - skipinitialspace: bool = ..., - lineterminator: str = ..., - quoting: _QuotingType = ..., - strict: bool = ..., + delimiter: str = ",", + quotechar: str | None = '"', + escapechar: str | None = None, + doublequote: bool = True, + skipinitialspace: bool = False, + lineterminator: str = "\r\n", + quoting: _QuotingType = 0, + strict: bool = False, ) -> _writer: ... def reader( csvfile: Iterable[str], - dialect: _DialectLike = ..., + dialect: _DialectLike = "excel", *, - delimiter: str = ..., - quotechar: str | None = ..., - escapechar: str | None = ..., - doublequote: bool = ..., - skipinitialspace: bool = ..., - lineterminator: str = ..., - quoting: _QuotingType = ..., - strict: bool = ..., + delimiter: str = ",", + quotechar: str | None = '"', + escapechar: str | None = None, + doublequote: bool = True, + skipinitialspace: bool = False, + lineterminator: str = "\r\n", + quoting: _QuotingType = 0, + strict: bool = False, ) -> _reader: ... def register_dialect( name: str, - dialect: Any = ..., + dialect: type[Dialect] = ..., *, - delimiter: str = ..., - quotechar: str | None = ..., - escapechar: str | None = ..., - doublequote: bool = ..., - skipinitialspace: bool = ..., - lineterminator: str = ..., - quoting: _QuotingType = ..., - strict: bool = ..., + delimiter: str = ",", + quotechar: str | None = '"', + escapechar: str | None = None, + doublequote: bool = True, + skipinitialspace: bool = False, + lineterminator: str = "\r\n", + quoting: _QuotingType = 0, + strict: bool = False, ) -> None: ... def unregister_dialect(name: str) -> None: ... def get_dialect(name: str) -> Dialect: ... diff --git a/mypy/typeshed/stdlib/_ctypes.pyi b/mypy/typeshed/stdlib/_ctypes.pyi index 0ad2fcb571b8..cf9cb81a44a3 100644 --- a/mypy/typeshed/stdlib/_ctypes.pyi +++ b/mypy/typeshed/stdlib/_ctypes.pyi @@ -1,6 +1,16 @@ import sys -from ctypes import _CArgObject, _PointerLike -from typing_extensions import TypeAlias +from _typeshed import ReadableBuffer, WriteableBuffer +from abc import abstractmethod +from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence +from ctypes import CDLL, ArgumentError as ArgumentError +from typing import Any, ClassVar, Generic, TypeVar, overload +from typing_extensions import Self, TypeAlias + +if sys.version_info >= (3, 9): + from types import GenericAlias + +_T = TypeVar("_T") +_CT = TypeVar("_CT", bound=_CData) FUNCFLAG_CDECL: int FUNCFLAG_PYTHONAPI: int @@ -12,6 +22,9 @@ RTLD_LOCAL: int if sys.version_info >= (3, 11): CTYPES_MAX_ARGCOUNT: int +if sys.version_info >= (3, 12): + SIZEOF_TIME_T: int + if sys.platform == "win32": # Description, Source, HelpFile, HelpContext, scode _COMError_Details: TypeAlias = tuple[str | None, str | None, str | None, int | None, int | None] @@ -27,3 +40,168 @@ if sys.platform == "win32": FUNCFLAG_HRESULT: int FUNCFLAG_STDCALL: int + + def FormatError(code: int = ...) -> str: ... + def get_last_error() -> int: ... + def set_last_error(value: int) -> int: ... + def LoadLibrary(name: str, load_flags: int = 0, /) -> int: ... + def FreeLibrary(handle: int, /) -> None: ... + +class _CDataMeta(type): + # By default mypy complains about the following two methods, because strictly speaking cls + # might not be a Type[_CT]. However this can never actually happen, because the only class that + # uses _CDataMeta as its metaclass is _CData. So it's safe to ignore the errors here. + def __mul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] + def __rmul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] + +class _CData(metaclass=_CDataMeta): + _b_base_: int + _b_needsfree_: bool + _objects: Mapping[Any, int] | None + # At runtime the following classmethods are available only on classes, not + # on instances. This can't be reflected properly in the type system: + # + # Structure.from_buffer(...) # valid at runtime + # Structure(...).from_buffer(...) # invalid at runtime + # + + @classmethod + def from_buffer(cls, source: WriteableBuffer, offset: int = ...) -> Self: ... + @classmethod + def from_buffer_copy(cls, source: ReadableBuffer, offset: int = ...) -> Self: ... + @classmethod + def from_address(cls, address: int) -> Self: ... + @classmethod + def from_param(cls, obj: Any) -> Self | _CArgObject: ... + @classmethod + def in_dll(cls, library: CDLL, name: str) -> Self: ... + def __buffer__(self, flags: int, /) -> memoryview: ... + def __release_buffer__(self, buffer: memoryview, /) -> None: ... + +class _SimpleCData(_CData, Generic[_T]): + value: _T + # The TypeVar can be unsolved here, + # but we can't use overloads without creating many, many mypy false-positive errors + def __init__(self, value: _T = ...) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] + +class _CanCastTo(_CData): ... +class _PointerLike(_CanCastTo): ... + +class _Pointer(_PointerLike, _CData, Generic[_CT]): + _type_: type[_CT] + contents: _CT + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, arg: _CT) -> None: ... + @overload + def __getitem__(self, key: int, /) -> Any: ... + @overload + def __getitem__(self, key: slice, /) -> list[Any]: ... + def __setitem__(self, key: int, value: Any, /) -> None: ... + +def POINTER(type: type[_CT]) -> type[_Pointer[_CT]]: ... +def pointer(arg: _CT, /) -> _Pointer[_CT]: ... + +class _CArgObject: ... + +def byref(obj: _CData, offset: int = ...) -> _CArgObject: ... + +_ECT: TypeAlias = Callable[[_CData | None, CFuncPtr, tuple[_CData, ...]], _CData] +_PF: TypeAlias = tuple[int] | tuple[int, str | None] | tuple[int, str | None, Any] + +class CFuncPtr(_PointerLike, _CData): + restype: type[_CData] | Callable[[int], Any] | None + argtypes: Sequence[type[_CData]] + errcheck: _ECT + # Abstract attribute that must be defined on subclasses + _flags_: ClassVar[int] + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, address: int, /) -> None: ... + @overload + def __init__(self, callable: Callable[..., Any], /) -> None: ... + @overload + def __init__(self, func_spec: tuple[str | int, CDLL], paramflags: tuple[_PF, ...] | None = ..., /) -> None: ... + if sys.platform == "win32": + @overload + def __init__( + self, vtbl_index: int, name: str, paramflags: tuple[_PF, ...] | None = ..., iid: _CData | None = ..., / + ) -> None: ... + + def __call__(self, *args: Any, **kwargs: Any) -> Any: ... + +_GetT = TypeVar("_GetT") +_SetT = TypeVar("_SetT") + +class _CField(Generic[_CT, _GetT, _SetT]): + offset: int + size: int + @overload + def __get__(self, instance: None, owner: type[Any] | None, /) -> Self: ... + @overload + def __get__(self, instance: Any, owner: type[Any] | None, /) -> _GetT: ... + def __set__(self, instance: Any, value: _SetT, /) -> None: ... + +class _StructUnionMeta(_CDataMeta): + _fields_: Sequence[tuple[str, type[_CData]] | tuple[str, type[_CData], int]] + _pack_: int + _anonymous_: Sequence[str] + def __getattr__(self, name: str) -> _CField[Any, Any, Any]: ... + +class _StructUnionBase(_CData, metaclass=_StructUnionMeta): + def __init__(self, *args: Any, **kw: Any) -> None: ... + def __getattr__(self, name: str) -> Any: ... + def __setattr__(self, name: str, value: Any) -> None: ... + +class Union(_StructUnionBase): ... +class Structure(_StructUnionBase): ... + +class Array(_CData, Generic[_CT]): + @property + @abstractmethod + def _length_(self) -> int: ... + @_length_.setter + def _length_(self, value: int) -> None: ... + @property + @abstractmethod + def _type_(self) -> type[_CT]: ... + @_type_.setter + def _type_(self, value: type[_CT]) -> None: ... + raw: bytes # Note: only available if _CT == c_char + value: Any # Note: bytes if _CT == c_char, str if _CT == c_wchar, unavailable otherwise + # TODO These methods cannot be annotated correctly at the moment. + # All of these "Any"s stand for the array's element type, but it's not possible to use _CT + # here, because of a special feature of ctypes. + # By default, when accessing an element of an Array[_CT], the returned object has type _CT. + # However, when _CT is a "simple type" like c_int, ctypes automatically "unboxes" the object + # and converts it to the corresponding Python primitive. For example, when accessing an element + # of an Array[c_int], a Python int object is returned, not a c_int. + # This behavior does *not* apply to subclasses of "simple types". + # If MyInt is a subclass of c_int, then accessing an element of an Array[MyInt] returns + # a MyInt, not an int. + # This special behavior is not easy to model in a stub, so for now all places where + # the array element type would belong are annotated with Any instead. + def __init__(self, *args: Any) -> None: ... + @overload + def __getitem__(self, key: int, /) -> Any: ... + @overload + def __getitem__(self, key: slice, /) -> list[Any]: ... + @overload + def __setitem__(self, key: int, value: Any, /) -> None: ... + @overload + def __setitem__(self, key: slice, value: Iterable[Any], /) -> None: ... + def __iter__(self) -> Iterator[Any]: ... + # Can't inherit from Sized because the metaclass conflict between + # Sized and _CData prevents using _CDataMeta. + def __len__(self) -> int: ... + if sys.version_info >= (3, 9): + def __class_getitem__(cls, item: Any) -> GenericAlias: ... + +def addressof(obj: _CData) -> int: ... +def alignment(obj_or_type: _CData | type[_CData]) -> int: ... +def get_errno() -> int: ... +def resize(obj: _CData, size: int) -> None: ... +def set_errno(value: int) -> int: ... +def sizeof(obj_or_type: _CData | type[_CData]) -> int: ... diff --git a/mypy/typeshed/stdlib/_curses.pyi b/mypy/typeshed/stdlib/_curses.pyi index 61881fc09199..6f3fbd807fcc 100644 --- a/mypy/typeshed/stdlib/_curses.pyi +++ b/mypy/typeshed/stdlib/_curses.pyi @@ -1,555 +1,566 @@ import sys from _typeshed import ReadOnlyBuffer, SupportsRead -from typing import IO, Any, NamedTuple, overload -from typing_extensions import TypeAlias, final +from typing import IO, Any, NamedTuple, final, overload +from typing_extensions import TypeAlias -if sys.platform != "win32": - # Handled by PyCurses_ConvertToChtype in _cursesmodule.c. - _ChType: TypeAlias = str | bytes | int +# NOTE: This module is ordinarily only available on Unix, but the windows-curses +# package makes it available on Windows as well with the same contents. - # ACS codes are only initialized after initscr is called - ACS_BBSS: int - ACS_BLOCK: int - ACS_BOARD: int - ACS_BSBS: int - ACS_BSSB: int - ACS_BSSS: int - ACS_BTEE: int - ACS_BULLET: int - ACS_CKBOARD: int - ACS_DARROW: int - ACS_DEGREE: int - ACS_DIAMOND: int - ACS_GEQUAL: int - ACS_HLINE: int - ACS_LANTERN: int - ACS_LARROW: int - ACS_LEQUAL: int - ACS_LLCORNER: int - ACS_LRCORNER: int - ACS_LTEE: int - ACS_NEQUAL: int - ACS_PI: int - ACS_PLMINUS: int - ACS_PLUS: int - ACS_RARROW: int - ACS_RTEE: int - ACS_S1: int - ACS_S3: int - ACS_S7: int - ACS_S9: int - ACS_SBBS: int - ACS_SBSB: int - ACS_SBSS: int - ACS_SSBB: int - ACS_SSBS: int - ACS_SSSB: int - ACS_SSSS: int - ACS_STERLING: int - ACS_TTEE: int - ACS_UARROW: int - ACS_ULCORNER: int - ACS_URCORNER: int - ACS_VLINE: int - ALL_MOUSE_EVENTS: int - A_ALTCHARSET: int - A_ATTRIBUTES: int - A_BLINK: int - A_BOLD: int - A_CHARTEXT: int - A_COLOR: int - A_DIM: int - A_HORIZONTAL: int - A_INVIS: int +# Handled by PyCurses_ConvertToChtype in _cursesmodule.c. +_ChType: TypeAlias = str | bytes | int + +# ACS codes are only initialized after initscr is called +ACS_BBSS: int +ACS_BLOCK: int +ACS_BOARD: int +ACS_BSBS: int +ACS_BSSB: int +ACS_BSSS: int +ACS_BTEE: int +ACS_BULLET: int +ACS_CKBOARD: int +ACS_DARROW: int +ACS_DEGREE: int +ACS_DIAMOND: int +ACS_GEQUAL: int +ACS_HLINE: int +ACS_LANTERN: int +ACS_LARROW: int +ACS_LEQUAL: int +ACS_LLCORNER: int +ACS_LRCORNER: int +ACS_LTEE: int +ACS_NEQUAL: int +ACS_PI: int +ACS_PLMINUS: int +ACS_PLUS: int +ACS_RARROW: int +ACS_RTEE: int +ACS_S1: int +ACS_S3: int +ACS_S7: int +ACS_S9: int +ACS_SBBS: int +ACS_SBSB: int +ACS_SBSS: int +ACS_SSBB: int +ACS_SSBS: int +ACS_SSSB: int +ACS_SSSS: int +ACS_STERLING: int +ACS_TTEE: int +ACS_UARROW: int +ACS_ULCORNER: int +ACS_URCORNER: int +ACS_VLINE: int +ALL_MOUSE_EVENTS: int +A_ALTCHARSET: int +A_ATTRIBUTES: int +A_BLINK: int +A_BOLD: int +A_CHARTEXT: int +A_COLOR: int +A_DIM: int +A_HORIZONTAL: int +A_INVIS: int +if sys.platform != "darwin": A_ITALIC: int - A_LEFT: int - A_LOW: int - A_NORMAL: int - A_PROTECT: int - A_REVERSE: int - A_RIGHT: int - A_STANDOUT: int - A_TOP: int - A_UNDERLINE: int - A_VERTICAL: int - BUTTON1_CLICKED: int - BUTTON1_DOUBLE_CLICKED: int - BUTTON1_PRESSED: int - BUTTON1_RELEASED: int - BUTTON1_TRIPLE_CLICKED: int - BUTTON2_CLICKED: int - BUTTON2_DOUBLE_CLICKED: int - BUTTON2_PRESSED: int - BUTTON2_RELEASED: int - BUTTON2_TRIPLE_CLICKED: int - BUTTON3_CLICKED: int - BUTTON3_DOUBLE_CLICKED: int - BUTTON3_PRESSED: int - BUTTON3_RELEASED: int - BUTTON3_TRIPLE_CLICKED: int - BUTTON4_CLICKED: int - BUTTON4_DOUBLE_CLICKED: int - BUTTON4_PRESSED: int - BUTTON4_RELEASED: int - BUTTON4_TRIPLE_CLICKED: int - # Darwin ncurses doesn't provide BUTTON5_* constants - if sys.version_info >= (3, 10) and sys.platform != "darwin": - BUTTON5_PRESSED: int - BUTTON5_RELEASED: int - BUTTON5_CLICKED: int - BUTTON5_DOUBLE_CLICKED: int - BUTTON5_TRIPLE_CLICKED: int - BUTTON_ALT: int - BUTTON_CTRL: int - BUTTON_SHIFT: int - COLOR_BLACK: int - COLOR_BLUE: int - COLOR_CYAN: int - COLOR_GREEN: int - COLOR_MAGENTA: int - COLOR_RED: int - COLOR_WHITE: int - COLOR_YELLOW: int - ERR: int - KEY_A1: int - KEY_A3: int - KEY_B2: int - KEY_BACKSPACE: int - KEY_BEG: int - KEY_BREAK: int - KEY_BTAB: int - KEY_C1: int - KEY_C3: int - KEY_CANCEL: int - KEY_CATAB: int - KEY_CLEAR: int - KEY_CLOSE: int - KEY_COMMAND: int - KEY_COPY: int - KEY_CREATE: int - KEY_CTAB: int - KEY_DC: int - KEY_DL: int - KEY_DOWN: int - KEY_EIC: int - KEY_END: int - KEY_ENTER: int - KEY_EOL: int - KEY_EOS: int - KEY_EXIT: int - KEY_F0: int - KEY_F1: int - KEY_F10: int - KEY_F11: int - KEY_F12: int - KEY_F13: int - KEY_F14: int - KEY_F15: int - KEY_F16: int - KEY_F17: int - KEY_F18: int - KEY_F19: int - KEY_F2: int - KEY_F20: int - KEY_F21: int - KEY_F22: int - KEY_F23: int - KEY_F24: int - KEY_F25: int - KEY_F26: int - KEY_F27: int - KEY_F28: int - KEY_F29: int - KEY_F3: int - KEY_F30: int - KEY_F31: int - KEY_F32: int - KEY_F33: int - KEY_F34: int - KEY_F35: int - KEY_F36: int - KEY_F37: int - KEY_F38: int - KEY_F39: int - KEY_F4: int - KEY_F40: int - KEY_F41: int - KEY_F42: int - KEY_F43: int - KEY_F44: int - KEY_F45: int - KEY_F46: int - KEY_F47: int - KEY_F48: int - KEY_F49: int - KEY_F5: int - KEY_F50: int - KEY_F51: int - KEY_F52: int - KEY_F53: int - KEY_F54: int - KEY_F55: int - KEY_F56: int - KEY_F57: int - KEY_F58: int - KEY_F59: int - KEY_F6: int - KEY_F60: int - KEY_F61: int - KEY_F62: int - KEY_F63: int - KEY_F7: int - KEY_F8: int - KEY_F9: int - KEY_FIND: int - KEY_HELP: int - KEY_HOME: int - KEY_IC: int - KEY_IL: int - KEY_LEFT: int - KEY_LL: int - KEY_MARK: int - KEY_MAX: int - KEY_MESSAGE: int - KEY_MIN: int - KEY_MOUSE: int - KEY_MOVE: int - KEY_NEXT: int - KEY_NPAGE: int - KEY_OPEN: int - KEY_OPTIONS: int - KEY_PPAGE: int - KEY_PREVIOUS: int - KEY_PRINT: int - KEY_REDO: int - KEY_REFERENCE: int - KEY_REFRESH: int - KEY_REPLACE: int - KEY_RESET: int - KEY_RESIZE: int - KEY_RESTART: int - KEY_RESUME: int - KEY_RIGHT: int - KEY_SAVE: int - KEY_SBEG: int - KEY_SCANCEL: int - KEY_SCOMMAND: int - KEY_SCOPY: int - KEY_SCREATE: int - KEY_SDC: int - KEY_SDL: int - KEY_SELECT: int - KEY_SEND: int - KEY_SEOL: int - KEY_SEXIT: int - KEY_SF: int - KEY_SFIND: int - KEY_SHELP: int - KEY_SHOME: int - KEY_SIC: int - KEY_SLEFT: int - KEY_SMESSAGE: int - KEY_SMOVE: int - KEY_SNEXT: int - KEY_SOPTIONS: int - KEY_SPREVIOUS: int - KEY_SPRINT: int - KEY_SR: int - KEY_SREDO: int - KEY_SREPLACE: int - KEY_SRESET: int - KEY_SRIGHT: int - KEY_SRSUME: int - KEY_SSAVE: int - KEY_SSUSPEND: int - KEY_STAB: int - KEY_SUNDO: int - KEY_SUSPEND: int - KEY_UNDO: int - KEY_UP: int - OK: int - REPORT_MOUSE_POSITION: int - _C_API: Any - version: bytes - def baudrate() -> int: ... - def beep() -> None: ... - def can_change_color() -> bool: ... - def cbreak(__flag: bool = True) -> None: ... - def color_content(__color_number: int) -> tuple[int, int, int]: ... - # Changed in Python 3.8.8 and 3.9.2 - if sys.version_info >= (3, 8): - def color_pair(pair_number: int) -> int: ... - else: - def color_pair(__color_number: int) -> int: ... +A_LEFT: int +A_LOW: int +A_NORMAL: int +A_PROTECT: int +A_REVERSE: int +A_RIGHT: int +A_STANDOUT: int +A_TOP: int +A_UNDERLINE: int +A_VERTICAL: int +BUTTON1_CLICKED: int +BUTTON1_DOUBLE_CLICKED: int +BUTTON1_PRESSED: int +BUTTON1_RELEASED: int +BUTTON1_TRIPLE_CLICKED: int +BUTTON2_CLICKED: int +BUTTON2_DOUBLE_CLICKED: int +BUTTON2_PRESSED: int +BUTTON2_RELEASED: int +BUTTON2_TRIPLE_CLICKED: int +BUTTON3_CLICKED: int +BUTTON3_DOUBLE_CLICKED: int +BUTTON3_PRESSED: int +BUTTON3_RELEASED: int +BUTTON3_TRIPLE_CLICKED: int +BUTTON4_CLICKED: int +BUTTON4_DOUBLE_CLICKED: int +BUTTON4_PRESSED: int +BUTTON4_RELEASED: int +BUTTON4_TRIPLE_CLICKED: int +# Darwin ncurses doesn't provide BUTTON5_* constants +if sys.version_info >= (3, 10) and sys.platform != "darwin": + BUTTON5_PRESSED: int + BUTTON5_RELEASED: int + BUTTON5_CLICKED: int + BUTTON5_DOUBLE_CLICKED: int + BUTTON5_TRIPLE_CLICKED: int +BUTTON_ALT: int +BUTTON_CTRL: int +BUTTON_SHIFT: int +COLOR_BLACK: int +COLOR_BLUE: int +COLOR_CYAN: int +COLOR_GREEN: int +COLOR_MAGENTA: int +COLOR_RED: int +COLOR_WHITE: int +COLOR_YELLOW: int +ERR: int +KEY_A1: int +KEY_A3: int +KEY_B2: int +KEY_BACKSPACE: int +KEY_BEG: int +KEY_BREAK: int +KEY_BTAB: int +KEY_C1: int +KEY_C3: int +KEY_CANCEL: int +KEY_CATAB: int +KEY_CLEAR: int +KEY_CLOSE: int +KEY_COMMAND: int +KEY_COPY: int +KEY_CREATE: int +KEY_CTAB: int +KEY_DC: int +KEY_DL: int +KEY_DOWN: int +KEY_EIC: int +KEY_END: int +KEY_ENTER: int +KEY_EOL: int +KEY_EOS: int +KEY_EXIT: int +KEY_F0: int +KEY_F1: int +KEY_F10: int +KEY_F11: int +KEY_F12: int +KEY_F13: int +KEY_F14: int +KEY_F15: int +KEY_F16: int +KEY_F17: int +KEY_F18: int +KEY_F19: int +KEY_F2: int +KEY_F20: int +KEY_F21: int +KEY_F22: int +KEY_F23: int +KEY_F24: int +KEY_F25: int +KEY_F26: int +KEY_F27: int +KEY_F28: int +KEY_F29: int +KEY_F3: int +KEY_F30: int +KEY_F31: int +KEY_F32: int +KEY_F33: int +KEY_F34: int +KEY_F35: int +KEY_F36: int +KEY_F37: int +KEY_F38: int +KEY_F39: int +KEY_F4: int +KEY_F40: int +KEY_F41: int +KEY_F42: int +KEY_F43: int +KEY_F44: int +KEY_F45: int +KEY_F46: int +KEY_F47: int +KEY_F48: int +KEY_F49: int +KEY_F5: int +KEY_F50: int +KEY_F51: int +KEY_F52: int +KEY_F53: int +KEY_F54: int +KEY_F55: int +KEY_F56: int +KEY_F57: int +KEY_F58: int +KEY_F59: int +KEY_F6: int +KEY_F60: int +KEY_F61: int +KEY_F62: int +KEY_F63: int +KEY_F7: int +KEY_F8: int +KEY_F9: int +KEY_FIND: int +KEY_HELP: int +KEY_HOME: int +KEY_IC: int +KEY_IL: int +KEY_LEFT: int +KEY_LL: int +KEY_MARK: int +KEY_MAX: int +KEY_MESSAGE: int +KEY_MIN: int +KEY_MOUSE: int +KEY_MOVE: int +KEY_NEXT: int +KEY_NPAGE: int +KEY_OPEN: int +KEY_OPTIONS: int +KEY_PPAGE: int +KEY_PREVIOUS: int +KEY_PRINT: int +KEY_REDO: int +KEY_REFERENCE: int +KEY_REFRESH: int +KEY_REPLACE: int +KEY_RESET: int +KEY_RESIZE: int +KEY_RESTART: int +KEY_RESUME: int +KEY_RIGHT: int +KEY_SAVE: int +KEY_SBEG: int +KEY_SCANCEL: int +KEY_SCOMMAND: int +KEY_SCOPY: int +KEY_SCREATE: int +KEY_SDC: int +KEY_SDL: int +KEY_SELECT: int +KEY_SEND: int +KEY_SEOL: int +KEY_SEXIT: int +KEY_SF: int +KEY_SFIND: int +KEY_SHELP: int +KEY_SHOME: int +KEY_SIC: int +KEY_SLEFT: int +KEY_SMESSAGE: int +KEY_SMOVE: int +KEY_SNEXT: int +KEY_SOPTIONS: int +KEY_SPREVIOUS: int +KEY_SPRINT: int +KEY_SR: int +KEY_SREDO: int +KEY_SREPLACE: int +KEY_SRESET: int +KEY_SRIGHT: int +KEY_SRSUME: int +KEY_SSAVE: int +KEY_SSUSPEND: int +KEY_STAB: int +KEY_SUNDO: int +KEY_SUSPEND: int +KEY_UNDO: int +KEY_UP: int +OK: int +REPORT_MOUSE_POSITION: int +_C_API: Any +version: bytes - def curs_set(__visibility: int) -> int: ... - def def_prog_mode() -> None: ... - def def_shell_mode() -> None: ... - def delay_output(__ms: int) -> None: ... - def doupdate() -> None: ... - def echo(__flag: bool = True) -> None: ... - def endwin() -> None: ... - def erasechar() -> bytes: ... - def filter() -> None: ... - def flash() -> None: ... - def flushinp() -> None: ... - if sys.version_info >= (3, 9): - def get_escdelay() -> int: ... - def get_tabsize() -> int: ... +def baudrate() -> int: ... +def beep() -> None: ... +def can_change_color() -> bool: ... +def cbreak(flag: bool = True, /) -> None: ... +def color_content(color_number: int, /) -> tuple[int, int, int]: ... +def color_pair(pair_number: int, /) -> int: ... +def curs_set(visibility: int, /) -> int: ... +def def_prog_mode() -> None: ... +def def_shell_mode() -> None: ... +def delay_output(ms: int, /) -> None: ... +def doupdate() -> None: ... +def echo(flag: bool = True, /) -> None: ... +def endwin() -> None: ... +def erasechar() -> bytes: ... +def filter() -> None: ... +def flash() -> None: ... +def flushinp() -> None: ... - def getmouse() -> tuple[int, int, int, int, int]: ... - def getsyx() -> tuple[int, int]: ... - def getwin(__file: SupportsRead[bytes]) -> _CursesWindow: ... - def halfdelay(__tenths: int) -> None: ... - def has_colors() -> bool: ... - if sys.version_info >= (3, 10): - def has_extended_color_support() -> bool: ... +if sys.version_info >= (3, 9): + def get_escdelay() -> int: ... + def get_tabsize() -> int: ... - def has_ic() -> bool: ... - def has_il() -> bool: ... - def has_key(__key: int) -> bool: ... - def init_color(__color_number: int, __r: int, __g: int, __b: int) -> None: ... - def init_pair(__pair_number: int, __fg: int, __bg: int) -> None: ... - def initscr() -> _CursesWindow: ... - def intrflush(__flag: bool) -> None: ... - def is_term_resized(__nlines: int, __ncols: int) -> bool: ... - def isendwin() -> bool: ... - def keyname(__key: int) -> bytes: ... - def killchar() -> bytes: ... - def longname() -> bytes: ... - def meta(__yes: bool) -> None: ... - def mouseinterval(__interval: int) -> None: ... - def mousemask(__newmask: int) -> tuple[int, int]: ... - def napms(__ms: int) -> int: ... - def newpad(__nlines: int, __ncols: int) -> _CursesWindow: ... - def newwin(__nlines: int, __ncols: int, __begin_y: int = ..., __begin_x: int = ...) -> _CursesWindow: ... - def nl(__flag: bool = True) -> None: ... - def nocbreak() -> None: ... - def noecho() -> None: ... - def nonl() -> None: ... - def noqiflush() -> None: ... - def noraw() -> None: ... - def pair_content(__pair_number: int) -> tuple[int, int]: ... - def pair_number(__attr: int) -> int: ... - def putp(__string: ReadOnlyBuffer) -> None: ... - def qiflush(__flag: bool = True) -> None: ... - def raw(__flag: bool = True) -> None: ... - def reset_prog_mode() -> None: ... - def reset_shell_mode() -> None: ... - def resetty() -> None: ... - def resize_term(__nlines: int, __ncols: int) -> None: ... - def resizeterm(__nlines: int, __ncols: int) -> None: ... - def savetty() -> None: ... - if sys.version_info >= (3, 9): - def set_escdelay(__ms: int) -> None: ... - def set_tabsize(__size: int) -> None: ... +def getmouse() -> tuple[int, int, int, int, int]: ... +def getsyx() -> tuple[int, int]: ... +def getwin(file: SupportsRead[bytes], /) -> _CursesWindow: ... +def halfdelay(tenths: int, /) -> None: ... +def has_colors() -> bool: ... - def setsyx(__y: int, __x: int) -> None: ... - def setupterm(term: str | None = None, fd: int = -1) -> None: ... - def start_color() -> None: ... - def termattrs() -> int: ... - def termname() -> bytes: ... - def tigetflag(__capname: str) -> int: ... - def tigetnum(__capname: str) -> int: ... - def tigetstr(__capname: str) -> bytes | None: ... - def tparm( - __str: ReadOnlyBuffer, - __i1: int = 0, - __i2: int = 0, - __i3: int = 0, - __i4: int = 0, - __i5: int = 0, - __i6: int = 0, - __i7: int = 0, - __i8: int = 0, - __i9: int = 0, - ) -> bytes: ... - def typeahead(__fd: int) -> None: ... - def unctrl(__ch: _ChType) -> bytes: ... - def unget_wch(__ch: int | str) -> None: ... - def ungetch(__ch: _ChType) -> None: ... - def ungetmouse(__id: int, __x: int, __y: int, __z: int, __bstate: int) -> None: ... - def update_lines_cols() -> None: ... - def use_default_colors() -> None: ... - def use_env(__flag: bool) -> None: ... +if sys.version_info >= (3, 10): + def has_extended_color_support() -> bool: ... - class error(Exception): ... +def has_ic() -> bool: ... +def has_il() -> bool: ... +def has_key(key: int, /) -> bool: ... +def init_color(color_number: int, r: int, g: int, b: int, /) -> None: ... +def init_pair(pair_number: int, fg: int, bg: int, /) -> None: ... +def initscr() -> _CursesWindow: ... +def intrflush(flag: bool, /) -> None: ... +def is_term_resized(nlines: int, ncols: int, /) -> bool: ... +def isendwin() -> bool: ... +def keyname(key: int, /) -> bytes: ... +def killchar() -> bytes: ... +def longname() -> bytes: ... +def meta(yes: bool, /) -> None: ... +def mouseinterval(interval: int, /) -> None: ... +def mousemask(newmask: int, /) -> tuple[int, int]: ... +def napms(ms: int, /) -> int: ... +def newpad(nlines: int, ncols: int, /) -> _CursesWindow: ... +def newwin(nlines: int, ncols: int, begin_y: int = ..., begin_x: int = ..., /) -> _CursesWindow: ... +def nl(flag: bool = True, /) -> None: ... +def nocbreak() -> None: ... +def noecho() -> None: ... +def nonl() -> None: ... +def noqiflush() -> None: ... +def noraw() -> None: ... +def pair_content(pair_number: int, /) -> tuple[int, int]: ... +def pair_number(attr: int, /) -> int: ... +def putp(string: ReadOnlyBuffer, /) -> None: ... +def qiflush(flag: bool = True, /) -> None: ... +def raw(flag: bool = True, /) -> None: ... +def reset_prog_mode() -> None: ... +def reset_shell_mode() -> None: ... +def resetty() -> None: ... +def resize_term(nlines: int, ncols: int, /) -> None: ... +def resizeterm(nlines: int, ncols: int, /) -> None: ... +def savetty() -> None: ... - @final - class _CursesWindow: - encoding: str - @overload - def addch(self, ch: _ChType, attr: int = ...) -> None: ... - @overload - def addch(self, y: int, x: int, ch: _ChType, attr: int = ...) -> None: ... - @overload - def addnstr(self, str: str, n: int, attr: int = ...) -> None: ... - @overload - def addnstr(self, y: int, x: int, str: str, n: int, attr: int = ...) -> None: ... - @overload - def addstr(self, str: str, attr: int = ...) -> None: ... - @overload - def addstr(self, y: int, x: int, str: str, attr: int = ...) -> None: ... - def attroff(self, __attr: int) -> None: ... - def attron(self, __attr: int) -> None: ... - def attrset(self, __attr: int) -> None: ... - def bkgd(self, __ch: _ChType, __attr: int = ...) -> None: ... - def bkgdset(self, __ch: _ChType, __attr: int = ...) -> None: ... - def border( - self, - ls: _ChType = ..., - rs: _ChType = ..., - ts: _ChType = ..., - bs: _ChType = ..., - tl: _ChType = ..., - tr: _ChType = ..., - bl: _ChType = ..., - br: _ChType = ..., - ) -> None: ... - @overload - def box(self) -> None: ... - @overload - def box(self, vertch: _ChType = ..., horch: _ChType = ...) -> None: ... - @overload - def chgat(self, attr: int) -> None: ... - @overload - def chgat(self, num: int, attr: int) -> None: ... - @overload - def chgat(self, y: int, x: int, attr: int) -> None: ... - @overload - def chgat(self, y: int, x: int, num: int, attr: int) -> None: ... - def clear(self) -> None: ... - def clearok(self, yes: int) -> None: ... - def clrtobot(self) -> None: ... - def clrtoeol(self) -> None: ... - def cursyncup(self) -> None: ... - @overload - def delch(self) -> None: ... - @overload - def delch(self, y: int, x: int) -> None: ... - def deleteln(self) -> None: ... - @overload - def derwin(self, begin_y: int, begin_x: int) -> _CursesWindow: ... - @overload - def derwin(self, nlines: int, ncols: int, begin_y: int, begin_x: int) -> _CursesWindow: ... - def echochar(self, __ch: _ChType, __attr: int = ...) -> None: ... - def enclose(self, __y: int, __x: int) -> bool: ... - def erase(self) -> None: ... - def getbegyx(self) -> tuple[int, int]: ... - def getbkgd(self) -> tuple[int, int]: ... - @overload - def getch(self) -> int: ... - @overload - def getch(self, y: int, x: int) -> int: ... +if sys.version_info >= (3, 9): + def set_escdelay(ms: int, /) -> None: ... + def set_tabsize(size: int, /) -> None: ... + +def setsyx(y: int, x: int, /) -> None: ... +def setupterm(term: str | None = None, fd: int = -1) -> None: ... +def start_color() -> None: ... +def termattrs() -> int: ... +def termname() -> bytes: ... +def tigetflag(capname: str, /) -> int: ... +def tigetnum(capname: str, /) -> int: ... +def tigetstr(capname: str, /) -> bytes | None: ... +def tparm( + str: ReadOnlyBuffer, + i1: int = 0, + i2: int = 0, + i3: int = 0, + i4: int = 0, + i5: int = 0, + i6: int = 0, + i7: int = 0, + i8: int = 0, + i9: int = 0, + /, +) -> bytes: ... +def typeahead(fd: int, /) -> None: ... +def unctrl(ch: _ChType, /) -> bytes: ... + +if sys.version_info < (3, 12) or sys.platform != "darwin": + # The support for macos was dropped in 3.12 + def unget_wch(ch: int | str, /) -> None: ... + +def ungetch(ch: _ChType, /) -> None: ... +def ungetmouse(id: int, x: int, y: int, z: int, bstate: int, /) -> None: ... +def update_lines_cols() -> None: ... +def use_default_colors() -> None: ... +def use_env(flag: bool, /) -> None: ... + +class error(Exception): ... + +@final +class _CursesWindow: + encoding: str + @overload + def addch(self, ch: _ChType, attr: int = ...) -> None: ... + @overload + def addch(self, y: int, x: int, ch: _ChType, attr: int = ...) -> None: ... + @overload + def addnstr(self, str: str, n: int, attr: int = ...) -> None: ... + @overload + def addnstr(self, y: int, x: int, str: str, n: int, attr: int = ...) -> None: ... + @overload + def addstr(self, str: str, attr: int = ...) -> None: ... + @overload + def addstr(self, y: int, x: int, str: str, attr: int = ...) -> None: ... + def attroff(self, attr: int, /) -> None: ... + def attron(self, attr: int, /) -> None: ... + def attrset(self, attr: int, /) -> None: ... + def bkgd(self, ch: _ChType, attr: int = ..., /) -> None: ... + def bkgdset(self, ch: _ChType, attr: int = ..., /) -> None: ... + def border( + self, + ls: _ChType = ..., + rs: _ChType = ..., + ts: _ChType = ..., + bs: _ChType = ..., + tl: _ChType = ..., + tr: _ChType = ..., + bl: _ChType = ..., + br: _ChType = ..., + ) -> None: ... + @overload + def box(self) -> None: ... + @overload + def box(self, vertch: _ChType = ..., horch: _ChType = ...) -> None: ... + @overload + def chgat(self, attr: int) -> None: ... + @overload + def chgat(self, num: int, attr: int) -> None: ... + @overload + def chgat(self, y: int, x: int, attr: int) -> None: ... + @overload + def chgat(self, y: int, x: int, num: int, attr: int) -> None: ... + def clear(self) -> None: ... + def clearok(self, yes: int) -> None: ... + def clrtobot(self) -> None: ... + def clrtoeol(self) -> None: ... + def cursyncup(self) -> None: ... + @overload + def delch(self) -> None: ... + @overload + def delch(self, y: int, x: int) -> None: ... + def deleteln(self) -> None: ... + @overload + def derwin(self, begin_y: int, begin_x: int) -> _CursesWindow: ... + @overload + def derwin(self, nlines: int, ncols: int, begin_y: int, begin_x: int) -> _CursesWindow: ... + def echochar(self, ch: _ChType, attr: int = ..., /) -> None: ... + def enclose(self, y: int, x: int, /) -> bool: ... + def erase(self) -> None: ... + def getbegyx(self) -> tuple[int, int]: ... + def getbkgd(self) -> tuple[int, int]: ... + @overload + def getch(self) -> int: ... + @overload + def getch(self, y: int, x: int) -> int: ... + if sys.version_info < (3, 12) or sys.platform != "darwin": + # The support for macos was dropped in 3.12 @overload def get_wch(self) -> int | str: ... @overload def get_wch(self, y: int, x: int) -> int | str: ... - @overload - def getkey(self) -> str: ... - @overload - def getkey(self, y: int, x: int) -> str: ... - def getmaxyx(self) -> tuple[int, int]: ... - def getparyx(self) -> tuple[int, int]: ... - @overload - def getstr(self) -> bytes: ... - @overload - def getstr(self, n: int) -> bytes: ... - @overload - def getstr(self, y: int, x: int) -> bytes: ... - @overload - def getstr(self, y: int, x: int, n: int) -> bytes: ... - def getyx(self) -> tuple[int, int]: ... - @overload - def hline(self, ch: _ChType, n: int) -> None: ... - @overload - def hline(self, y: int, x: int, ch: _ChType, n: int) -> None: ... - def idcok(self, flag: bool) -> None: ... - def idlok(self, yes: bool) -> None: ... - def immedok(self, flag: bool) -> None: ... - @overload - def inch(self) -> int: ... - @overload - def inch(self, y: int, x: int) -> int: ... - @overload - def insch(self, ch: _ChType, attr: int = ...) -> None: ... - @overload - def insch(self, y: int, x: int, ch: _ChType, attr: int = ...) -> None: ... - def insdelln(self, nlines: int) -> None: ... - def insertln(self) -> None: ... - @overload - def insnstr(self, str: str, n: int, attr: int = ...) -> None: ... - @overload - def insnstr(self, y: int, x: int, str: str, n: int, attr: int = ...) -> None: ... - @overload - def insstr(self, str: str, attr: int = ...) -> None: ... - @overload - def insstr(self, y: int, x: int, str: str, attr: int = ...) -> None: ... - @overload - def instr(self, n: int = ...) -> bytes: ... - @overload - def instr(self, y: int, x: int, n: int = ...) -> bytes: ... - def is_linetouched(self, __line: int) -> bool: ... - def is_wintouched(self) -> bool: ... - def keypad(self, yes: bool) -> None: ... - def leaveok(self, yes: bool) -> None: ... - def move(self, new_y: int, new_x: int) -> None: ... - def mvderwin(self, y: int, x: int) -> None: ... - def mvwin(self, new_y: int, new_x: int) -> None: ... - def nodelay(self, yes: bool) -> None: ... - def notimeout(self, yes: bool) -> None: ... - @overload - def noutrefresh(self) -> None: ... - @overload - def noutrefresh(self, pminrow: int, pmincol: int, sminrow: int, smincol: int, smaxrow: int, smaxcol: int) -> None: ... - @overload - def overlay(self, destwin: _CursesWindow) -> None: ... - @overload - def overlay( - self, destwin: _CursesWindow, sminrow: int, smincol: int, dminrow: int, dmincol: int, dmaxrow: int, dmaxcol: int - ) -> None: ... - @overload - def overwrite(self, destwin: _CursesWindow) -> None: ... - @overload - def overwrite( - self, destwin: _CursesWindow, sminrow: int, smincol: int, dminrow: int, dmincol: int, dmaxrow: int, dmaxcol: int - ) -> None: ... - def putwin(self, __file: IO[Any]) -> None: ... - def redrawln(self, __beg: int, __num: int) -> None: ... - def redrawwin(self) -> None: ... - @overload - def refresh(self) -> None: ... - @overload - def refresh(self, pminrow: int, pmincol: int, sminrow: int, smincol: int, smaxrow: int, smaxcol: int) -> None: ... - def resize(self, nlines: int, ncols: int) -> None: ... - def scroll(self, lines: int = ...) -> None: ... - def scrollok(self, flag: bool) -> None: ... - def setscrreg(self, __top: int, __bottom: int) -> None: ... - def standend(self) -> None: ... - def standout(self) -> None: ... - @overload - def subpad(self, begin_y: int, begin_x: int) -> _CursesWindow: ... - @overload - def subpad(self, nlines: int, ncols: int, begin_y: int, begin_x: int) -> _CursesWindow: ... - @overload - def subwin(self, begin_y: int, begin_x: int) -> _CursesWindow: ... - @overload - def subwin(self, nlines: int, ncols: int, begin_y: int, begin_x: int) -> _CursesWindow: ... - def syncdown(self) -> None: ... - def syncok(self, flag: bool) -> None: ... - def syncup(self) -> None: ... - def timeout(self, delay: int) -> None: ... - def touchline(self, start: int, count: int, changed: bool = ...) -> None: ... - def touchwin(self) -> None: ... - def untouchwin(self) -> None: ... - @overload - def vline(self, ch: _ChType, n: int) -> None: ... - @overload - def vline(self, y: int, x: int, ch: _ChType, n: int) -> None: ... - if sys.version_info >= (3, 8): - class _ncurses_version(NamedTuple): - major: int - minor: int - patch: int - ncurses_version: _ncurses_version - window = _CursesWindow # undocumented + + @overload + def getkey(self) -> str: ... + @overload + def getkey(self, y: int, x: int) -> str: ... + def getmaxyx(self) -> tuple[int, int]: ... + def getparyx(self) -> tuple[int, int]: ... + @overload + def getstr(self) -> bytes: ... + @overload + def getstr(self, n: int) -> bytes: ... + @overload + def getstr(self, y: int, x: int) -> bytes: ... + @overload + def getstr(self, y: int, x: int, n: int) -> bytes: ... + def getyx(self) -> tuple[int, int]: ... + @overload + def hline(self, ch: _ChType, n: int) -> None: ... + @overload + def hline(self, y: int, x: int, ch: _ChType, n: int) -> None: ... + def idcok(self, flag: bool) -> None: ... + def idlok(self, yes: bool) -> None: ... + def immedok(self, flag: bool) -> None: ... + @overload + def inch(self) -> int: ... + @overload + def inch(self, y: int, x: int) -> int: ... + @overload + def insch(self, ch: _ChType, attr: int = ...) -> None: ... + @overload + def insch(self, y: int, x: int, ch: _ChType, attr: int = ...) -> None: ... + def insdelln(self, nlines: int) -> None: ... + def insertln(self) -> None: ... + @overload + def insnstr(self, str: str, n: int, attr: int = ...) -> None: ... + @overload + def insnstr(self, y: int, x: int, str: str, n: int, attr: int = ...) -> None: ... + @overload + def insstr(self, str: str, attr: int = ...) -> None: ... + @overload + def insstr(self, y: int, x: int, str: str, attr: int = ...) -> None: ... + @overload + def instr(self, n: int = ...) -> bytes: ... + @overload + def instr(self, y: int, x: int, n: int = ...) -> bytes: ... + def is_linetouched(self, line: int, /) -> bool: ... + def is_wintouched(self) -> bool: ... + def keypad(self, yes: bool) -> None: ... + def leaveok(self, yes: bool) -> None: ... + def move(self, new_y: int, new_x: int) -> None: ... + def mvderwin(self, y: int, x: int) -> None: ... + def mvwin(self, new_y: int, new_x: int) -> None: ... + def nodelay(self, yes: bool) -> None: ... + def notimeout(self, yes: bool) -> None: ... + @overload + def noutrefresh(self) -> None: ... + @overload + def noutrefresh(self, pminrow: int, pmincol: int, sminrow: int, smincol: int, smaxrow: int, smaxcol: int) -> None: ... + @overload + def overlay(self, destwin: _CursesWindow) -> None: ... + @overload + def overlay( + self, destwin: _CursesWindow, sminrow: int, smincol: int, dminrow: int, dmincol: int, dmaxrow: int, dmaxcol: int + ) -> None: ... + @overload + def overwrite(self, destwin: _CursesWindow) -> None: ... + @overload + def overwrite( + self, destwin: _CursesWindow, sminrow: int, smincol: int, dminrow: int, dmincol: int, dmaxrow: int, dmaxcol: int + ) -> None: ... + def putwin(self, file: IO[Any], /) -> None: ... + def redrawln(self, beg: int, num: int, /) -> None: ... + def redrawwin(self) -> None: ... + @overload + def refresh(self) -> None: ... + @overload + def refresh(self, pminrow: int, pmincol: int, sminrow: int, smincol: int, smaxrow: int, smaxcol: int) -> None: ... + def resize(self, nlines: int, ncols: int) -> None: ... + def scroll(self, lines: int = ...) -> None: ... + def scrollok(self, flag: bool) -> None: ... + def setscrreg(self, top: int, bottom: int, /) -> None: ... + def standend(self) -> None: ... + def standout(self) -> None: ... + @overload + def subpad(self, begin_y: int, begin_x: int) -> _CursesWindow: ... + @overload + def subpad(self, nlines: int, ncols: int, begin_y: int, begin_x: int) -> _CursesWindow: ... + @overload + def subwin(self, begin_y: int, begin_x: int) -> _CursesWindow: ... + @overload + def subwin(self, nlines: int, ncols: int, begin_y: int, begin_x: int) -> _CursesWindow: ... + def syncdown(self) -> None: ... + def syncok(self, flag: bool) -> None: ... + def syncup(self) -> None: ... + def timeout(self, delay: int) -> None: ... + def touchline(self, start: int, count: int, changed: bool = ...) -> None: ... + def touchwin(self) -> None: ... + def untouchwin(self) -> None: ... + @overload + def vline(self, ch: _ChType, n: int) -> None: ... + @overload + def vline(self, y: int, x: int, ch: _ChType, n: int) -> None: ... + +class _ncurses_version(NamedTuple): + major: int + minor: int + patch: int + +ncurses_version: _ncurses_version +window = _CursesWindow # undocumented diff --git a/mypy/typeshed/stdlib/_decimal.pyi b/mypy/typeshed/stdlib/_decimal.pyi index b8208fe180a1..90d16215c280 100644 --- a/mypy/typeshed/stdlib/_decimal.pyi +++ b/mypy/typeshed/stdlib/_decimal.pyi @@ -2,8 +2,8 @@ import numbers import sys from collections.abc import Container, Sequence from types import TracebackType -from typing import Any, ClassVar, NamedTuple, overload -from typing_extensions import Final, Literal, Self, TypeAlias +from typing import Any, ClassVar, Final, Literal, NamedTuple, overload +from typing_extensions import Self, TypeAlias _Decimal: TypeAlias = Decimal | int _DecimalNew: TypeAlias = Decimal | float | str | tuple[int, Sequence[int], int] @@ -47,7 +47,7 @@ class Overflow(Inexact, Rounded): ... class Underflow(Inexact, Rounded, Subnormal): ... class FloatOperation(DecimalException, TypeError): ... -def setcontext(__context: Context) -> None: ... +def setcontext(context: Context, /) -> None: ... def getcontext() -> Context: ... if sys.version_info >= (3, 11): @@ -70,35 +70,36 @@ else: class Decimal: def __new__(cls, value: _DecimalNew = ..., context: Context | None = ...) -> Self: ... @classmethod - def from_float(cls, __f: float) -> Self: ... + def from_float(cls, f: float, /) -> Self: ... def __bool__(self) -> bool: ... def compare(self, other: _Decimal, context: Context | None = None) -> Decimal: ... + def __hash__(self) -> int: ... def as_tuple(self) -> DecimalTuple: ... def as_integer_ratio(self) -> tuple[int, int]: ... def to_eng_string(self, context: Context | None = None) -> str: ... def __abs__(self) -> Decimal: ... - def __add__(self, __other: _Decimal) -> Decimal: ... - def __divmod__(self, __other: _Decimal) -> tuple[Decimal, Decimal]: ... - def __eq__(self, __other: object) -> bool: ... - def __floordiv__(self, __other: _Decimal) -> Decimal: ... - def __ge__(self, __other: _ComparableNum) -> bool: ... - def __gt__(self, __other: _ComparableNum) -> bool: ... - def __le__(self, __other: _ComparableNum) -> bool: ... - def __lt__(self, __other: _ComparableNum) -> bool: ... - def __mod__(self, __other: _Decimal) -> Decimal: ... - def __mul__(self, __other: _Decimal) -> Decimal: ... + def __add__(self, value: _Decimal, /) -> Decimal: ... + def __divmod__(self, value: _Decimal, /) -> tuple[Decimal, Decimal]: ... + def __eq__(self, value: object, /) -> bool: ... + def __floordiv__(self, value: _Decimal, /) -> Decimal: ... + def __ge__(self, value: _ComparableNum, /) -> bool: ... + def __gt__(self, value: _ComparableNum, /) -> bool: ... + def __le__(self, value: _ComparableNum, /) -> bool: ... + def __lt__(self, value: _ComparableNum, /) -> bool: ... + def __mod__(self, value: _Decimal, /) -> Decimal: ... + def __mul__(self, value: _Decimal, /) -> Decimal: ... def __neg__(self) -> Decimal: ... def __pos__(self) -> Decimal: ... - def __pow__(self, __other: _Decimal, __modulo: _Decimal | None = ...) -> Decimal: ... - def __radd__(self, __other: _Decimal) -> Decimal: ... - def __rdivmod__(self, __other: _Decimal) -> tuple[Decimal, Decimal]: ... - def __rfloordiv__(self, __other: _Decimal) -> Decimal: ... - def __rmod__(self, __other: _Decimal) -> Decimal: ... - def __rmul__(self, __other: _Decimal) -> Decimal: ... - def __rsub__(self, __other: _Decimal) -> Decimal: ... - def __rtruediv__(self, __other: _Decimal) -> Decimal: ... - def __sub__(self, __other: _Decimal) -> Decimal: ... - def __truediv__(self, __other: _Decimal) -> Decimal: ... + def __pow__(self, value: _Decimal, mod: _Decimal | None = None, /) -> Decimal: ... + def __radd__(self, value: _Decimal, /) -> Decimal: ... + def __rdivmod__(self, value: _Decimal, /) -> tuple[Decimal, Decimal]: ... + def __rfloordiv__(self, value: _Decimal, /) -> Decimal: ... + def __rmod__(self, value: _Decimal, /) -> Decimal: ... + def __rmul__(self, value: _Decimal, /) -> Decimal: ... + def __rsub__(self, value: _Decimal, /) -> Decimal: ... + def __rtruediv__(self, value: _Decimal, /) -> Decimal: ... + def __sub__(self, value: _Decimal, /) -> Decimal: ... + def __truediv__(self, value: _Decimal, /) -> Decimal: ... def remainder_near(self, other: _Decimal, context: Context | None = None) -> Decimal: ... def __float__(self) -> float: ... def __int__(self) -> int: ... @@ -112,11 +113,11 @@ class Decimal: @overload def __round__(self) -> int: ... @overload - def __round__(self, __ndigits: int) -> Decimal: ... + def __round__(self, ndigits: int, /) -> Decimal: ... def __floor__(self) -> int: ... def __ceil__(self) -> int: ... def fma(self, other: _Decimal, third: _Decimal, context: Context | None = None) -> Decimal: ... - def __rpow__(self, __other: _Decimal, __context: Context | None = ...) -> Decimal: ... + def __rpow__(self, value: _Decimal, mod: Context | None = None, /) -> Decimal: ... def normalize(self, context: Context | None = None) -> Decimal: ... def quantize(self, exp: _Decimal, rounding: str | None = None, context: Context | None = None) -> Decimal: ... def same_quantum(self, other: _Decimal, context: Context | None = None) -> bool: ... @@ -164,8 +165,8 @@ class Decimal: def shift(self, other: _Decimal, context: Context | None = None) -> Decimal: ... def __reduce__(self) -> tuple[type[Self], tuple[str]]: ... def __copy__(self) -> Self: ... - def __deepcopy__(self, __memo: Any) -> Self: ... - def __format__(self, __specifier: str, __context: Context | None = ...) -> str: ... + def __deepcopy__(self, memo: Any, /) -> Self: ... + def __format__(self, specifier: str, context: Context | None = ..., /) -> str: ... class _ContextManager: new_context: Context @@ -181,7 +182,7 @@ class Context: # even settable attributes like `prec` and `rounding`, # but that's inexpressable in the stub. # Type checkers either ignore it or misinterpret it - # if you add a `def __delattr__(self, __name: str) -> NoReturn` method to the stub + # if you add a `def __delattr__(self, name: str, /) -> NoReturn` method to the stub prec: int rounding: str Emin: int @@ -211,69 +212,69 @@ class Context: __hash__: ClassVar[None] # type: ignore[assignment] def Etiny(self) -> int: ... def Etop(self) -> int: ... - def create_decimal(self, __num: _DecimalNew = "0") -> Decimal: ... - def create_decimal_from_float(self, __f: float) -> Decimal: ... - def abs(self, __x: _Decimal) -> Decimal: ... - def add(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def canonical(self, __x: Decimal) -> Decimal: ... - def compare(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def compare_signal(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def compare_total(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def compare_total_mag(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def copy_abs(self, __x: _Decimal) -> Decimal: ... - def copy_decimal(self, __x: _Decimal) -> Decimal: ... - def copy_negate(self, __x: _Decimal) -> Decimal: ... - def copy_sign(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def divide(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def divide_int(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def divmod(self, __x: _Decimal, __y: _Decimal) -> tuple[Decimal, Decimal]: ... - def exp(self, __x: _Decimal) -> Decimal: ... - def fma(self, __x: _Decimal, __y: _Decimal, __z: _Decimal) -> Decimal: ... - def is_canonical(self, __x: _Decimal) -> bool: ... - def is_finite(self, __x: _Decimal) -> bool: ... - def is_infinite(self, __x: _Decimal) -> bool: ... - def is_nan(self, __x: _Decimal) -> bool: ... - def is_normal(self, __x: _Decimal) -> bool: ... - def is_qnan(self, __x: _Decimal) -> bool: ... - def is_signed(self, __x: _Decimal) -> bool: ... - def is_snan(self, __x: _Decimal) -> bool: ... - def is_subnormal(self, __x: _Decimal) -> bool: ... - def is_zero(self, __x: _Decimal) -> bool: ... - def ln(self, __x: _Decimal) -> Decimal: ... - def log10(self, __x: _Decimal) -> Decimal: ... - def logb(self, __x: _Decimal) -> Decimal: ... - def logical_and(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def logical_invert(self, __x: _Decimal) -> Decimal: ... - def logical_or(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def logical_xor(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def max(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def max_mag(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def min(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def min_mag(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def minus(self, __x: _Decimal) -> Decimal: ... - def multiply(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def next_minus(self, __x: _Decimal) -> Decimal: ... - def next_plus(self, __x: _Decimal) -> Decimal: ... - def next_toward(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def normalize(self, __x: _Decimal) -> Decimal: ... - def number_class(self, __x: _Decimal) -> str: ... - def plus(self, __x: _Decimal) -> Decimal: ... + def create_decimal(self, num: _DecimalNew = "0", /) -> Decimal: ... + def create_decimal_from_float(self, f: float, /) -> Decimal: ... + def abs(self, x: _Decimal, /) -> Decimal: ... + def add(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def canonical(self, x: Decimal, /) -> Decimal: ... + def compare(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def compare_signal(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def compare_total(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def compare_total_mag(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def copy_abs(self, x: _Decimal, /) -> Decimal: ... + def copy_decimal(self, x: _Decimal, /) -> Decimal: ... + def copy_negate(self, x: _Decimal, /) -> Decimal: ... + def copy_sign(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def divide(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def divide_int(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def divmod(self, x: _Decimal, y: _Decimal, /) -> tuple[Decimal, Decimal]: ... + def exp(self, x: _Decimal, /) -> Decimal: ... + def fma(self, x: _Decimal, y: _Decimal, z: _Decimal, /) -> Decimal: ... + def is_canonical(self, x: _Decimal, /) -> bool: ... + def is_finite(self, x: _Decimal, /) -> bool: ... + def is_infinite(self, x: _Decimal, /) -> bool: ... + def is_nan(self, x: _Decimal, /) -> bool: ... + def is_normal(self, x: _Decimal, /) -> bool: ... + def is_qnan(self, x: _Decimal, /) -> bool: ... + def is_signed(self, x: _Decimal, /) -> bool: ... + def is_snan(self, x: _Decimal, /) -> bool: ... + def is_subnormal(self, x: _Decimal, /) -> bool: ... + def is_zero(self, x: _Decimal, /) -> bool: ... + def ln(self, x: _Decimal, /) -> Decimal: ... + def log10(self, x: _Decimal, /) -> Decimal: ... + def logb(self, x: _Decimal, /) -> Decimal: ... + def logical_and(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def logical_invert(self, x: _Decimal, /) -> Decimal: ... + def logical_or(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def logical_xor(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def max(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def max_mag(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def min(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def min_mag(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def minus(self, x: _Decimal, /) -> Decimal: ... + def multiply(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def next_minus(self, x: _Decimal, /) -> Decimal: ... + def next_plus(self, x: _Decimal, /) -> Decimal: ... + def next_toward(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def normalize(self, x: _Decimal, /) -> Decimal: ... + def number_class(self, x: _Decimal, /) -> str: ... + def plus(self, x: _Decimal, /) -> Decimal: ... def power(self, a: _Decimal, b: _Decimal, modulo: _Decimal | None = None) -> Decimal: ... - def quantize(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... + def quantize(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... def radix(self) -> Decimal: ... - def remainder(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def remainder_near(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def rotate(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def same_quantum(self, __x: _Decimal, __y: _Decimal) -> bool: ... - def scaleb(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def shift(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def sqrt(self, __x: _Decimal) -> Decimal: ... - def subtract(self, __x: _Decimal, __y: _Decimal) -> Decimal: ... - def to_eng_string(self, __x: _Decimal) -> str: ... - def to_sci_string(self, __x: _Decimal) -> str: ... - def to_integral_exact(self, __x: _Decimal) -> Decimal: ... - def to_integral_value(self, __x: _Decimal) -> Decimal: ... - def to_integral(self, __x: _Decimal) -> Decimal: ... + def remainder(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def remainder_near(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def rotate(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def same_quantum(self, x: _Decimal, y: _Decimal, /) -> bool: ... + def scaleb(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def shift(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def sqrt(self, x: _Decimal, /) -> Decimal: ... + def subtract(self, x: _Decimal, y: _Decimal, /) -> Decimal: ... + def to_eng_string(self, x: _Decimal, /) -> str: ... + def to_sci_string(self, x: _Decimal, /) -> str: ... + def to_integral_exact(self, x: _Decimal, /) -> Decimal: ... + def to_integral_value(self, x: _Decimal, /) -> Decimal: ... + def to_integral(self, x: _Decimal, /) -> Decimal: ... DefaultContext: Context BasicContext: Context diff --git a/mypy/typeshed/stdlib/_dummy_thread.pyi b/mypy/typeshed/stdlib/_dummy_thread.pyi index e371dd0e9933..1182e53c66c3 100644 --- a/mypy/typeshed/stdlib/_dummy_thread.pyi +++ b/mypy/typeshed/stdlib/_dummy_thread.pyi @@ -1,13 +1,19 @@ from collections.abc import Callable from types import TracebackType -from typing import Any, NoReturn +from typing import Any, NoReturn, overload +from typing_extensions import TypeVarTuple, Unpack __all__ = ["error", "start_new_thread", "exit", "get_ident", "allocate_lock", "interrupt_main", "LockType", "RLock"] +_Ts = TypeVarTuple("_Ts") + TIMEOUT_MAX: int error = RuntimeError -def start_new_thread(function: Callable[..., object], args: tuple[Any, ...], kwargs: dict[str, Any] = ...) -> None: ... +@overload +def start_new_thread(function: Callable[[Unpack[_Ts]], object], args: tuple[Unpack[_Ts]]) -> None: ... +@overload +def start_new_thread(function: Callable[..., object], args: tuple[Any, ...], kwargs: dict[str, Any]) -> None: ... def exit() -> NoReturn: ... def get_ident() -> int: ... def allocate_lock() -> LockType: ... diff --git a/mypy/typeshed/stdlib/_dummy_threading.pyi b/mypy/typeshed/stdlib/_dummy_threading.pyi index 9a49dfa9649e..21d1d1921c0e 100644 --- a/mypy/typeshed/stdlib/_dummy_threading.pyi +++ b/mypy/typeshed/stdlib/_dummy_threading.pyi @@ -1,4 +1,5 @@ import sys +from _thread import _excepthook, _ExceptHookArgs from _typeshed import ProfileFunction, TraceFunction from collections.abc import Callable, Iterable, Mapping from types import TracebackType @@ -28,11 +29,10 @@ __all__ = [ "settrace", "local", "stack_size", + "ExceptHookArgs", + "excepthook", ] -if sys.version_info >= (3, 8): - __all__ += ["ExceptHookArgs", "excepthook"] - def active_count() -> int: ... def current_thread() -> Thread: ... def currentThread() -> Thread: ... @@ -62,7 +62,7 @@ class Thread: group: None = None, target: Callable[..., object] | None = None, name: str | None = None, - args: Iterable[Any] = ..., + args: Iterable[Any] = (), kwargs: Mapping[str, Any] | None = None, *, daemon: bool | None = None, @@ -72,10 +72,8 @@ class Thread: def join(self, timeout: float | None = None) -> None: ... def getName(self) -> str: ... def setName(self, name: str) -> None: ... - if sys.version_info >= (3, 8): - @property - def native_id(self) -> int | None: ... # only available on some platforms - + @property + def native_id(self) -> int | None: ... # only available on some platforms def is_alive(self) -> bool: ... if sys.version_info < (3, 9): def isAlive(self) -> bool: ... @@ -138,11 +136,8 @@ class Event: def clear(self) -> None: ... def wait(self, timeout: float | None = None) -> bool: ... -if sys.version_info >= (3, 8): - from _thread import _excepthook, _ExceptHookArgs - - excepthook = _excepthook - ExceptHookArgs = _ExceptHookArgs +excepthook = _excepthook +ExceptHookArgs = _ExceptHookArgs class Timer(Thread): def __init__( diff --git a/mypy/typeshed/stdlib/_heapq.pyi b/mypy/typeshed/stdlib/_heapq.pyi index 8d6c3e88103e..9f731bf91eef 100644 --- a/mypy/typeshed/stdlib/_heapq.pyi +++ b/mypy/typeshed/stdlib/_heapq.pyi @@ -1,12 +1,11 @@ -from typing import Any, TypeVar -from typing_extensions import Final +from typing import Any, Final, TypeVar _T = TypeVar("_T") __about__: Final[str] -def heapify(__heap: list[Any]) -> None: ... -def heappop(__heap: list[_T]) -> _T: ... -def heappush(__heap: list[_T], __item: _T) -> None: ... -def heappushpop(__heap: list[_T], __item: _T) -> _T: ... -def heapreplace(__heap: list[_T], __item: _T) -> _T: ... +def heapify(heap: list[Any], /) -> None: ... +def heappop(heap: list[_T], /) -> _T: ... +def heappush(heap: list[_T], item: _T, /) -> None: ... +def heappushpop(heap: list[_T], item: _T, /) -> _T: ... +def heapreplace(heap: list[_T], item: _T, /) -> _T: ... diff --git a/mypy/typeshed/stdlib/_imp.pyi b/mypy/typeshed/stdlib/_imp.pyi index adab2e803efe..de3549a91da5 100644 --- a/mypy/typeshed/stdlib/_imp.pyi +++ b/mypy/typeshed/stdlib/_imp.pyi @@ -7,22 +7,22 @@ from typing import Any check_hash_based_pycs: str def source_hash(key: int, source: ReadableBuffer) -> bytes: ... -def create_builtin(__spec: ModuleSpec) -> types.ModuleType: ... -def create_dynamic(__spec: ModuleSpec, __file: Any = None) -> types.ModuleType: ... +def create_builtin(spec: ModuleSpec, /) -> types.ModuleType: ... +def create_dynamic(spec: ModuleSpec, file: Any = None, /) -> types.ModuleType: ... def acquire_lock() -> None: ... -def exec_builtin(__mod: types.ModuleType) -> int: ... -def exec_dynamic(__mod: types.ModuleType) -> int: ... +def exec_builtin(mod: types.ModuleType, /) -> int: ... +def exec_dynamic(mod: types.ModuleType, /) -> int: ... def extension_suffixes() -> list[str]: ... -def init_frozen(__name: str) -> types.ModuleType: ... -def is_builtin(__name: str) -> int: ... -def is_frozen(__name: str) -> bool: ... -def is_frozen_package(__name: str) -> bool: ... +def init_frozen(name: str, /) -> types.ModuleType: ... +def is_builtin(name: str, /) -> int: ... +def is_frozen(name: str, /) -> bool: ... +def is_frozen_package(name: str, /) -> bool: ... def lock_held() -> bool: ... def release_lock() -> None: ... if sys.version_info >= (3, 11): - def find_frozen(__name: str, *, withdata: bool = False) -> tuple[memoryview | None, bool, str | None] | None: ... - def get_frozen_object(__name: str, __data: ReadableBuffer | None = None) -> types.CodeType: ... + def find_frozen(name: str, /, *, withdata: bool = False) -> tuple[memoryview | None, bool, str | None] | None: ... + def get_frozen_object(name: str, data: ReadableBuffer | None = None, /) -> types.CodeType: ... else: - def get_frozen_object(__name: str) -> types.CodeType: ... + def get_frozen_object(name: str, /) -> types.CodeType: ... diff --git a/mypy/typeshed/stdlib/_json.pyi b/mypy/typeshed/stdlib/_json.pyi index 130f7ab92e97..a6a62be184d8 100644 --- a/mypy/typeshed/stdlib/_json.pyi +++ b/mypy/typeshed/stdlib/_json.pyi @@ -1,6 +1,5 @@ from collections.abc import Callable -from typing import Any -from typing_extensions import final +from typing import Any, final @final class make_encoder: diff --git a/mypy/typeshed/stdlib/_locale.pyi b/mypy/typeshed/stdlib/_locale.pyi new file mode 100644 index 000000000000..0825e12034f4 --- /dev/null +++ b/mypy/typeshed/stdlib/_locale.pyi @@ -0,0 +1,100 @@ +import sys +from _typeshed import StrPath +from collections.abc import Mapping + +LC_CTYPE: int +LC_COLLATE: int +LC_TIME: int +LC_MONETARY: int +LC_NUMERIC: int +LC_ALL: int +CHAR_MAX: int + +def setlocale(category: int, locale: str | None = None, /) -> str: ... +def localeconv() -> Mapping[str, int | str | list[int]]: ... + +if sys.version_info >= (3, 11): + def getencoding() -> str: ... + +def strcoll(os1: str, os2: str, /) -> int: ... +def strxfrm(string: str, /) -> str: ... + +# native gettext functions +# https://docs.python.org/3/library/locale.html#access-to-message-catalogs +# https://github.com/python/cpython/blob/f4c03484da59049eb62a9bf7777b963e2267d187/Modules/_localemodule.c#L626 +if sys.platform != "win32": + LC_MESSAGES: int + + ABDAY_1: int + ABDAY_2: int + ABDAY_3: int + ABDAY_4: int + ABDAY_5: int + ABDAY_6: int + ABDAY_7: int + + ABMON_1: int + ABMON_2: int + ABMON_3: int + ABMON_4: int + ABMON_5: int + ABMON_6: int + ABMON_7: int + ABMON_8: int + ABMON_9: int + ABMON_10: int + ABMON_11: int + ABMON_12: int + + DAY_1: int + DAY_2: int + DAY_3: int + DAY_4: int + DAY_5: int + DAY_6: int + DAY_7: int + + ERA: int + ERA_D_T_FMT: int + ERA_D_FMT: int + ERA_T_FMT: int + + MON_1: int + MON_2: int + MON_3: int + MON_4: int + MON_5: int + MON_6: int + MON_7: int + MON_8: int + MON_9: int + MON_10: int + MON_11: int + MON_12: int + + CODESET: int + D_T_FMT: int + D_FMT: int + T_FMT: int + T_FMT_AMPM: int + AM_STR: int + PM_STR: int + + RADIXCHAR: int + THOUSEP: int + YESEXPR: int + NOEXPR: int + CRNCYSTR: int + ALT_DIGITS: int + + def nl_langinfo(key: int, /) -> str: ... + + # This is dependent on `libintl.h` which is a part of `gettext` + # system dependency. These functions might be missing. + # But, we always say that they are present. + def gettext(msg: str, /) -> str: ... + def dgettext(domain: str | None, msg: str, /) -> str: ... + def dcgettext(domain: str | None, msg: str, category: int, /) -> str: ... + def textdomain(domain: str | None, /) -> str: ... + def bindtextdomain(domain: str, dir: StrPath | None, /) -> str: ... + def bind_textdomain_codeset(domain: str, codeset: str | None, /) -> str | None: ... diff --git a/mypy/typeshed/stdlib/_lsprof.pyi b/mypy/typeshed/stdlib/_lsprof.pyi new file mode 100644 index 000000000000..8a6934162c92 --- /dev/null +++ b/mypy/typeshed/stdlib/_lsprof.pyi @@ -0,0 +1,35 @@ +import sys +from _typeshed import structseq +from collections.abc import Callable +from types import CodeType +from typing import Any, Final, final + +class Profiler: + def __init__( + self, timer: Callable[[], float] | None = None, timeunit: float = 0.0, subcalls: bool = True, builtins: bool = True + ) -> None: ... + def getstats(self) -> list[profiler_entry]: ... + def enable(self, subcalls: bool = True, builtins: bool = True) -> None: ... + def disable(self) -> None: ... + def clear(self) -> None: ... + +@final +class profiler_entry(structseq[Any], tuple[CodeType | str, int, int, float, float, list[profiler_subentry]]): + if sys.version_info >= (3, 10): + __match_args__: Final = ("code", "callcount", "reccallcount", "totaltime", "inlinetime", "calls") + code: CodeType | str + callcount: int + reccallcount: int + totaltime: float + inlinetime: float + calls: list[profiler_subentry] + +@final +class profiler_subentry(structseq[Any], tuple[CodeType | str, int, int, float, float]): + if sys.version_info >= (3, 10): + __match_args__: Final = ("code", "callcount", "reccallcount", "totaltime", "inlinetime") + code: CodeType | str + callcount: int + reccallcount: int + totaltime: float + inlinetime: float diff --git a/mypy/typeshed/stdlib/_msi.pyi b/mypy/typeshed/stdlib/_msi.pyi index 2fdbdfd0e9f4..779fda3b67fe 100644 --- a/mypy/typeshed/stdlib/_msi.pyi +++ b/mypy/typeshed/stdlib/_msi.pyi @@ -1,6 +1,7 @@ import sys if sys.platform == "win32": + class MSIError(Exception): ... # Actual typename View, not exposed by the implementation class _View: def Execute(self, params: _Record | None = ...) -> None: ... @@ -44,10 +45,11 @@ if sys.platform == "win32": # Don't exist at runtime __new__: None # type: ignore[assignment] __init__: None # type: ignore[assignment] + def UuidCreate() -> str: ... - def FCICreate(__cabname: str, __files: list[str]) -> None: ... - def OpenDatabase(__path: str, __persist: int) -> _Database: ... - def CreateRecord(__count: int) -> _Record: ... + def FCICreate(cabname: str, files: list[str], /) -> None: ... + def OpenDatabase(path: str, persist: int, /) -> _Database: ... + def CreateRecord(count: int, /) -> _Record: ... MSICOLINFO_NAMES: int MSICOLINFO_TYPES: int diff --git a/mypy/typeshed/stdlib/_operator.pyi b/mypy/typeshed/stdlib/_operator.pyi index e7d1a98c4027..69ee563b5cf4 100644 --- a/mypy/typeshed/stdlib/_operator.pyi +++ b/mypy/typeshed/stdlib/_operator.pyi @@ -1,31 +1,34 @@ import sys from _typeshed import SupportsGetItem from collections.abc import Callable, Container, Iterable, MutableMapping, MutableSequence, Sequence -from typing import Any, AnyStr, Generic, Protocol, SupportsAbs, TypeVar, overload -from typing_extensions import ParamSpec, SupportsIndex, TypeAlias, final +from typing import Any, AnyStr, Generic, Protocol, SupportsAbs, SupportsIndex, TypeVar, final, overload +from typing_extensions import ParamSpec, TypeAlias, TypeVarTuple, Unpack _R = TypeVar("_R") _T = TypeVar("_T") _T_co = TypeVar("_T_co", covariant=True) +_T1 = TypeVar("_T1") +_T2 = TypeVar("_T2") _K = TypeVar("_K") _V = TypeVar("_V") _P = ParamSpec("_P") +_Ts = TypeVarTuple("_Ts") # The following protocols return "Any" instead of bool, since the comparison # operators can be overloaded to return an arbitrary object. For example, # the numpy.array comparison dunders return another numpy.array. class _SupportsDunderLT(Protocol): - def __lt__(self, __other: Any) -> Any: ... + def __lt__(self, other: Any, /) -> Any: ... class _SupportsDunderGT(Protocol): - def __gt__(self, __other: Any) -> Any: ... + def __gt__(self, other: Any, /) -> Any: ... class _SupportsDunderLE(Protocol): - def __le__(self, __other: Any) -> Any: ... + def __le__(self, other: Any, /) -> Any: ... class _SupportsDunderGE(Protocol): - def __ge__(self, __other: Any) -> Any: ... + def __ge__(self, other: Any, /) -> Any: ... _SupportsComparison: TypeAlias = _SupportsDunderLE | _SupportsDunderGE | _SupportsDunderGT | _SupportsDunderLT @@ -39,88 +42,76 @@ class _SupportsPos(Protocol[_T_co]): def __pos__(self) -> _T_co: ... # All four comparison functions must have the same signature, or we get false-positive errors -def lt(__a: _SupportsComparison, __b: _SupportsComparison) -> Any: ... -def le(__a: _SupportsComparison, __b: _SupportsComparison) -> Any: ... -def eq(__a: object, __b: object) -> Any: ... -def ne(__a: object, __b: object) -> Any: ... -def ge(__a: _SupportsComparison, __b: _SupportsComparison) -> Any: ... -def gt(__a: _SupportsComparison, __b: _SupportsComparison) -> Any: ... -def not_(__a: object) -> bool: ... -def truth(__a: object) -> bool: ... -def is_(__a: object, __b: object) -> bool: ... -def is_not(__a: object, __b: object) -> bool: ... -def abs(__a: SupportsAbs[_T]) -> _T: ... -def add(__a: Any, __b: Any) -> Any: ... -def and_(__a: Any, __b: Any) -> Any: ... -def floordiv(__a: Any, __b: Any) -> Any: ... -def index(__a: SupportsIndex) -> int: ... -def inv(__a: _SupportsInversion[_T_co]) -> _T_co: ... -def invert(__a: _SupportsInversion[_T_co]) -> _T_co: ... -def lshift(__a: Any, __b: Any) -> Any: ... -def mod(__a: Any, __b: Any) -> Any: ... -def mul(__a: Any, __b: Any) -> Any: ... -def matmul(__a: Any, __b: Any) -> Any: ... -def neg(__a: _SupportsNeg[_T_co]) -> _T_co: ... -def or_(__a: Any, __b: Any) -> Any: ... -def pos(__a: _SupportsPos[_T_co]) -> _T_co: ... -def pow(__a: Any, __b: Any) -> Any: ... -def rshift(__a: Any, __b: Any) -> Any: ... -def sub(__a: Any, __b: Any) -> Any: ... -def truediv(__a: Any, __b: Any) -> Any: ... -def xor(__a: Any, __b: Any) -> Any: ... -def concat(__a: Sequence[_T], __b: Sequence[_T]) -> Sequence[_T]: ... -def contains(__a: Container[object], __b: object) -> bool: ... -def countOf(__a: Iterable[object], __b: object) -> int: ... +def lt(a: _SupportsComparison, b: _SupportsComparison, /) -> Any: ... +def le(a: _SupportsComparison, b: _SupportsComparison, /) -> Any: ... +def eq(a: object, b: object, /) -> Any: ... +def ne(a: object, b: object, /) -> Any: ... +def ge(a: _SupportsComparison, b: _SupportsComparison, /) -> Any: ... +def gt(a: _SupportsComparison, b: _SupportsComparison, /) -> Any: ... +def not_(a: object, /) -> bool: ... +def truth(a: object, /) -> bool: ... +def is_(a: object, b: object, /) -> bool: ... +def is_not(a: object, b: object, /) -> bool: ... +def abs(a: SupportsAbs[_T], /) -> _T: ... +def add(a: Any, b: Any, /) -> Any: ... +def and_(a: Any, b: Any, /) -> Any: ... +def floordiv(a: Any, b: Any, /) -> Any: ... +def index(a: SupportsIndex, /) -> int: ... +def inv(a: _SupportsInversion[_T_co], /) -> _T_co: ... +def invert(a: _SupportsInversion[_T_co], /) -> _T_co: ... +def lshift(a: Any, b: Any, /) -> Any: ... +def mod(a: Any, b: Any, /) -> Any: ... +def mul(a: Any, b: Any, /) -> Any: ... +def matmul(a: Any, b: Any, /) -> Any: ... +def neg(a: _SupportsNeg[_T_co], /) -> _T_co: ... +def or_(a: Any, b: Any, /) -> Any: ... +def pos(a: _SupportsPos[_T_co], /) -> _T_co: ... +def pow(a: Any, b: Any, /) -> Any: ... +def rshift(a: Any, b: Any, /) -> Any: ... +def sub(a: Any, b: Any, /) -> Any: ... +def truediv(a: Any, b: Any, /) -> Any: ... +def xor(a: Any, b: Any, /) -> Any: ... +def concat(a: Sequence[_T], b: Sequence[_T], /) -> Sequence[_T]: ... +def contains(a: Container[object], b: object, /) -> bool: ... +def countOf(a: Iterable[object], b: object, /) -> int: ... @overload -def delitem(__a: MutableSequence[Any], __b: SupportsIndex) -> None: ... +def delitem(a: MutableSequence[Any], b: SupportsIndex, /) -> None: ... @overload -def delitem(__a: MutableSequence[Any], __b: slice) -> None: ... +def delitem(a: MutableSequence[Any], b: slice, /) -> None: ... @overload -def delitem(__a: MutableMapping[_K, Any], __b: _K) -> None: ... +def delitem(a: MutableMapping[_K, Any], b: _K, /) -> None: ... @overload -def getitem(__a: Sequence[_T], __b: slice) -> Sequence[_T]: ... +def getitem(a: Sequence[_T], b: slice, /) -> Sequence[_T]: ... @overload -def getitem(__a: SupportsGetItem[_K, _V], __b: _K) -> _V: ... -def indexOf(__a: Iterable[_T], __b: _T) -> int: ... +def getitem(a: SupportsGetItem[_K, _V], b: _K, /) -> _V: ... +def indexOf(a: Iterable[_T], b: _T, /) -> int: ... @overload -def setitem(__a: MutableSequence[_T], __b: SupportsIndex, __c: _T) -> None: ... +def setitem(a: MutableSequence[_T], b: SupportsIndex, c: _T, /) -> None: ... @overload -def setitem(__a: MutableSequence[_T], __b: slice, __c: Sequence[_T]) -> None: ... +def setitem(a: MutableSequence[_T], b: slice, c: Sequence[_T], /) -> None: ... @overload -def setitem(__a: MutableMapping[_K, _V], __b: _K, __c: _V) -> None: ... -def length_hint(__obj: object, __default: int = 0) -> int: ... +def setitem(a: MutableMapping[_K, _V], b: _K, c: _V, /) -> None: ... +def length_hint(obj: object, default: int = 0, /) -> int: ... @final class attrgetter(Generic[_T_co]): @overload - def __new__(cls, attr: str) -> attrgetter[Any]: ... + def __new__(cls, attr: str, /) -> attrgetter[Any]: ... @overload - def __new__(cls, attr: str, __attr2: str) -> attrgetter[tuple[Any, Any]]: ... + def __new__(cls, attr: str, attr2: str, /) -> attrgetter[tuple[Any, Any]]: ... @overload - def __new__(cls, attr: str, __attr2: str, __attr3: str) -> attrgetter[tuple[Any, Any, Any]]: ... + def __new__(cls, attr: str, attr2: str, attr3: str, /) -> attrgetter[tuple[Any, Any, Any]]: ... @overload - def __new__(cls, attr: str, __attr2: str, __attr3: str, __attr4: str) -> attrgetter[tuple[Any, Any, Any, Any]]: ... + def __new__(cls, attr: str, attr2: str, attr3: str, attr4: str, /) -> attrgetter[tuple[Any, Any, Any, Any]]: ... @overload - def __new__(cls, attr: str, *attrs: str) -> attrgetter[tuple[Any, ...]]: ... - def __call__(self, obj: Any) -> _T_co: ... + def __new__(cls, attr: str, /, *attrs: str) -> attrgetter[tuple[Any, ...]]: ... + def __call__(self, obj: Any, /) -> _T_co: ... @final class itemgetter(Generic[_T_co]): - # mypy lacks support for PEP 646 https://github.com/python/mypy/issues/12280 - # So we have to define all of these overloads to simulate unpacking the arguments @overload - def __new__(cls, item: _T_co) -> itemgetter[_T_co]: ... + def __new__(cls, item: _T, /) -> itemgetter[_T]: ... @overload - def __new__(cls, item: _T_co, __item2: _T_co) -> itemgetter[tuple[_T_co, _T_co]]: ... - @overload - def __new__(cls, item: _T_co, __item2: _T_co, __item3: _T_co) -> itemgetter[tuple[_T_co, _T_co, _T_co]]: ... - @overload - def __new__( - cls, item: _T_co, __item2: _T_co, __item3: _T_co, __item4: _T_co - ) -> itemgetter[tuple[_T_co, _T_co, _T_co, _T_co]]: ... - @overload - def __new__( - cls, item: _T_co, __item2: _T_co, __item3: _T_co, __item4: _T_co, *items: _T_co - ) -> itemgetter[tuple[_T_co, ...]]: ... + def __new__(cls, item1: _T1, item2: _T2, /, *items: Unpack[_Ts]) -> itemgetter[tuple[_T1, _T2, Unpack[_Ts]]]: ... # __key: _KT_contra in SupportsGetItem seems to be causing variance issues, ie: # TypeVar "_KT_contra@SupportsGetItem" is contravariant # "tuple[int, int]" is incompatible with protocol "SupportsIndex" @@ -132,25 +123,25 @@ class itemgetter(Generic[_T_co]): @final class methodcaller: - def __init__(self, __name: str, *args: Any, **kwargs: Any) -> None: ... + def __init__(self, name: str, /, *args: Any, **kwargs: Any) -> None: ... def __call__(self, obj: Any) -> Any: ... -def iadd(__a: Any, __b: Any) -> Any: ... -def iand(__a: Any, __b: Any) -> Any: ... -def iconcat(__a: Any, __b: Any) -> Any: ... -def ifloordiv(__a: Any, __b: Any) -> Any: ... -def ilshift(__a: Any, __b: Any) -> Any: ... -def imod(__a: Any, __b: Any) -> Any: ... -def imul(__a: Any, __b: Any) -> Any: ... -def imatmul(__a: Any, __b: Any) -> Any: ... -def ior(__a: Any, __b: Any) -> Any: ... -def ipow(__a: Any, __b: Any) -> Any: ... -def irshift(__a: Any, __b: Any) -> Any: ... -def isub(__a: Any, __b: Any) -> Any: ... -def itruediv(__a: Any, __b: Any) -> Any: ... -def ixor(__a: Any, __b: Any) -> Any: ... +def iadd(a: Any, b: Any, /) -> Any: ... +def iand(a: Any, b: Any, /) -> Any: ... +def iconcat(a: Any, b: Any, /) -> Any: ... +def ifloordiv(a: Any, b: Any, /) -> Any: ... +def ilshift(a: Any, b: Any, /) -> Any: ... +def imod(a: Any, b: Any, /) -> Any: ... +def imul(a: Any, b: Any, /) -> Any: ... +def imatmul(a: Any, b: Any, /) -> Any: ... +def ior(a: Any, b: Any, /) -> Any: ... +def ipow(a: Any, b: Any, /) -> Any: ... +def irshift(a: Any, b: Any, /) -> Any: ... +def isub(a: Any, b: Any, /) -> Any: ... +def itruediv(a: Any, b: Any, /) -> Any: ... +def ixor(a: Any, b: Any, /) -> Any: ... if sys.version_info >= (3, 11): - def call(__obj: Callable[_P, _R], *args: _P.args, **kwargs: _P.kwargs) -> _R: ... + def call(obj: Callable[_P, _R], /, *args: _P.args, **kwargs: _P.kwargs) -> _R: ... -def _compare_digest(__a: AnyStr, __b: AnyStr) -> bool: ... +def _compare_digest(a: AnyStr, b: AnyStr, /) -> bool: ... diff --git a/mypy/typeshed/stdlib/_osx_support.pyi b/mypy/typeshed/stdlib/_osx_support.pyi index 3eb6f4ddc67c..64dbdd24fd40 100644 --- a/mypy/typeshed/stdlib/_osx_support.pyi +++ b/mypy/typeshed/stdlib/_osx_support.pyi @@ -1,4 +1,3 @@ -import sys from collections.abc import Iterable, Sequence from typing import TypeVar @@ -13,13 +12,7 @@ _COMPILER_CONFIG_VARS: tuple[str, ...] # undocumented _INITPRE: str # undocumented def _find_executable(executable: str, path: str | None = None) -> str | None: ... # undocumented - -if sys.version_info >= (3, 8): - def _read_output(commandstring: str, capture_stderr: bool = False) -> str | None: ... # undocumented - -else: - def _read_output(commandstring: str) -> str | None: ... # undocumented - +def _read_output(commandstring: str, capture_stderr: bool = False) -> str | None: ... # undocumented def _find_build_tool(toolname: str) -> str: ... # undocumented _SYSTEM_VERSION: str | None # undocumented diff --git a/mypy/typeshed/stdlib/_posixsubprocess.pyi b/mypy/typeshed/stdlib/_posixsubprocess.pyi index ca95336bb503..3df56d9a3d03 100644 --- a/mypy/typeshed/stdlib/_posixsubprocess.pyi +++ b/mypy/typeshed/stdlib/_posixsubprocess.pyi @@ -1,32 +1,33 @@ import sys from _typeshed import StrOrBytesPath from collections.abc import Callable, Sequence -from typing_extensions import SupportsIndex +from typing import SupportsIndex if sys.platform != "win32": def cloexec_pipe() -> tuple[int, int]: ... def fork_exec( - __process_args: Sequence[StrOrBytesPath] | None, - __executable_list: Sequence[bytes], - __close_fds: bool, - __fds_to_keep: tuple[int, ...], - __cwd_obj: str, - __env_list: Sequence[bytes] | None, - __p2cread: int, - __p2cwrite: int, - __c2pred: int, - __c2pwrite: int, - __errread: int, - __errwrite: int, - __errpipe_read: int, - __errpipe_write: int, - __restore_signals: int, - __call_setsid: int, - __pgid_to_set: int, - __gid_object: SupportsIndex | None, - __groups_list: list[int] | None, - __uid_object: SupportsIndex | None, - __child_umask: int, - __preexec_fn: Callable[[], None], - __allow_vfork: bool, + args: Sequence[StrOrBytesPath] | None, + executable_list: Sequence[bytes], + close_fds: bool, + pass_fds: tuple[int, ...], + cwd: str, + env: Sequence[bytes] | None, + p2cread: int, + p2cwrite: int, + c2pread: int, + c2pwrite: int, + errread: int, + errwrite: int, + errpipe_read: int, + errpipe_write: int, + restore_signals: int, + call_setsid: int, + pgid_to_set: int, + gid: SupportsIndex | None, + extra_groups: list[int] | None, + uid: SupportsIndex | None, + child_umask: int, + preexec_fn: Callable[[], None], + allow_vfork: bool, + /, ) -> int: ... diff --git a/mypy/typeshed/stdlib/_py_abc.pyi b/mypy/typeshed/stdlib/_py_abc.pyi index cc45c6ad3814..1260717489e4 100644 --- a/mypy/typeshed/stdlib/_py_abc.pyi +++ b/mypy/typeshed/stdlib/_py_abc.pyi @@ -9,6 +9,6 @@ def get_cache_token() -> _CacheToken: ... class ABCMeta(type): def __new__( - __mcls: type[_typeshed.Self], __name: str, __bases: tuple[type[Any], ...], __namespace: dict[str, Any] + mcls: type[_typeshed.Self], name: str, bases: tuple[type[Any], ...], namespace: dict[str, Any], / ) -> _typeshed.Self: ... def register(cls, subclass: type[_T]) -> type[_T]: ... diff --git a/mypy/typeshed/stdlib/_random.pyi b/mypy/typeshed/stdlib/_random.pyi index 7c5803ede781..4082344ade8e 100644 --- a/mypy/typeshed/stdlib/_random.pyi +++ b/mypy/typeshed/stdlib/_random.pyi @@ -5,8 +5,8 @@ _State: TypeAlias = tuple[int, ...] class Random: def __init__(self, seed: object = ...) -> None: ... - def seed(self, __n: object = None) -> None: ... + def seed(self, n: object = None, /) -> None: ... def getstate(self) -> _State: ... - def setstate(self, __state: _State) -> None: ... + def setstate(self, state: _State, /) -> None: ... def random(self) -> float: ... - def getrandbits(self, __k: int) -> int: ... + def getrandbits(self, k: int, /) -> int: ... diff --git a/mypy/typeshed/stdlib/_sitebuiltins.pyi b/mypy/typeshed/stdlib/_sitebuiltins.pyi index 3bda2d88425d..49e88a196825 100644 --- a/mypy/typeshed/stdlib/_sitebuiltins.pyi +++ b/mypy/typeshed/stdlib/_sitebuiltins.pyi @@ -1,6 +1,5 @@ from collections.abc import Iterable -from typing import ClassVar, NoReturn -from typing_extensions import Literal +from typing import ClassVar, Literal, NoReturn class Quitter: name: str @@ -10,7 +9,7 @@ class Quitter: class _Printer: MAXLINES: ClassVar[Literal[23]] - def __init__(self, name: str, data: str, files: Iterable[str] = ..., dirs: Iterable[str] = ...) -> None: ... + def __init__(self, name: str, data: str, files: Iterable[str] = (), dirs: Iterable[str] = ()) -> None: ... def __call__(self) -> None: ... class _Helper: diff --git a/mypy/typeshed/stdlib/_socket.pyi b/mypy/typeshed/stdlib/_socket.pyi index f7b0e6901bf4..2a48349d4f7d 100644 --- a/mypy/typeshed/stdlib/_socket.pyi +++ b/mypy/typeshed/stdlib/_socket.pyi @@ -1,16 +1,9 @@ import sys from _typeshed import ReadableBuffer, WriteableBuffer from collections.abc import Iterable -from typing import Any, SupportsInt, overload +from typing import Any, SupportsIndex, overload from typing_extensions import TypeAlias -if sys.version_info >= (3, 8): - from typing import SupportsIndex - - _FD: TypeAlias = SupportsIndex -else: - _FD: TypeAlias = SupportsInt - _CMSG: TypeAlias = tuple[int, int, bytes] _CMSGArg: TypeAlias = tuple[int, int, ReadableBuffer] @@ -20,19 +13,18 @@ _CMSGArg: TypeAlias = tuple[int, int, ReadableBuffer] _Address: TypeAlias = tuple[Any, ...] | str | ReadableBuffer _RetAddress: TypeAlias = Any -# ----- Constants ----- -# Some socket families are listed in the "Socket families" section of the docs, -# but not the "Constants" section. These are listed at the end of the list of -# constants. -# -# Besides those and the first few constants listed, the constants are listed in -# documentation order. +# ===== Constants ===== +# This matches the order in the CPython documentation +# https://docs.python.org/3/library/socket.html#constants -has_ipv6: bool +if sys.platform != "win32": + AF_UNIX: int AF_INET: int AF_INET6: int +AF_UNSPEC: int + SOCK_STREAM: int SOCK_DGRAM: int SOCK_RAW: int @@ -40,162 +32,29 @@ SOCK_RDM: int SOCK_SEQPACKET: int if sys.platform == "linux": + # Availability: Linux >= 2.6.27 SOCK_CLOEXEC: int SOCK_NONBLOCK: int -# Address families not mentioned in the docs -AF_APPLETALK: int -AF_DECnet: int -AF_IPX: int -AF_SNA: int -AF_UNSPEC: int - -if sys.platform != "win32": - AF_ROUTE: int - AF_SYSTEM: int - AF_UNIX: int - -if sys.platform != "darwin": - AF_IRDA: int - -if sys.platform != "darwin" and sys.platform != "win32": - AF_AAL5: int - AF_ASH: int - AF_ATMPVC: int - AF_ATMSVC: int - AF_AX25: int - AF_BRIDGE: int - AF_ECONET: int - AF_KEY: int - AF_LLC: int - AF_NETBEUI: int - AF_NETROM: int - AF_PPPOX: int - AF_ROSE: int - AF_SECURITY: int - AF_WANPIPE: int - AF_X25: int - -# The "many constants" referenced by the docs -SOMAXCONN: int -AI_ADDRCONFIG: int -AI_ALL: int -AI_CANONNAME: int -AI_NUMERICHOST: int -AI_NUMERICSERV: int -AI_PASSIVE: int -AI_V4MAPPED: int -EAI_AGAIN: int -EAI_BADFLAGS: int -EAI_FAIL: int -EAI_FAMILY: int -EAI_MEMORY: int -EAI_NODATA: int -EAI_NONAME: int -EAI_SERVICE: int -EAI_SOCKTYPE: int -INADDR_ALLHOSTS_GROUP: int -INADDR_ANY: int -INADDR_BROADCAST: int -INADDR_LOOPBACK: int -INADDR_MAX_LOCAL_GROUP: int -INADDR_NONE: int -INADDR_UNSPEC_GROUP: int -IPPORT_RESERVED: int -IPPORT_USERRESERVED: int - -if sys.platform != "win32" or sys.version_info >= (3, 8): - IPPROTO_AH: int - IPPROTO_DSTOPTS: int - IPPROTO_EGP: int - IPPROTO_ESP: int - IPPROTO_FRAGMENT: int - IPPROTO_GGP: int - IPPROTO_HOPOPTS: int - IPPROTO_ICMPV6: int - IPPROTO_IDP: int - IPPROTO_IGMP: int - IPPROTO_IPV4: int - IPPROTO_IPV6: int - IPPROTO_MAX: int - IPPROTO_ND: int - IPPROTO_NONE: int - IPPROTO_PIM: int - IPPROTO_PUP: int - IPPROTO_ROUTING: int - IPPROTO_SCTP: int - - if sys.platform != "darwin": - IPPROTO_CBT: int - IPPROTO_ICLFXBM: int - IPPROTO_IGP: int - IPPROTO_L2TP: int - IPPROTO_PGM: int - IPPROTO_RDP: int - IPPROTO_ST: int - -IPPROTO_ICMP: int -IPPROTO_IP: int -IPPROTO_RAW: int -IPPROTO_TCP: int -IPPROTO_UDP: int -IPV6_CHECKSUM: int -IPV6_JOIN_GROUP: int -IPV6_LEAVE_GROUP: int -IPV6_MULTICAST_HOPS: int -IPV6_MULTICAST_IF: int -IPV6_MULTICAST_LOOP: int -IPV6_RECVTCLASS: int -IPV6_TCLASS: int -IPV6_UNICAST_HOPS: int -IPV6_V6ONLY: int - -if sys.platform != "darwin" or sys.version_info >= (3, 9): - IPV6_DONTFRAG: int - IPV6_HOPLIMIT: int - IPV6_HOPOPTS: int - IPV6_PKTINFO: int - IPV6_RECVRTHDR: int - IPV6_RTHDR: int - -IP_ADD_MEMBERSHIP: int -IP_DROP_MEMBERSHIP: int -IP_HDRINCL: int -IP_MULTICAST_IF: int -IP_MULTICAST_LOOP: int -IP_MULTICAST_TTL: int -IP_OPTIONS: int -IP_RECVDSTADDR: int -if sys.version_info >= (3, 10): - IP_RECVTOS: int -elif sys.platform != "win32" and sys.platform != "darwin": - IP_RECVTOS: int -IP_TOS: int -IP_TTL: int -MSG_CTRUNC: int -MSG_DONTROUTE: int - -if sys.platform != "darwin": - MSG_ERRQUEUE: int +# -------------------- +# Many constants of these forms, documented in the Unix documentation on +# sockets and/or the IP protocol, are also defined in the socket module. +# SO_* +# socket.SOMAXCONN +# MSG_* +# SOL_* +# SCM_* +# IPPROTO_* +# IPPORT_* +# INADDR_* +# IP_* +# IPV6_* +# EAI_* +# AI_* +# NI_* +# TCP_* +# -------------------- -MSG_OOB: int -MSG_PEEK: int -MSG_TRUNC: int -MSG_WAITALL: int -NI_DGRAM: int -NI_MAXHOST: int -NI_MAXSERV: int -NI_NAMEREQD: int -NI_NOFQDN: int -NI_NUMERICHOST: int -NI_NUMERICSERV: int -SHUT_RD: int -SHUT_RDWR: int -SHUT_WR: int -SOL_IP: int -SOL_SOCKET: int -SOL_TCP: int -SOL_UDP: int SO_ACCEPTCONN: int SO_BROADCAST: int SO_DEBUG: int @@ -213,39 +72,99 @@ SO_SNDLOWAT: int SO_SNDTIMEO: int SO_TYPE: int SO_USELOOPBACK: int -if sys.platform == "linux" and sys.version_info >= (3, 11): - SO_INCOMING_CPU: int -TCP_FASTOPEN: int -TCP_KEEPCNT: int -TCP_KEEPINTVL: int +if sys.platform == "win32": + SO_EXCLUSIVEADDRUSE: int +if sys.platform != "win32": + SO_REUSEPORT: int +if sys.platform != "win32" and sys.platform != "darwin": + SO_BINDTODEVICE: int + SO_DOMAIN: int + SO_MARK: int + SO_PASSCRED: int + SO_PASSSEC: int + SO_PEERCRED: int + SO_PEERSEC: int + SO_PRIORITY: int + SO_PROTOCOL: int + SO_SETFIB: int -if sys.platform != "darwin": - TCP_KEEPIDLE: int +SOMAXCONN: int -TCP_MAXSEG: int -TCP_NODELAY: int +MSG_CTRUNC: int +MSG_DONTROUTE: int +MSG_OOB: int +MSG_PEEK: int +MSG_TRUNC: int +MSG_WAITALL: int if sys.platform != "win32": - TCP_NOTSENT_LOWAT: int -if sys.version_info >= (3, 10) and sys.platform == "darwin": - TCP_KEEPALIVE: int -if sys.version_info >= (3, 11) and sys.platform == "darwin": - TCP_CONNECTION_INFO: int - + MSG_DONTWAIT: int + MSG_EOF: int + MSG_EOR: int + MSG_NOSIGNAL: int # Sometimes this exists on darwin, sometimes not if sys.platform != "darwin": MSG_BCAST: int + MSG_ERRQUEUE: int MSG_MCAST: int - SO_EXCLUSIVEADDRUSE: int +if sys.platform != "win32" and sys.platform != "darwin": + MSG_BTAG: int + MSG_CMSG_CLOEXEC: int + MSG_CONFIRM: int + MSG_ETAG: int + MSG_FASTOPEN: int + MSG_MORE: int + MSG_NOTIFICATION: int + +SOL_IP: int +SOL_SOCKET: int +SOL_TCP: int +SOL_UDP: int +if sys.platform != "win32" and sys.platform != "darwin": + SOL_ATALK: int + SOL_AX25: int + SOL_HCI: int + SOL_IPX: int + SOL_NETROM: int + SOL_ROSE: int if sys.platform != "win32": - AI_DEFAULT: int - AI_MASK: int - AI_V4MAPPED_CFG: int - EAI_ADDRFAMILY: int - EAI_BADHINTS: int - EAI_MAX: int - EAI_OVERFLOW: int - EAI_PROTOCOL: int - EAI_SYSTEM: int + SCM_CREDS: int + SCM_RIGHTS: int +if sys.platform != "win32" and sys.platform != "darwin": + SCM_CREDENTIALS: int + +IPPROTO_ICMP: int +IPPROTO_IP: int +IPPROTO_RAW: int +IPPROTO_TCP: int +IPPROTO_UDP: int +IPPROTO_AH: int +IPPROTO_DSTOPTS: int +IPPROTO_EGP: int +IPPROTO_ESP: int +IPPROTO_FRAGMENT: int +IPPROTO_GGP: int +IPPROTO_HOPOPTS: int +IPPROTO_ICMPV6: int +IPPROTO_IDP: int +IPPROTO_IGMP: int +IPPROTO_IPV4: int +IPPROTO_IPV6: int +IPPROTO_MAX: int +IPPROTO_ND: int +IPPROTO_NONE: int +IPPROTO_PIM: int +IPPROTO_PUP: int +IPPROTO_ROUTING: int +IPPROTO_SCTP: int +if sys.platform != "darwin": + IPPROTO_CBT: int + IPPROTO_ICLFXBM: int + IPPROTO_IGP: int + IPPROTO_L2TP: int + IPPROTO_PGM: int + IPPROTO_RDP: int + IPPROTO_ST: int +if sys.platform != "win32": IPPROTO_EON: int IPPROTO_GRE: int IPPROTO_HELLO: int @@ -254,24 +173,78 @@ if sys.platform != "win32": IPPROTO_RSVP: int IPPROTO_TP: int IPPROTO_XTP: int - IPV6_RTHDR_TYPE_0: int +if sys.platform != "win32" and sys.platform != "darwin": + IPPROTO_BIP: int + IPPROTO_MOBILE: int + IPPROTO_VRRP: int +if sys.version_info >= (3, 9) and sys.platform == "linux": + # Availability: Linux >= 2.6.20, FreeBSD >= 10.1 + IPPROTO_UDPLITE: int +if sys.version_info >= (3, 10) and sys.platform == "linux": + IPPROTO_MPTCP: int + +IPPORT_RESERVED: int +IPPORT_USERRESERVED: int + +INADDR_ALLHOSTS_GROUP: int +INADDR_ANY: int +INADDR_BROADCAST: int +INADDR_LOOPBACK: int +INADDR_MAX_LOCAL_GROUP: int +INADDR_NONE: int +INADDR_UNSPEC_GROUP: int + +IP_ADD_MEMBERSHIP: int +IP_DROP_MEMBERSHIP: int +IP_HDRINCL: int +IP_MULTICAST_IF: int +IP_MULTICAST_LOOP: int +IP_MULTICAST_TTL: int +IP_OPTIONS: int +IP_RECVDSTADDR: int +if sys.version_info >= (3, 10): + IP_RECVTOS: int +elif sys.platform != "win32" and sys.platform != "darwin": + IP_RECVTOS: int +IP_TOS: int +IP_TTL: int +if sys.platform != "win32": IP_DEFAULT_MULTICAST_LOOP: int IP_DEFAULT_MULTICAST_TTL: int IP_MAX_MEMBERSHIPS: int IP_RECVOPTS: int IP_RECVRETOPTS: int IP_RETOPTS: int - LOCAL_PEERCRED: int - MSG_DONTWAIT: int - MSG_EOF: int - MSG_EOR: int - MSG_NOSIGNAL: int # Sometimes this exists on darwin, sometimes not - SCM_CREDS: int - SCM_RIGHTS: int - SO_REUSEPORT: int +if sys.platform != "win32" and sys.platform != "darwin": + IP_TRANSPARENT: int + IP_BIND_ADDRESS_NO_PORT: int +if sys.version_info >= (3, 12): + IP_ADD_SOURCE_MEMBERSHIP: int + IP_BLOCK_SOURCE: int + IP_DROP_SOURCE_MEMBERSHIP: int + IP_PKTINFO: int + IP_UNBLOCK_SOURCE: int +IPV6_CHECKSUM: int +IPV6_JOIN_GROUP: int +IPV6_LEAVE_GROUP: int +IPV6_MULTICAST_HOPS: int +IPV6_MULTICAST_IF: int +IPV6_MULTICAST_LOOP: int +IPV6_RECVTCLASS: int +IPV6_TCLASS: int +IPV6_UNICAST_HOPS: int +IPV6_V6ONLY: int +if sys.version_info >= (3, 9) or sys.platform != "darwin": + IPV6_DONTFRAG: int + IPV6_HOPLIMIT: int + IPV6_HOPOPTS: int + IPV6_PKTINFO: int + IPV6_RECVRTHDR: int + IPV6_RTHDR: int if sys.platform != "win32": - if sys.platform != "darwin" or sys.version_info >= (3, 9): + IPV6_RTHDR_TYPE_0: int + if sys.version_info >= (3, 9) or sys.platform != "darwin": IPV6_DSTOPTS: int IPV6_NEXTHOP: int IPV6_PATHMTU: int @@ -283,43 +256,74 @@ if sys.platform != "win32": IPV6_RTHDRDSTOPTS: int IPV6_USE_MIN_MTU: int +EAI_AGAIN: int +EAI_BADFLAGS: int +EAI_FAIL: int +EAI_FAMILY: int +EAI_MEMORY: int +EAI_NODATA: int +EAI_NONAME: int +EAI_SERVICE: int +EAI_SOCKTYPE: int +if sys.platform != "win32": + EAI_ADDRFAMILY: int + EAI_BADHINTS: int + EAI_MAX: int + EAI_OVERFLOW: int + EAI_PROTOCOL: int + EAI_SYSTEM: int + +AI_ADDRCONFIG: int +AI_ALL: int +AI_CANONNAME: int +AI_NUMERICHOST: int +AI_NUMERICSERV: int +AI_PASSIVE: int +AI_V4MAPPED: int +if sys.platform != "win32": + AI_DEFAULT: int + AI_MASK: int + AI_V4MAPPED_CFG: int + +NI_DGRAM: int +NI_MAXHOST: int +NI_MAXSERV: int +NI_NAMEREQD: int +NI_NOFQDN: int +NI_NUMERICHOST: int +NI_NUMERICSERV: int + +TCP_FASTOPEN: int +TCP_KEEPCNT: int +TCP_KEEPINTVL: int +TCP_MAXSEG: int +TCP_NODELAY: int +if sys.platform != "win32": + TCP_NOTSENT_LOWAT: int +if sys.platform != "darwin": + TCP_KEEPIDLE: int +if sys.version_info >= (3, 10) and sys.platform == "darwin": + TCP_KEEPALIVE: int +if sys.version_info >= (3, 11) and sys.platform == "darwin": + TCP_CONNECTION_INFO: int + if sys.platform != "win32" and sys.platform != "darwin": - IPPROTO_BIP: int - IPPROTO_MOBILE: int - IPPROTO_VRRP: int - IPX_TYPE: int - IP_TRANSPARENT: int - MSG_BTAG: int - MSG_CMSG_CLOEXEC: int - MSG_CONFIRM: int - MSG_ETAG: int - MSG_FASTOPEN: int - MSG_MORE: int - MSG_NOTIFICATION: int - SCM_CREDENTIALS: int - SOL_ATALK: int - SOL_AX25: int - SOL_HCI: int - SOL_IPX: int - SOL_NETROM: int - SOL_ROSE: int - SO_BINDTODEVICE: int - SO_MARK: int - SO_PASSCRED: int - SO_PEERCRED: int - SO_PRIORITY: int - SO_SETFIB: int + TCP_CONGESTION: int TCP_CORK: int TCP_DEFER_ACCEPT: int TCP_INFO: int TCP_LINGER2: int TCP_QUICKACK: int TCP_SYNCNT: int + TCP_USER_TIMEOUT: int TCP_WINDOW_CLAMP: int -# Specifically-documented constants +# -------------------- +# Specifically documented constants +# -------------------- if sys.platform == "linux": + # Availability: Linux >= 2.6.25, NetBSD >= 8 AF_CAN: int PF_CAN: int SOL_CAN_BASE: int @@ -336,6 +340,8 @@ if sys.platform == "linux": CAN_RTR_FLAG: int CAN_SFF_MASK: int +if sys.platform == "linux": + # Availability: Linux >= 2.6.25 CAN_BCM: int CAN_BCM_TX_SETUP: int CAN_BCM_TX_DELETE: int @@ -349,10 +355,6 @@ if sys.platform == "linux": CAN_BCM_RX_STATUS: int CAN_BCM_RX_TIMEOUT: int CAN_BCM_RX_CHANGED: int - - CAN_RAW_FD_FRAMES: int - -if sys.platform == "linux" and sys.version_info >= (3, 8): CAN_BCM_SETTIMER: int CAN_BCM_STARTTIMER: int CAN_BCM_TX_COUNTEVT: int @@ -367,11 +369,20 @@ if sys.platform == "linux" and sys.version_info >= (3, 8): CAN_BCM_CAN_FD_FRAME: int if sys.platform == "linux": + # Availability: Linux >= 3.6 + CAN_RAW_FD_FRAMES: int + +if sys.platform == "linux" and sys.version_info >= (3, 9): + # Availability: Linux >= 4.1 + CAN_RAW_JOIN_FILTERS: int + +if sys.platform == "linux": + # Availability: Linux >= 2.6.25 CAN_ISOTP: int if sys.platform == "linux" and sys.version_info >= (3, 9): + # Availability: Linux >= 5.4 CAN_J1939: int - CAN_RAW_JOIN_FILTERS: int J1939_MAX_UNICAST_ADDR: int J1939_IDLE_ADDR: int @@ -396,16 +407,17 @@ if sys.platform == "linux" and sys.version_info >= (3, 9): J1939_NLA_PAD: int J1939_NLA_BYTES_ACKED: int - J1939_EE_INFO_NONE: int J1939_EE_INFO_TX_ABORT: int - J1939_FILTER_MAX: int -if sys.platform == "linux" and sys.version_info >= (3, 10): - IPPROTO_MPTCP: int +if sys.version_info >= (3, 12) and sys.platform != "linux" and sys.platform != "win32" and sys.platform != "darwin": + # Availability: FreeBSD >= 14.0 + AF_DIVERT: int + PF_DIVERT: int if sys.platform == "linux": + # Availability: Linux >= 2.2 AF_PACKET: int PF_PACKET: int PACKET_BROADCAST: int @@ -416,7 +428,11 @@ if sys.platform == "linux": PACKET_OTHERHOST: int PACKET_OUTGOING: int +if sys.version_info >= (3, 12) and sys.platform == "linux": + ETH_P_ALL: int + if sys.platform == "linux": + # Availability: Linux >= 2.6.30 AF_RDS: int PF_RDS: int SOL_RDS: int @@ -476,6 +492,7 @@ if sys.platform == "linux": TIPC_ZONE_SCOPE: int if sys.platform == "linux": + # Availability: Linux >= 2.6.38 AF_ALG: int SOL_ALG: int ALG_OP_DECRYPT: int @@ -490,6 +507,7 @@ if sys.platform == "linux": ALG_SET_PUBKEY: int if sys.platform == "linux": + # Availability: Linux >= 4.8 (or maybe 3.9, CPython docs are confusing) AF_VSOCK: int IOCTL_VM_SOCKETS_GET_LOCAL_CID: int VMADDR_CID_ANY: int @@ -498,26 +516,65 @@ if sys.platform == "linux": SO_VM_SOCKETS_BUFFER_MAX_SIZE: int SO_VM_SOCKETS_BUFFER_SIZE: int SO_VM_SOCKETS_BUFFER_MIN_SIZE: int - VM_SOCKETS_INVALID_VERSION: int + VM_SOCKETS_INVALID_VERSION: int # undocumented if sys.platform != "win32" or sys.version_info >= (3, 9): + # Documented as only available on BSD, macOS, but empirically sometimes + # available on Windows AF_LINK: int -# BDADDR_* and HCI_* listed with other bluetooth constants below +has_ipv6: bool + +if sys.platform != "darwin": + if sys.platform != "win32" or sys.version_info >= (3, 9): + BDADDR_ANY: str + BDADDR_LOCAL: str if sys.platform != "win32" and sys.platform != "darwin": - SO_DOMAIN: int - SO_PASSSEC: int - SO_PEERSEC: int - SO_PROTOCOL: int - TCP_CONGESTION: int - TCP_USER_TIMEOUT: int + HCI_FILTER: int # not in NetBSD or DragonFlyBSD + HCI_TIME_STAMP: int # not in FreeBSD, NetBSD, or DragonFlyBSD + HCI_DATA_DIR: int # not in FreeBSD, NetBSD, or DragonFlyBSD -if sys.platform == "linux" and sys.version_info >= (3, 8): - AF_QIPCRTR: int +if sys.platform == "linux": + AF_QIPCRTR: int # Availability: Linux >= 4.7 + +if sys.version_info >= (3, 11) and sys.platform != "linux" and sys.platform != "win32" and sys.platform != "darwin": + # FreeBSD + SCM_CREDS2: int + LOCAL_CREDS: int + LOCAL_CREDS_PERSISTENT: int + +if sys.version_info >= (3, 11) and sys.platform == "linux": + SO_INCOMING_CPU: int # Availability: Linux >= 3.9 + +if sys.version_info >= (3, 12) and sys.platform == "win32": + # Availability: Windows + AF_HYPERV: int + HV_PROTOCOL_RAW: int + HVSOCKET_CONNECT_TIMEOUT: int + HVSOCKET_CONNECT_TIMEOUT_MAX: int + HVSOCKET_CONNECTED_SUSPEND: int + HVSOCKET_ADDRESS_FLAG_PASSTHRU: int + HV_GUID_ZERO: str + HV_GUID_WILDCARD: str + HV_GUID_BROADCAST: str + HV_GUID_CHILDREN: str + HV_GUID_LOOPBACK: str + HV_GUID_PARENT: str + +if sys.version_info >= (3, 12): + if sys.platform != "win32": + # Availability: Linux, FreeBSD, macOS + ETHERTYPE_ARP: int + ETHERTYPE_IP: int + ETHERTYPE_IPV6: int + ETHERTYPE_VLAN: int +# -------------------- # Semi-documented constants -# (Listed under "Socket families" in the docs, but not "Constants") +# These are alluded to under the "Socket families" section in the docs +# https://docs.python.org/3/library/socket.html#socket-families +# -------------------- if sys.platform == "linux": # Netlink is defined by Linux @@ -537,12 +594,13 @@ if sys.platform == "linux": NETLINK_W1: int NETLINK_XFRM: int +if sys.platform == "darwin": + PF_SYSTEM: int + SYSPROTO_CONTROL: int + if sys.platform != "darwin": - if sys.platform != "win32" or sys.version_info >= (3, 9): + if sys.version_info >= (3, 9) or sys.platform != "win32": AF_BLUETOOTH: int - BDADDR_ANY: str - BDADDR_LOCAL: str - BTPROTO_RFCOMM: int if sys.platform != "win32" and sys.platform != "darwin": # Linux and some BSD support is explicit in the docs @@ -550,17 +608,65 @@ if sys.platform != "win32" and sys.platform != "darwin": BTPROTO_HCI: int BTPROTO_L2CAP: int BTPROTO_SCO: int # not in FreeBSD - HCI_FILTER: int # not in NetBSD or DragonFlyBSD - # not in FreeBSD, NetBSD, or DragonFlyBSD - HCI_TIME_STAMP: int - HCI_DATA_DIR: int +if sys.platform != "darwin": + if sys.version_info >= (3, 9) or sys.platform != "win32": + BTPROTO_RFCOMM: int -if sys.platform == "darwin": - # PF_SYSTEM is defined by macOS - PF_SYSTEM: int - SYSPROTO_CONTROL: int +if sys.version_info >= (3, 9) and sys.platform == "linux": + UDPLITE_RECV_CSCOV: int + UDPLITE_SEND_CSCOV: int + +# -------------------- +# Documented under socket.shutdown +# -------------------- +SHUT_RD: int +SHUT_RDWR: int +SHUT_WR: int + +# -------------------- +# Undocumented constants +# -------------------- + +# Undocumented address families +AF_APPLETALK: int +AF_DECnet: int +AF_IPX: int +AF_SNA: int + +if sys.platform != "win32": + AF_ROUTE: int + AF_SYSTEM: int + +if sys.platform != "darwin": + AF_IRDA: int -# ----- Exceptions ----- +if sys.platform != "win32" and sys.platform != "darwin": + AF_AAL5: int + AF_ASH: int + AF_ATMPVC: int + AF_ATMSVC: int + AF_AX25: int + AF_BRIDGE: int + AF_ECONET: int + AF_KEY: int + AF_LLC: int + AF_NETBEUI: int + AF_NETROM: int + AF_PPPOX: int + AF_ROSE: int + AF_SECURITY: int + AF_WANPIPE: int + AF_X25: int + +# Miscellaneous undocumented + +if sys.platform != "win32": + LOCAL_PEERCRED: int + +if sys.platform != "win32" and sys.platform != "darwin": + IPX_TYPE: int + +# ===== Exceptions ===== error = OSError @@ -572,7 +678,7 @@ if sys.version_info >= (3, 10): else: class timeout(error): ... -# ----- Classes ----- +# ===== Classes ===== class socket: @property @@ -584,74 +690,77 @@ class socket: @property def timeout(self) -> float | None: ... if sys.platform == "win32": - def __init__(self, family: int = ..., type: int = ..., proto: int = ..., fileno: _FD | bytes | None = ...) -> None: ... + def __init__( + self, family: int = ..., type: int = ..., proto: int = ..., fileno: SupportsIndex | bytes | None = ... + ) -> None: ... else: - def __init__(self, family: int = ..., type: int = ..., proto: int = ..., fileno: _FD | None = ...) -> None: ... + def __init__(self, family: int = ..., type: int = ..., proto: int = ..., fileno: SupportsIndex | None = ...) -> None: ... - def bind(self, __address: _Address) -> None: ... + def bind(self, address: _Address, /) -> None: ... def close(self) -> None: ... - def connect(self, __address: _Address) -> None: ... - def connect_ex(self, __address: _Address) -> int: ... + def connect(self, address: _Address, /) -> None: ... + def connect_ex(self, address: _Address, /) -> int: ... def detach(self) -> int: ... def fileno(self) -> int: ... def getpeername(self) -> _RetAddress: ... def getsockname(self) -> _RetAddress: ... @overload - def getsockopt(self, __level: int, __optname: int) -> int: ... + def getsockopt(self, level: int, optname: int, /) -> int: ... @overload - def getsockopt(self, __level: int, __optname: int, __buflen: int) -> bytes: ... + def getsockopt(self, level: int, optname: int, buflen: int, /) -> bytes: ... def getblocking(self) -> bool: ... def gettimeout(self) -> float | None: ... if sys.platform == "win32": - def ioctl(self, __control: int, __option: int | tuple[int, int, int] | bool) -> None: ... + def ioctl(self, control: int, option: int | tuple[int, int, int] | bool, /) -> None: ... - def listen(self, __backlog: int = ...) -> None: ... - def recv(self, __bufsize: int, __flags: int = ...) -> bytes: ... - def recvfrom(self, __bufsize: int, __flags: int = ...) -> tuple[bytes, _RetAddress]: ... + def listen(self, backlog: int = ..., /) -> None: ... + def recv(self, bufsize: int, flags: int = ..., /) -> bytes: ... + def recvfrom(self, bufsize: int, flags: int = ..., /) -> tuple[bytes, _RetAddress]: ... if sys.platform != "win32": - def recvmsg(self, __bufsize: int, __ancbufsize: int = ..., __flags: int = ...) -> tuple[bytes, list[_CMSG], int, Any]: ... + def recvmsg(self, bufsize: int, ancbufsize: int = ..., flags: int = ..., /) -> tuple[bytes, list[_CMSG], int, Any]: ... def recvmsg_into( - self, __buffers: Iterable[WriteableBuffer], __ancbufsize: int = ..., __flags: int = ... + self, buffers: Iterable[WriteableBuffer], ancbufsize: int = ..., flags: int = ..., / ) -> tuple[int, list[_CMSG], int, Any]: ... def recvfrom_into(self, buffer: WriteableBuffer, nbytes: int = ..., flags: int = ...) -> tuple[int, _RetAddress]: ... def recv_into(self, buffer: WriteableBuffer, nbytes: int = ..., flags: int = ...) -> int: ... - def send(self, __data: ReadableBuffer, __flags: int = ...) -> int: ... - def sendall(self, __data: ReadableBuffer, __flags: int = ...) -> None: ... + def send(self, data: ReadableBuffer, flags: int = ..., /) -> int: ... + def sendall(self, data: ReadableBuffer, flags: int = ..., /) -> None: ... @overload - def sendto(self, __data: ReadableBuffer, __address: _Address) -> int: ... + def sendto(self, data: ReadableBuffer, address: _Address, /) -> int: ... @overload - def sendto(self, __data: ReadableBuffer, __flags: int, __address: _Address) -> int: ... + def sendto(self, data: ReadableBuffer, flags: int, address: _Address, /) -> int: ... if sys.platform != "win32": def sendmsg( self, - __buffers: Iterable[ReadableBuffer], - __ancdata: Iterable[_CMSGArg] = ..., - __flags: int = ..., - __address: _Address | None = ..., + buffers: Iterable[ReadableBuffer], + ancdata: Iterable[_CMSGArg] = ..., + flags: int = ..., + address: _Address | None = ..., + /, ) -> int: ... if sys.platform == "linux": def sendmsg_afalg( self, msg: Iterable[ReadableBuffer] = ..., *, op: int, iv: Any = ..., assoclen: int = ..., flags: int = ... ) -> int: ... - def setblocking(self, __flag: bool) -> None: ... - def settimeout(self, __value: float | None) -> None: ... + def setblocking(self, flag: bool, /) -> None: ... + def settimeout(self, value: float | None, /) -> None: ... @overload - def setsockopt(self, __level: int, __optname: int, __value: int | ReadableBuffer) -> None: ... + def setsockopt(self, level: int, optname: int, value: int | ReadableBuffer, /) -> None: ... @overload - def setsockopt(self, __level: int, __optname: int, __value: None, __optlen: int) -> None: ... + def setsockopt(self, level: int, optname: int, value: None, optlen: int, /) -> None: ... if sys.platform == "win32": - def share(self, __process_id: int) -> bytes: ... + def share(self, process_id: int, /) -> bytes: ... - def shutdown(self, __how: int) -> None: ... + def shutdown(self, how: int, /) -> None: ... SocketType = socket -# ----- Functions ----- +# ===== Functions ===== -def close(__fd: _FD) -> None: ... -def dup(__fd: _FD) -> int: ... +def close(fd: SupportsIndex, /) -> None: ... +def dup(fd: SupportsIndex, /) -> int: ... # the 5th tuple item is an address def getaddrinfo( @@ -662,33 +771,33 @@ def getaddrinfo( proto: int = ..., flags: int = ..., ) -> list[tuple[int, int, int, str, tuple[str, int] | tuple[str, int, int, int]]]: ... -def gethostbyname(__hostname: str) -> str: ... -def gethostbyname_ex(__hostname: str) -> tuple[str, list[str], list[str]]: ... +def gethostbyname(hostname: str, /) -> str: ... +def gethostbyname_ex(hostname: str, /) -> tuple[str, list[str], list[str]]: ... def gethostname() -> str: ... -def gethostbyaddr(__ip_address: str) -> tuple[str, list[str], list[str]]: ... -def getnameinfo(__sockaddr: tuple[str, int] | tuple[str, int, int, int], __flags: int) -> tuple[str, str]: ... -def getprotobyname(__protocolname: str) -> int: ... -def getservbyname(__servicename: str, __protocolname: str = ...) -> int: ... -def getservbyport(__port: int, __protocolname: str = ...) -> str: ... -def ntohl(__x: int) -> int: ... # param & ret val are 32-bit ints -def ntohs(__x: int) -> int: ... # param & ret val are 16-bit ints -def htonl(__x: int) -> int: ... # param & ret val are 32-bit ints -def htons(__x: int) -> int: ... # param & ret val are 16-bit ints -def inet_aton(__ip_string: str) -> bytes: ... # ret val 4 bytes in length -def inet_ntoa(__packed_ip: ReadableBuffer) -> str: ... -def inet_pton(__address_family: int, __ip_string: str) -> bytes: ... -def inet_ntop(__address_family: int, __packed_ip: ReadableBuffer) -> str: ... +def gethostbyaddr(ip_address: str, /) -> tuple[str, list[str], list[str]]: ... +def getnameinfo(sockaddr: tuple[str, int] | tuple[str, int, int, int], flags: int, /) -> tuple[str, str]: ... +def getprotobyname(protocolname: str, /) -> int: ... +def getservbyname(servicename: str, protocolname: str = ..., /) -> int: ... +def getservbyport(port: int, protocolname: str = ..., /) -> str: ... +def ntohl(x: int, /) -> int: ... # param & ret val are 32-bit ints +def ntohs(x: int, /) -> int: ... # param & ret val are 16-bit ints +def htonl(x: int, /) -> int: ... # param & ret val are 32-bit ints +def htons(x: int, /) -> int: ... # param & ret val are 16-bit ints +def inet_aton(ip_string: str, /) -> bytes: ... # ret val 4 bytes in length +def inet_ntoa(packed_ip: ReadableBuffer, /) -> str: ... +def inet_pton(address_family: int, ip_string: str, /) -> bytes: ... +def inet_ntop(address_family: int, packed_ip: ReadableBuffer, /) -> str: ... def getdefaulttimeout() -> float | None: ... -def setdefaulttimeout(__timeout: float | None) -> None: ... +def setdefaulttimeout(timeout: float | None, /) -> None: ... if sys.platform != "win32": - def sethostname(__name: str) -> None: ... - def CMSG_LEN(__length: int) -> int: ... - def CMSG_SPACE(__length: int) -> int: ... - def socketpair(__family: int = ..., __type: int = ..., __proto: int = ...) -> tuple[socket, socket]: ... - -# Windows added these in 3.8, but didn't have them before -if sys.platform != "win32" or sys.version_info >= (3, 8): - def if_nameindex() -> list[tuple[int, str]]: ... - def if_nametoindex(__name: str) -> int: ... - def if_indextoname(__index: int) -> str: ... + def sethostname(name: str, /) -> None: ... + def CMSG_LEN(length: int, /) -> int: ... + def CMSG_SPACE(length: int, /) -> int: ... + def socketpair(family: int = ..., type: int = ..., proto: int = ..., /) -> tuple[socket, socket]: ... + +def if_nameindex() -> list[tuple[int, str]]: ... +def if_nametoindex(name: str, /) -> int: ... +def if_indextoname(index: int, /) -> str: ... + +CAPI: object diff --git a/mypy/typeshed/stdlib/_stat.pyi b/mypy/typeshed/stdlib/_stat.pyi index 83d832e4dd8e..347897404edc 100644 --- a/mypy/typeshed/stdlib/_stat.pyi +++ b/mypy/typeshed/stdlib/_stat.pyi @@ -1,5 +1,5 @@ import sys -from typing_extensions import Literal +from typing import Literal SF_APPEND: Literal[0x00040000] SF_ARCHIVED: Literal[0x00010000] @@ -78,7 +78,7 @@ def S_ISSOCK(mode: int) -> bool: ... def S_ISWHT(mode: int) -> bool: ... def filemode(mode: int) -> str: ... -if sys.platform == "win32" and sys.version_info >= (3, 8): +if sys.platform == "win32": IO_REPARSE_TAG_SYMLINK: int IO_REPARSE_TAG_MOUNT_POINT: int IO_REPARSE_TAG_APPEXECLINK: int diff --git a/mypy/typeshed/stdlib/_thread.pyi b/mypy/typeshed/stdlib/_thread.pyi index 152362edcaea..4ea9aa0609e5 100644 --- a/mypy/typeshed/stdlib/_thread.pyi +++ b/mypy/typeshed/stdlib/_thread.pyi @@ -3,8 +3,10 @@ from _typeshed import structseq from collections.abc import Callable from threading import Thread from types import TracebackType -from typing import Any, NoReturn -from typing_extensions import Final, final +from typing import Any, Final, NoReturn, final, overload +from typing_extensions import TypeVarTuple, Unpack + +_Ts = TypeVarTuple("_Ts") error = RuntimeError @@ -19,7 +21,10 @@ class LockType: self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None ) -> None: ... -def start_new_thread(function: Callable[..., object], args: tuple[Any, ...], kwargs: dict[str, Any] = ...) -> int: ... +@overload +def start_new_thread(function: Callable[[Unpack[_Ts]], object], args: tuple[Unpack[_Ts]]) -> int: ... +@overload +def start_new_thread(function: Callable[..., object], args: tuple[Any, ...], kwargs: dict[str, Any]) -> int: ... def interrupt_main() -> None: ... def exit() -> NoReturn: ... def allocate_lock() -> LockType: ... @@ -28,18 +33,27 @@ def stack_size(size: int = ...) -> int: ... TIMEOUT_MAX: float -if sys.version_info >= (3, 8): - def get_native_id() -> int: ... # only available on some platforms - @final - class _ExceptHookArgs(structseq[Any], tuple[type[BaseException], BaseException | None, TracebackType | None, Thread | None]): - if sys.version_info >= (3, 10): - __match_args__: Final = ("exc_type", "exc_value", "exc_traceback", "thread") - @property - def exc_type(self) -> type[BaseException]: ... - @property - def exc_value(self) -> BaseException | None: ... - @property - def exc_traceback(self) -> TracebackType | None: ... - @property - def thread(self) -> Thread | None: ... - _excepthook: Callable[[_ExceptHookArgs], Any] +def get_native_id() -> int: ... # only available on some platforms +@final +class _ExceptHookArgs(structseq[Any], tuple[type[BaseException], BaseException | None, TracebackType | None, Thread | None]): + if sys.version_info >= (3, 10): + __match_args__: Final = ("exc_type", "exc_value", "exc_traceback", "thread") + + @property + def exc_type(self) -> type[BaseException]: ... + @property + def exc_value(self) -> BaseException | None: ... + @property + def exc_traceback(self) -> TracebackType | None: ... + @property + def thread(self) -> Thread | None: ... + +_excepthook: Callable[[_ExceptHookArgs], Any] + +if sys.version_info >= (3, 12): + def daemon_threads_allowed() -> bool: ... + +class _local: + def __getattribute__(self, name: str, /) -> Any: ... + def __setattr__(self, name: str, value: Any, /) -> None: ... + def __delattr__(self, name: str, /) -> None: ... diff --git a/mypy/typeshed/stdlib/_tkinter.pyi b/mypy/typeshed/stdlib/_tkinter.pyi index 271fd37df68b..3340df424163 100644 --- a/mypy/typeshed/stdlib/_tkinter.pyi +++ b/mypy/typeshed/stdlib/_tkinter.pyi @@ -1,6 +1,5 @@ import sys -from typing import Any, ClassVar -from typing_extensions import Literal, final +from typing import Any, ClassVar, Literal, final # _tkinter is meant to be only used internally by tkinter, but some tkinter # functions e.g. return _tkinter.Tcl_Obj objects. Tcl_Obj represents a Tcl @@ -22,12 +21,12 @@ class Tcl_Obj: @property def typename(self) -> str: ... __hash__: ClassVar[None] # type: ignore[assignment] - def __eq__(self, __other): ... - def __ge__(self, __other): ... - def __gt__(self, __other): ... - def __le__(self, __other): ... - def __lt__(self, __other): ... - def __ne__(self, __other): ... + def __eq__(self, value, /): ... + def __ge__(self, value, /): ... + def __gt__(self, value, /): ... + def __le__(self, value, /): ... + def __lt__(self, value, /): ... + def __ne__(self, value, /): ... class TclError(Exception): ... @@ -51,39 +50,39 @@ class TclError(Exception): ... @final class TkappType: # Please keep in sync with tkinter.Tk - def adderrorinfo(self, __msg): ... - def call(self, __command: Any, *args: Any) -> Any: ... - def createcommand(self, __name, __func): ... + def adderrorinfo(self, msg, /): ... + def call(self, command: Any, /, *args: Any) -> Any: ... + def createcommand(self, name, func, /): ... if sys.platform != "win32": - def createfilehandler(self, __file, __mask, __func): ... - def deletefilehandler(self, __file): ... + def createfilehandler(self, file, mask, func, /): ... + def deletefilehandler(self, file, /): ... - def createtimerhandler(self, __milliseconds, __func): ... - def deletecommand(self, __name): ... - def dooneevent(self, __flags: int = 0): ... - def eval(self, __script: str) -> str: ... - def evalfile(self, __fileName): ... - def exprboolean(self, __s): ... - def exprdouble(self, __s): ... - def exprlong(self, __s): ... - def exprstring(self, __s): ... - def getboolean(self, __arg): ... - def getdouble(self, __arg): ... - def getint(self, __arg): ... + def createtimerhandler(self, milliseconds, func, /): ... + def deletecommand(self, name, /): ... + def dooneevent(self, flags: int = 0, /): ... + def eval(self, script: str, /) -> str: ... + def evalfile(self, fileName, /): ... + def exprboolean(self, s, /): ... + def exprdouble(self, s, /): ... + def exprlong(self, s, /): ... + def exprstring(self, s, /): ... + def getboolean(self, arg, /): ... + def getdouble(self, arg, /): ... + def getint(self, arg, /): ... def getvar(self, *args, **kwargs): ... def globalgetvar(self, *args, **kwargs): ... def globalsetvar(self, *args, **kwargs): ... def globalunsetvar(self, *args, **kwargs): ... def interpaddr(self): ... def loadtk(self) -> None: ... - def mainloop(self, __threshold: int = 0): ... + def mainloop(self, threshold: int = 0, /): ... def quit(self): ... - def record(self, __script): ... + def record(self, script, /): ... def setvar(self, *ags, **kwargs): ... if sys.version_info < (3, 11): - def split(self, __arg): ... + def split(self, arg, /): ... - def splitlist(self, __arg): ... + def splitlist(self, arg, /): ... def unsetvar(self, *args, **kwargs): ... def wantobjects(self, *args, **kwargs): ... def willdispatch(self): ... @@ -107,29 +106,16 @@ TK_VERSION: str class TkttType: def deletetimerhandler(self): ... -if sys.version_info >= (3, 8): - def create( - __screenName: str | None = None, - __baseName: str = "", - __className: str = "Tk", - __interactive: bool = False, - __wantobjects: bool = False, - __wantTk: bool = True, - __sync: bool = False, - __use: str | None = None, - ): ... - -else: - def create( - __screenName: str | None = None, - __baseName: str | None = None, - __className: str = "Tk", - __interactive: bool = False, - __wantobjects: bool = False, - __wantTk: bool = True, - __sync: bool = False, - __use: str | None = None, - ): ... - +def create( + screenName: str | None = None, + baseName: str = "", + className: str = "Tk", + interactive: bool = False, + wantobjects: bool = False, + wantTk: bool = True, + sync: bool = False, + use: str | None = None, + /, +): ... def getbusywaitinterval(): ... -def setbusywaitinterval(__new_val): ... +def setbusywaitinterval(new_val, /): ... diff --git a/mypy/typeshed/stdlib/_tracemalloc.pyi b/mypy/typeshed/stdlib/_tracemalloc.pyi index 1b79d9dc5785..b1aeb710233e 100644 --- a/mypy/typeshed/stdlib/_tracemalloc.pyi +++ b/mypy/typeshed/stdlib/_tracemalloc.pyi @@ -2,7 +2,7 @@ import sys from collections.abc import Sequence from tracemalloc import _FrameTuple, _TraceTuple -def _get_object_traceback(__obj: object) -> Sequence[_FrameTuple] | None: ... +def _get_object_traceback(obj: object, /) -> Sequence[_FrameTuple] | None: ... def _get_traces() -> Sequence[_TraceTuple]: ... def clear_traces() -> None: ... def get_traceback_limit() -> int: ... @@ -13,5 +13,5 @@ def is_tracing() -> bool: ... if sys.version_info >= (3, 9): def reset_peak() -> None: ... -def start(__nframe: int = 1) -> None: ... +def start(nframe: int = 1, /) -> None: ... def stop() -> None: ... diff --git a/mypy/typeshed/stdlib/_typeshed/__init__.pyi b/mypy/typeshed/stdlib/_typeshed/__init__.pyi index d0c6b3ab1173..6937d97b87ea 100644 --- a/mypy/typeshed/stdlib/_typeshed/__init__.pyi +++ b/mypy/typeshed/stdlib/_typeshed/__init__.pyi @@ -2,17 +2,27 @@ # # See the README.md file in this directory for more information. -import array -import ctypes -import mmap -import pickle import sys -from collections.abc import Awaitable, Callable, Iterable, Set as AbstractSet +from collections.abc import Awaitable, Callable, Iterable, Sequence, Set as AbstractSet, Sized from dataclasses import Field from os import PathLike from types import FrameType, TracebackType -from typing import Any, AnyStr, ClassVar, Generic, Protocol, TypeVar -from typing_extensions import Final, Literal, LiteralString, TypeAlias, final +from typing import ( + Any, + AnyStr, + ClassVar, + Final, + Generic, + Literal, + Protocol, + SupportsFloat, + SupportsIndex, + SupportsInt, + TypeVar, + final, + overload, +) +from typing_extensions import Buffer, LiteralString, TypeAlias _KT = TypeVar("_KT") _KT_co = TypeVar("_KT_co", covariant=True) @@ -23,8 +33,10 @@ _T = TypeVar("_T") _T_co = TypeVar("_T_co", covariant=True) _T_contra = TypeVar("_T_contra", contravariant=True) -# Use for "self" annotations: -# def __enter__(self: Self) -> Self: ... +# Alternative to `typing_extensions.Self`, exclusively for use with `__new__` +# in metaclasses: +# def __new__(cls: type[Self], ...) -> Self: ... +# In other cases, use `typing_extensions.Self`. Self = TypeVar("Self") # noqa: Y001 # covariant version of typing.AnyStr, useful for protocols @@ -35,14 +47,32 @@ AnyStr_co = TypeVar("AnyStr_co", str, bytes, covariant=True) # noqa: Y001 # isn't possible or a type is already partially known. In cases like these, # use Incomplete instead of Any as a marker. For example, use # "Incomplete | None" instead of "Any | None". -Incomplete: TypeAlias = Any +Incomplete: TypeAlias = Any # stable # To describe a function parameter that is unused and will work with anything. -Unused: TypeAlias = object +Unused: TypeAlias = object # stable + +# Marker for return types that include None, but where forcing the user to +# check for None can be detrimental. Sometimes called "the Any trick". See +# CONTRIBUTING.md for more information. +MaybeNone: TypeAlias = Any # stable + +# Used to mark arguments that default to a sentinel value. This prevents +# stubtest from complaining about the default value not matching. +# +# def foo(x: int | None = sentinel) -> None: ... +# +# In cases where the sentinel object is exported and can be used by user code, +# a construct like this is better: +# +# _SentinelType = NewType("_SentinelType", object) +# sentinel: _SentinelType +# def foo(x: int | None | _SentinelType = ...) -> None: ... +sentinel: Any # stable class IdentityFunction(Protocol): - def __call__(self, __x: _T) -> _T: ... + def __call__(self, x: _T, /) -> _T: ... # stable class SupportsNext(Protocol[_T_co]): @@ -55,16 +85,16 @@ class SupportsAnext(Protocol[_T_co]): # Comparison protocols class SupportsDunderLT(Protocol[_T_contra]): - def __lt__(self, __other: _T_contra) -> bool: ... + def __lt__(self, other: _T_contra, /) -> bool: ... class SupportsDunderGT(Protocol[_T_contra]): - def __gt__(self, __other: _T_contra) -> bool: ... + def __gt__(self, other: _T_contra, /) -> bool: ... class SupportsDunderLE(Protocol[_T_contra]): - def __le__(self, __other: _T_contra) -> bool: ... + def __le__(self, other: _T_contra, /) -> bool: ... class SupportsDunderGE(Protocol[_T_contra]): - def __ge__(self, __other: _T_contra) -> bool: ... + def __ge__(self, other: _T_contra, /) -> bool: ... class SupportsAllComparisons( SupportsDunderLT[Any], SupportsDunderGT[Any], SupportsDunderLE[Any], SupportsDunderGE[Any], Protocol @@ -76,22 +106,22 @@ SupportsRichComparisonT = TypeVar("SupportsRichComparisonT", bound=SupportsRichC # Dunder protocols class SupportsAdd(Protocol[_T_contra, _T_co]): - def __add__(self, __x: _T_contra) -> _T_co: ... + def __add__(self, x: _T_contra, /) -> _T_co: ... class SupportsRAdd(Protocol[_T_contra, _T_co]): - def __radd__(self, __x: _T_contra) -> _T_co: ... + def __radd__(self, x: _T_contra, /) -> _T_co: ... class SupportsSub(Protocol[_T_contra, _T_co]): - def __sub__(self, __x: _T_contra) -> _T_co: ... + def __sub__(self, x: _T_contra, /) -> _T_co: ... class SupportsRSub(Protocol[_T_contra, _T_co]): - def __rsub__(self, __x: _T_contra) -> _T_co: ... + def __rsub__(self, x: _T_contra, /) -> _T_co: ... class SupportsDivMod(Protocol[_T_contra, _T_co]): - def __divmod__(self, __other: _T_contra) -> _T_co: ... + def __divmod__(self, other: _T_contra, /) -> _T_co: ... class SupportsRDivMod(Protocol[_T_contra, _T_co]): - def __rdivmod__(self, __other: _T_contra) -> _T_co: ... + def __rdivmod__(self, other: _T_contra, /) -> _T_co: ... # This protocol is generic over the iterator type, while Iterable is # generic over the type that is iterated over. @@ -105,7 +135,7 @@ class SupportsAiter(Protocol[_T_co]): class SupportsLenAndGetItem(Protocol[_T_co]): def __len__(self) -> int: ... - def __getitem__(self, __k: int) -> _T_co: ... + def __getitem__(self, k: int, /) -> _T_co: ... class SupportsTrunc(Protocol): def __trunc__(self) -> int: ... @@ -119,17 +149,26 @@ class SupportsItems(Protocol[_KT_co, _VT_co]): # stable class SupportsKeysAndGetItem(Protocol[_KT, _VT_co]): def keys(self) -> Iterable[_KT]: ... - def __getitem__(self, __key: _KT) -> _VT_co: ... + def __getitem__(self, key: _KT, /) -> _VT_co: ... -# stable +# This protocol is currently under discussion. Use SupportsContainsAndGetItem +# instead, if you require the __contains__ method. +# See https://github.com/python/typeshed/issues/11822. class SupportsGetItem(Protocol[_KT_contra, _VT_co]): - def __contains__(self, __x: Any) -> bool: ... - def __getitem__(self, __key: _KT_contra) -> _VT_co: ... + def __contains__(self, x: Any, /) -> bool: ... + def __getitem__(self, key: _KT_contra, /) -> _VT_co: ... + +# stable +class SupportsContainsAndGetItem(Protocol[_KT_contra, _VT_co]): + def __contains__(self, x: Any, /) -> bool: ... + def __getitem__(self, key: _KT_contra, /) -> _VT_co: ... # stable -class SupportsItemAccess(SupportsGetItem[_KT_contra, _VT], Protocol[_KT_contra, _VT]): - def __setitem__(self, __key: _KT_contra, __value: _VT) -> None: ... - def __delitem__(self, __key: _KT_contra) -> None: ... +class SupportsItemAccess(Protocol[_KT_contra, _VT]): + def __contains__(self, x: Any, /) -> bool: ... + def __getitem__(self, key: _KT_contra, /) -> _VT: ... + def __setitem__(self, key: _KT_contra, value: _VT, /) -> None: ... + def __delitem__(self, key: _KT_contra, /) -> None: ... StrPath: TypeAlias = str | PathLike[str] # stable BytesPath: TypeAlias = bytes | PathLike[bytes] # stable @@ -213,11 +252,11 @@ FileDescriptorOrPath: TypeAlias = int | StrOrBytesPath # stable class SupportsRead(Protocol[_T_co]): - def read(self, __length: int = ...) -> _T_co: ... + def read(self, length: int = ..., /) -> _T_co: ... # stable class SupportsReadline(Protocol[_T_co]): - def readline(self, __length: int = ...) -> _T_co: ... + def readline(self, length: int = ..., /) -> _T_co: ... # stable class SupportsNoArgReadline(Protocol[_T_co]): @@ -225,44 +264,39 @@ class SupportsNoArgReadline(Protocol[_T_co]): # stable class SupportsWrite(Protocol[_T_contra]): - def write(self, __s: _T_contra) -> object: ... + def write(self, s: _T_contra, /) -> object: ... -ReadOnlyBuffer: TypeAlias = bytes # stable +# stable +class SupportsFlush(Protocol): + def flush(self) -> object: ... + +# Unfortunately PEP 688 does not allow us to distinguish read-only +# from writable buffers. We use these aliases for readability for now. +# Perhaps a future extension of the buffer protocol will allow us to +# distinguish these cases in the type system. +ReadOnlyBuffer: TypeAlias = Buffer # stable # Anything that implements the read-write buffer interface. -# The buffer interface is defined purely on the C level, so we cannot define a normal Protocol -# for it (until PEP 688 is implemented). Instead we have to list the most common stdlib buffer classes in a Union. -if sys.version_info >= (3, 8): - WriteableBuffer: TypeAlias = ( - bytearray | memoryview | array.array[Any] | mmap.mmap | ctypes._CData | pickle.PickleBuffer - ) # stable -else: - WriteableBuffer: TypeAlias = bytearray | memoryview | array.array[Any] | mmap.mmap | ctypes._CData # stable -# Same as _WriteableBuffer, but also includes read-only buffer types (like bytes). -ReadableBuffer: TypeAlias = ReadOnlyBuffer | WriteableBuffer # stable -_BufferWithLen: TypeAlias = ReadableBuffer # not stable # noqa: Y047 - -# Anything that implements the read-write buffer interface, and can be sliced/indexed. -SliceableBuffer: TypeAlias = bytes | bytearray | memoryview | array.array[Any] | mmap.mmap -IndexableBuffer: TypeAlias = bytes | bytearray | memoryview | array.array[Any] | mmap.mmap -# https://github.com/python/typeshed/pull/9115#issuecomment-1304905864 -# Post PEP 688, they should be rewritten as such: -# from collections.abc import Sequence -# from typing import Sized, overload -# class SliceableBuffer(Protocol): -# def __buffer__(self, __flags: int) -> memoryview: ... -# def __getitem__(self, __slice: slice) -> Sequence[int]: ... -# class IndexableBuffer(Protocol): -# def __buffer__(self, __flags: int) -> memoryview: ... -# def __getitem__(self, __i: int) -> int: ... -# class SupportsGetItemBuffer(SliceableBuffer, IndexableBuffer, Protocol): -# def __buffer__(self, __flags: int) -> memoryview: ... -# def __contains__(self, __x: Any) -> bool: ... -# @overload -# def __getitem__(self, __slice: slice) -> Sequence[int]: ... -# @overload -# def __getitem__(self, __i: int) -> int: ... -# class SizedBuffer(Sized, Protocol): # instead of _BufferWithLen -# def __buffer__(self, __flags: int) -> memoryview: ... +WriteableBuffer: TypeAlias = Buffer +# Same as WriteableBuffer, but also includes read-only buffer types (like bytes). +ReadableBuffer: TypeAlias = Buffer # stable + +class SliceableBuffer(Buffer, Protocol): + def __getitem__(self, slice: slice, /) -> Sequence[int]: ... + +class IndexableBuffer(Buffer, Protocol): + def __getitem__(self, i: int, /) -> int: ... + +class SupportsGetItemBuffer(SliceableBuffer, IndexableBuffer, Protocol): + def __contains__(self, x: Any, /) -> bool: ... + @overload + def __getitem__(self, slice: slice, /) -> Sequence[int]: ... + @overload + def __getitem__(self, i: int, /) -> int: ... + +class SizedBuffer(Sized, Buffer, Protocol): ... + +# for compatibility with third-party stubs that may use this +_BufferWithLen: TypeAlias = SizedBuffer # not stable # noqa: Y047 ExcInfo: TypeAlias = tuple[type[BaseException], BaseException, TracebackType] OptExcInfo: TypeAlias = ExcInfo | tuple[None, None, None] @@ -293,7 +327,7 @@ class structseq(Generic[_T_co]): # https://github.com/python/typeshed/pull/6560#discussion_r767149830 def __new__(cls: type[Self], sequence: Iterable[_T_co], dict: dict[str, Any] = ...) -> Self: ... -# Superset of typing.AnyStr that also inclues LiteralString +# Superset of typing.AnyStr that also includes LiteralString AnyOrLiteralStr = TypeVar("AnyOrLiteralStr", str, bytes, LiteralString) # noqa: Y001 # Represents when str or LiteralStr is acceptable. Useful for string processing @@ -312,3 +346,16 @@ TraceFunction: TypeAlias = Callable[[FrameType, str, Any], TraceFunction | None] # https://github.com/microsoft/pyright/issues/4339 class DataclassInstance(Protocol): __dataclass_fields__: ClassVar[dict[str, Field[Any]]] + +# Anything that can be passed to the int/float constructors +ConvertibleToInt: TypeAlias = str | ReadableBuffer | SupportsInt | SupportsIndex | SupportsTrunc +ConvertibleToFloat: TypeAlias = str | ReadableBuffer | SupportsFloat | SupportsIndex + +# A few classes updated from Foo(str, Enum) to Foo(StrEnum). This is a convenience so these +# can be accurate on all python versions without getting too wordy +if sys.version_info >= (3, 11): + from enum import StrEnum as StrEnum +else: + from enum import Enum + + class StrEnum(str, Enum): ... diff --git a/mypy/typeshed/stdlib/_typeshed/dbapi.pyi b/mypy/typeshed/stdlib/_typeshed/dbapi.pyi index 022e95996bb3..d54fbee57042 100644 --- a/mypy/typeshed/stdlib/_typeshed/dbapi.pyi +++ b/mypy/typeshed/stdlib/_typeshed/dbapi.pyi @@ -23,15 +23,15 @@ class DBAPICursor(Protocol): @property def rowcount(self) -> int: ... # optional: - # def callproc(self, __procname: str, __parameters: Sequence[Any] = ...) -> Sequence[Any]: ... + # def callproc(self, procname: str, parameters: Sequence[Any] = ..., /) -> Sequence[Any]: ... def close(self) -> object: ... - def execute(self, __operation: str, __parameters: Sequence[Any] | Mapping[str, Any] = ...) -> object: ... - def executemany(self, __operation: str, __seq_of_parameters: Sequence[Sequence[Any]]) -> object: ... + def execute(self, operation: str, parameters: Sequence[Any] | Mapping[str, Any] = ..., /) -> object: ... + def executemany(self, operation: str, seq_of_parameters: Sequence[Sequence[Any]], /) -> object: ... def fetchone(self) -> Sequence[Any] | None: ... - def fetchmany(self, __size: int = ...) -> Sequence[Sequence[Any]]: ... + def fetchmany(self, size: int = ..., /) -> Sequence[Sequence[Any]]: ... def fetchall(self) -> Sequence[Sequence[Any]]: ... # optional: # def nextset(self) -> None | Literal[True]: ... arraysize: int - def setinputsizes(self, __sizes: Sequence[DBAPITypeCode | int | None]) -> object: ... - def setoutputsize(self, __size: int, __column: int = ...) -> object: ... + def setinputsizes(self, sizes: Sequence[DBAPITypeCode | int | None], /) -> object: ... + def setoutputsize(self, size: int, column: int = ..., /) -> object: ... diff --git a/mypy/typeshed/stdlib/_typeshed/importlib.pyi b/mypy/typeshed/stdlib/_typeshed/importlib.pyi new file mode 100644 index 000000000000..a4e56cdaff62 --- /dev/null +++ b/mypy/typeshed/stdlib/_typeshed/importlib.pyi @@ -0,0 +1,18 @@ +# Implicit protocols used in importlib. +# We intentionally omit deprecated and optional methods. + +from collections.abc import Sequence +from importlib.machinery import ModuleSpec +from types import ModuleType +from typing import Protocol + +__all__ = ["LoaderProtocol", "MetaPathFinderProtocol", "PathEntryFinderProtocol"] + +class LoaderProtocol(Protocol): + def load_module(self, fullname: str, /) -> ModuleType: ... + +class MetaPathFinderProtocol(Protocol): + def find_spec(self, fullname: str, path: Sequence[str] | None, target: ModuleType | None = ..., /) -> ModuleSpec | None: ... + +class PathEntryFinderProtocol(Protocol): + def find_spec(self, fullname: str, target: ModuleType | None = ..., /) -> ModuleSpec | None: ... diff --git a/mypy/typeshed/stdlib/_typeshed/wsgi.pyi b/mypy/typeshed/stdlib/_typeshed/wsgi.pyi index de731aea918b..63f204eb889b 100644 --- a/mypy/typeshed/stdlib/_typeshed/wsgi.pyi +++ b/mypy/typeshed/stdlib/_typeshed/wsgi.pyi @@ -11,7 +11,7 @@ from typing import Any, Protocol from typing_extensions import TypeAlias class _Readable(Protocol): - def read(self, size: int = ...) -> bytes: ... + def read(self, size: int = ..., /) -> bytes: ... # Optional: def close(self) -> object: ... if sys.version_info >= (3, 11): @@ -20,7 +20,7 @@ else: # stable class StartResponse(Protocol): def __call__( - self, __status: str, __headers: list[tuple[str, str]], __exc_info: OptExcInfo | None = ... + self, status: str, headers: list[tuple[str, str]], exc_info: OptExcInfo | None = ..., / ) -> Callable[[bytes], object]: ... WSGIEnvironment: TypeAlias = dict[str, Any] # stable @@ -28,17 +28,17 @@ else: # WSGI input streams per PEP 3333, stable class InputStream(Protocol): - def read(self, __size: int = ...) -> bytes: ... - def readline(self, __size: int = ...) -> bytes: ... - def readlines(self, __hint: int = ...) -> list[bytes]: ... + def read(self, size: int = ..., /) -> bytes: ... + def readline(self, size: int = ..., /) -> bytes: ... + def readlines(self, hint: int = ..., /) -> list[bytes]: ... def __iter__(self) -> Iterator[bytes]: ... # WSGI error streams per PEP 3333, stable class ErrorStream(Protocol): def flush(self) -> object: ... - def write(self, __s: str) -> object: ... - def writelines(self, __seq: list[str]) -> object: ... + def write(self, s: str, /) -> object: ... + def writelines(self, seq: list[str], /) -> object: ... # Optional file wrapper in wsgi.file_wrapper class FileWrapper(Protocol): - def __call__(self, __file: _Readable, __block_size: int = ...) -> Iterable[bytes]: ... + def __call__(self, file: _Readable, block_size: int = ..., /) -> Iterable[bytes]: ... diff --git a/mypy/typeshed/stdlib/_typeshed/xml.pyi b/mypy/typeshed/stdlib/_typeshed/xml.pyi index 231c2b86e912..6cd1b39af628 100644 --- a/mypy/typeshed/stdlib/_typeshed/xml.pyi +++ b/mypy/typeshed/stdlib/_typeshed/xml.pyi @@ -4,6 +4,6 @@ from typing import Any, Protocol # As defined https://docs.python.org/3/library/xml.dom.html#domimplementation-objects class DOMImplementation(Protocol): - def hasFeature(self, feature: str, version: str | None) -> bool: ... - def createDocument(self, namespaceUri: str, qualifiedName: str, doctype: Any | None) -> Any: ... - def createDocumentType(self, qualifiedName: str, publicId: str, systemId: str) -> Any: ... + def hasFeature(self, feature: str, version: str | None, /) -> bool: ... + def createDocument(self, namespaceUri: str, qualifiedName: str, doctype: Any | None, /) -> Any: ... + def createDocumentType(self, qualifiedName: str, publicId: str, systemId: str, /) -> Any: ... diff --git a/mypy/typeshed/stdlib/_warnings.pyi b/mypy/typeshed/stdlib/_warnings.pyi index 0981dfeaafee..2e571e676c97 100644 --- a/mypy/typeshed/stdlib/_warnings.pyi +++ b/mypy/typeshed/stdlib/_warnings.pyi @@ -1,13 +1,36 @@ +import sys from typing import Any, overload _defaultaction: str _onceregistry: dict[Any, Any] filters: list[tuple[str, str | None, type[Warning], str | None, int]] -@overload -def warn(message: str, category: type[Warning] | None = None, stacklevel: int = 1, source: Any | None = None) -> None: ... -@overload -def warn(message: Warning, category: Any = None, stacklevel: int = 1, source: Any | None = None) -> None: ... +if sys.version_info >= (3, 12): + @overload + def warn( + message: str, + category: type[Warning] | None = None, + stacklevel: int = 1, + source: Any | None = None, + *, + skip_file_prefixes: tuple[str, ...] = (), + ) -> None: ... + @overload + def warn( + message: Warning, + category: Any = None, + stacklevel: int = 1, + source: Any | None = None, + *, + skip_file_prefixes: tuple[str, ...] = (), + ) -> None: ... + +else: + @overload + def warn(message: str, category: type[Warning] | None = None, stacklevel: int = 1, source: Any | None = None) -> None: ... + @overload + def warn(message: Warning, category: Any = None, stacklevel: int = 1, source: Any | None = None) -> None: ... + @overload def warn_explicit( message: str, diff --git a/mypy/typeshed/stdlib/_weakref.pyi b/mypy/typeshed/stdlib/_weakref.pyi index 2a43de3ffd6b..e395143cc027 100644 --- a/mypy/typeshed/stdlib/_weakref.pyi +++ b/mypy/typeshed/stdlib/_weakref.pyi @@ -1,7 +1,7 @@ import sys from collections.abc import Callable -from typing import Any, Generic, TypeVar, overload -from typing_extensions import Self, final +from typing import Any, Generic, TypeVar, final, overload +from typing_extensions import Self if sys.version_info >= (3, 9): from types import GenericAlias @@ -11,27 +11,31 @@ _T = TypeVar("_T") @final class CallableProxyType(Generic[_C]): # "weakcallableproxy" + def __eq__(self, value: object, /) -> bool: ... def __getattr__(self, attr: str) -> Any: ... __call__: _C @final class ProxyType(Generic[_T]): # "weakproxy" + def __eq__(self, value: object, /) -> bool: ... def __getattr__(self, attr: str) -> Any: ... class ReferenceType(Generic[_T]): __callback__: Callable[[ReferenceType[_T]], Any] - def __new__(cls, o: _T, callback: Callable[[ReferenceType[_T]], Any] | None = ...) -> Self: ... + def __new__(cls, o: _T, callback: Callable[[ReferenceType[_T]], Any] | None = ..., /) -> Self: ... def __call__(self) -> _T | None: ... + def __eq__(self, value: object, /) -> bool: ... + def __hash__(self) -> int: ... if sys.version_info >= (3, 9): def __class_getitem__(cls, item: Any) -> GenericAlias: ... ref = ReferenceType -def getweakrefcount(__object: Any) -> int: ... -def getweakrefs(__object: Any) -> list[Any]: ... +def getweakrefcount(object: Any, /) -> int: ... +def getweakrefs(object: Any, /) -> list[Any]: ... # Return CallableProxyType if object is callable, ProxyType otherwise @overload -def proxy(__object: _C, __callback: Callable[[_C], Any] | None = None) -> CallableProxyType[_C]: ... +def proxy(object: _C, callback: Callable[[_C], Any] | None = None, /) -> CallableProxyType[_C]: ... @overload -def proxy(__object: _T, __callback: Callable[[_T], Any] | None = None) -> Any: ... +def proxy(object: _T, callback: Callable[[_T], Any] | None = None, /) -> Any: ... diff --git a/mypy/typeshed/stdlib/_weakrefset.pyi b/mypy/typeshed/stdlib/_weakrefset.pyi index d73d79155329..6482ade1271e 100644 --- a/mypy/typeshed/stdlib/_weakrefset.pyi +++ b/mypy/typeshed/stdlib/_weakrefset.pyi @@ -1,6 +1,6 @@ import sys from collections.abc import Iterable, Iterator, MutableSet -from typing import Any, Generic, TypeVar, overload +from typing import Any, TypeVar, overload from typing_extensions import Self if sys.version_info >= (3, 9): @@ -11,7 +11,7 @@ __all__ = ["WeakSet"] _S = TypeVar("_S") _T = TypeVar("_T") -class WeakSet(MutableSet[_T], Generic[_T]): +class WeakSet(MutableSet[_T]): @overload def __init__(self, data: None = None) -> None: ... @overload diff --git a/mypy/typeshed/stdlib/_winapi.pyi b/mypy/typeshed/stdlib/_winapi.pyi index e21402b801c5..c6fb0484df8e 100644 --- a/mypy/typeshed/stdlib/_winapi.pyi +++ b/mypy/typeshed/stdlib/_winapi.pyi @@ -1,8 +1,7 @@ import sys from _typeshed import ReadableBuffer from collections.abc import Sequence -from typing import Any, NoReturn, overload -from typing_extensions import Literal, final +from typing import Any, Literal, NoReturn, final, overload if sys.platform == "win32": ABOVE_NORMAL_PRIORITY_CLASS: Literal[0x8000] @@ -36,12 +35,11 @@ if sys.platform == "win32": FILE_GENERIC_READ: Literal[1179785] FILE_GENERIC_WRITE: Literal[1179926] - if sys.version_info >= (3, 8): - FILE_MAP_ALL_ACCESS: Literal[983071] - FILE_MAP_COPY: Literal[1] - FILE_MAP_EXECUTE: Literal[32] - FILE_MAP_READ: Literal[4] - FILE_MAP_WRITE: Literal[2] + FILE_MAP_ALL_ACCESS: Literal[983071] + FILE_MAP_COPY: Literal[1] + FILE_MAP_EXECUTE: Literal[32] + FILE_MAP_READ: Literal[4] + FILE_MAP_WRITE: Literal[2] FILE_TYPE_CHAR: Literal[2] FILE_TYPE_DISK: Literal[1] @@ -53,20 +51,21 @@ if sys.platform == "win32": GENERIC_WRITE: Literal[0x40000000] HIGH_PRIORITY_CLASS: Literal[0x80] INFINITE: Literal[0xFFFFFFFF] - if sys.version_info >= (3, 8): - INVALID_HANDLE_VALUE: Literal[0xFFFFFFFFFFFFFFFF] + # Ignore the Flake8 error -- flake8-pyi assumes + # most numbers this long will be implementation details, + # but here we can see that it's a power of 2 + INVALID_HANDLE_VALUE: Literal[0xFFFFFFFFFFFFFFFF] # noqa: Y054 IDLE_PRIORITY_CLASS: Literal[0x40] NORMAL_PRIORITY_CLASS: Literal[0x20] REALTIME_PRIORITY_CLASS: Literal[0x100] NMPWAIT_WAIT_FOREVER: Literal[0xFFFFFFFF] - if sys.version_info >= (3, 8): - MEM_COMMIT: Literal[0x1000] - MEM_FREE: Literal[0x10000] - MEM_IMAGE: Literal[0x1000000] - MEM_MAPPED: Literal[0x40000] - MEM_PRIVATE: Literal[0x20000] - MEM_RESERVE: Literal[0x2000] + MEM_COMMIT: Literal[0x1000] + MEM_FREE: Literal[0x10000] + MEM_IMAGE: Literal[0x1000000] + MEM_MAPPED: Literal[0x40000] + MEM_PRIVATE: Literal[0x20000] + MEM_RESERVE: Literal[0x2000] NULL: Literal[0] OPEN_EXISTING: Literal[3] @@ -78,29 +77,27 @@ if sys.platform == "win32": PIPE_UNLIMITED_INSTANCES: Literal[255] PIPE_WAIT: Literal[0] - if sys.version_info >= (3, 8): - PAGE_EXECUTE: Literal[0x10] - PAGE_EXECUTE_READ: Literal[0x20] - PAGE_EXECUTE_READWRITE: Literal[0x40] - PAGE_EXECUTE_WRITECOPY: Literal[0x80] - PAGE_GUARD: Literal[0x100] - PAGE_NOACCESS: Literal[0x1] - PAGE_NOCACHE: Literal[0x200] - PAGE_READONLY: Literal[0x2] - PAGE_READWRITE: Literal[0x4] - PAGE_WRITECOMBINE: Literal[0x400] - PAGE_WRITECOPY: Literal[0x8] + PAGE_EXECUTE: Literal[0x10] + PAGE_EXECUTE_READ: Literal[0x20] + PAGE_EXECUTE_READWRITE: Literal[0x40] + PAGE_EXECUTE_WRITECOPY: Literal[0x80] + PAGE_GUARD: Literal[0x100] + PAGE_NOACCESS: Literal[0x1] + PAGE_NOCACHE: Literal[0x200] + PAGE_READONLY: Literal[0x2] + PAGE_READWRITE: Literal[0x4] + PAGE_WRITECOMBINE: Literal[0x400] + PAGE_WRITECOPY: Literal[0x8] PROCESS_ALL_ACCESS: Literal[0x1FFFFF] PROCESS_DUP_HANDLE: Literal[0x40] - if sys.version_info >= (3, 8): - SEC_COMMIT: Literal[0x8000000] - SEC_IMAGE: Literal[0x1000000] - SEC_LARGE_PAGES: Literal[0x80000000] - SEC_NOCACHE: Literal[0x10000000] - SEC_RESERVE: Literal[0x4000000] - SEC_WRITECOMBINE: Literal[0x40000000] + SEC_COMMIT: Literal[0x8000000] + SEC_IMAGE: Literal[0x1000000] + SEC_LARGE_PAGES: Literal[0x80000000] + SEC_NOCACHE: Literal[0x10000000] + SEC_RESERVE: Literal[0x4000000] + SEC_WRITECOMBINE: Literal[0x40000000] STARTF_USESHOWWINDOW: Literal[0x1] STARTF_USESTDHANDLES: Literal[0x100] @@ -111,8 +108,7 @@ if sys.platform == "win32": STILL_ACTIVE: Literal[259] SW_HIDE: Literal[0] - if sys.version_info >= (3, 8): - SYNCHRONIZE: Literal[0x100000] + SYNCHRONIZE: Literal[0x100000] WAIT_ABANDONED_0: Literal[128] WAIT_OBJECT_0: Literal[0] WAIT_TIMEOUT: Literal[258] @@ -134,7 +130,35 @@ if sys.platform == "win32": LCMAP_TRADITIONAL_CHINESE: int LCMAP_UPPERCASE: int - def CloseHandle(__handle: int) -> None: ... + if sys.version_info >= (3, 12): + COPYFILE2_CALLBACK_CHUNK_STARTED: Literal[1] + COPYFILE2_CALLBACK_CHUNK_FINISHED: Literal[2] + COPYFILE2_CALLBACK_STREAM_STARTED: Literal[3] + COPYFILE2_CALLBACK_STREAM_FINISHED: Literal[4] + COPYFILE2_CALLBACK_POLL_CONTINUE: Literal[5] + COPYFILE2_CALLBACK_ERROR: Literal[6] + + COPYFILE2_PROGRESS_CONTINUE: Literal[0] + COPYFILE2_PROGRESS_CANCEL: Literal[1] + COPYFILE2_PROGRESS_STOP: Literal[2] + COPYFILE2_PROGRESS_QUIET: Literal[3] + COPYFILE2_PROGRESS_PAUSE: Literal[4] + + COPY_FILE_FAIL_IF_EXISTS: Literal[0x1] + COPY_FILE_RESTARTABLE: Literal[0x2] + COPY_FILE_OPEN_SOURCE_FOR_WRITE: Literal[0x4] + COPY_FILE_ALLOW_DECRYPTED_DESTINATION: Literal[0x8] + COPY_FILE_COPY_SYMLINK: Literal[0x800] + COPY_FILE_NO_BUFFERING: Literal[0x1000] + COPY_FILE_REQUEST_SECURITY_PRIVILEGES: Literal[0x2000] + COPY_FILE_RESUME_FROM_PAUSE: Literal[0x4000] + COPY_FILE_NO_OFFLOAD: Literal[0x40000] + COPY_FILE_REQUEST_COMPRESSED_TRAFFIC: Literal[0x10000000] + + ERROR_ACCESS_DENIED: Literal[5] + ERROR_PRIVILEGE_NOT_HELD: Literal[1314] + + def CloseHandle(handle: int, /) -> None: ... @overload def ConnectNamedPipe(handle: int, overlapped: Literal[True]) -> Overlapped: ... @overload @@ -142,59 +166,63 @@ if sys.platform == "win32": @overload def ConnectNamedPipe(handle: int, overlapped: bool) -> Overlapped | None: ... def CreateFile( - __file_name: str, - __desired_access: int, - __share_mode: int, - __security_attributes: int, - __creation_disposition: int, - __flags_and_attributes: int, - __template_file: int, + file_name: str, + desired_access: int, + share_mode: int, + security_attributes: int, + creation_disposition: int, + flags_and_attributes: int, + template_file: int, + /, ) -> int: ... - def CreateJunction(__src_path: str, __dst_path: str) -> None: ... + def CreateJunction(src_path: str, dst_path: str, /) -> None: ... def CreateNamedPipe( - __name: str, - __open_mode: int, - __pipe_mode: int, - __max_instances: int, - __out_buffer_size: int, - __in_buffer_size: int, - __default_timeout: int, - __security_attributes: int, + name: str, + open_mode: int, + pipe_mode: int, + max_instances: int, + out_buffer_size: int, + in_buffer_size: int, + default_timeout: int, + security_attributes: int, + /, ) -> int: ... - def CreatePipe(__pipe_attrs: Any, __size: int) -> tuple[int, int]: ... + def CreatePipe(pipe_attrs: Any, size: int, /) -> tuple[int, int]: ... def CreateProcess( - __application_name: str | None, - __command_line: str | None, - __proc_attrs: Any, - __thread_attrs: Any, - __inherit_handles: bool, - __creation_flags: int, - __env_mapping: dict[str, str], - __current_directory: str | None, - __startup_info: Any, + application_name: str | None, + command_line: str | None, + proc_attrs: Any, + thread_attrs: Any, + inherit_handles: bool, + creation_flags: int, + env_mapping: dict[str, str], + current_directory: str | None, + startup_info: Any, + /, ) -> tuple[int, int, int, int]: ... def DuplicateHandle( - __source_process_handle: int, - __source_handle: int, - __target_process_handle: int, - __desired_access: int, - __inherit_handle: bool, - __options: int = 0, + source_process_handle: int, + source_handle: int, + target_process_handle: int, + desired_access: int, + inherit_handle: bool, + options: int = 0, + /, ) -> int: ... - def ExitProcess(__ExitCode: int) -> NoReturn: ... + def ExitProcess(ExitCode: int, /) -> NoReturn: ... def GetACP() -> int: ... def GetFileType(handle: int) -> int: ... def GetCurrentProcess() -> int: ... - def GetExitCodeProcess(__process: int) -> int: ... + def GetExitCodeProcess(process: int, /) -> int: ... def GetLastError() -> int: ... - def GetModuleFileName(__module_handle: int) -> str: ... - def GetStdHandle(__std_handle: int) -> int: ... + def GetModuleFileName(module_handle: int, /) -> str: ... + def GetStdHandle(std_handle: int, /) -> int: ... def GetVersion() -> int: ... - def OpenProcess(__desired_access: int, __inherit_handle: bool, __process_id: int) -> int: ... - def PeekNamedPipe(__handle: int, __size: int = 0) -> tuple[int, int] | tuple[bytes, int, int]: ... + def OpenProcess(desired_access: int, inherit_handle: bool, process_id: int, /) -> int: ... + def PeekNamedPipe(handle: int, size: int = 0, /) -> tuple[int, int] | tuple[bytes, int, int]: ... if sys.version_info >= (3, 10): def LCMapStringEx(locale: str, flags: int, src: str) -> str: ... - def UnmapViewOfFile(__address: int) -> None: ... + def UnmapViewOfFile(address: int, /) -> None: ... @overload def ReadFile(handle: int, size: int, overlapped: Literal[True]) -> tuple[Overlapped, int]: ... @@ -203,12 +231,12 @@ if sys.platform == "win32": @overload def ReadFile(handle: int, size: int, overlapped: int | bool) -> tuple[Any, int]: ... def SetNamedPipeHandleState( - __named_pipe: int, __mode: int | None, __max_collection_count: int | None, __collect_data_timeout: int | None + named_pipe: int, mode: int | None, max_collection_count: int | None, collect_data_timeout: int | None, / ) -> None: ... - def TerminateProcess(__handle: int, __exit_code: int) -> None: ... - def WaitForMultipleObjects(__handle_seq: Sequence[int], __wait_flag: bool, __milliseconds: int = 0xFFFFFFFF) -> int: ... - def WaitForSingleObject(__handle: int, __milliseconds: int) -> int: ... - def WaitNamedPipe(__name: str, __timeout: int) -> None: ... + def TerminateProcess(handle: int, exit_code: int, /) -> None: ... + def WaitForMultipleObjects(handle_seq: Sequence[int], wait_flag: bool, milliseconds: int = 0xFFFFFFFF, /) -> int: ... + def WaitForSingleObject(handle: int, milliseconds: int, /) -> int: ... + def WaitNamedPipe(name: str, timeout: int, /) -> None: ... @overload def WriteFile(handle: int, buffer: ReadableBuffer, overlapped: Literal[True]) -> tuple[Overlapped, int]: ... @overload @@ -218,6 +246,10 @@ if sys.platform == "win32": @final class Overlapped: event: int - def GetOverlappedResult(self, __wait: bool) -> tuple[int, int]: ... + def GetOverlappedResult(self, wait: bool, /) -> tuple[int, int]: ... def cancel(self) -> None: ... def getbuffer(self) -> bytes | None: ... + + if sys.version_info >= (3, 12): + def CopyFile2(existing_file_name: str, new_file_name: str, flags: int, progress_routine: int | None = None) -> int: ... + def NeedCurrentDirectoryForExePath(exe_name: str, /) -> bool: ... diff --git a/mypy/typeshed/stdlib/abc.pyi b/mypy/typeshed/stdlib/abc.pyi index 068dab4752be..6bf7821f1c1b 100644 --- a/mypy/typeshed/stdlib/abc.pyi +++ b/mypy/typeshed/stdlib/abc.pyi @@ -2,19 +2,20 @@ import _typeshed import sys from _typeshed import SupportsWrite from collections.abc import Callable -from typing import Any, Generic, TypeVar -from typing_extensions import Literal +from typing import Any, Literal, TypeVar +from typing_extensions import Concatenate, ParamSpec, deprecated _T = TypeVar("_T") _R_co = TypeVar("_R_co", covariant=True) _FuncT = TypeVar("_FuncT", bound=Callable[..., Any]) +_P = ParamSpec("_P") # These definitions have special processing in mypy class ABCMeta(type): __abstractmethods__: frozenset[str] if sys.version_info >= (3, 11): def __new__( - __mcls: type[_typeshed.Self], __name: str, __bases: tuple[type, ...], __namespace: dict[str, Any], **kwargs: Any + mcls: type[_typeshed.Self], name: str, bases: tuple[type, ...], namespace: dict[str, Any], /, **kwargs: Any ) -> _typeshed.Self: ... else: def __new__( @@ -27,19 +28,22 @@ class ABCMeta(type): def register(cls: ABCMeta, subclass: type[_T]) -> type[_T]: ... def abstractmethod(funcobj: _FuncT) -> _FuncT: ... - -class abstractclassmethod(classmethod[_R_co], Generic[_R_co]): +@deprecated("Deprecated, use 'classmethod' with 'abstractmethod' instead") +class abstractclassmethod(classmethod[_T, _P, _R_co]): __isabstractmethod__: Literal[True] - def __init__(self: abstractclassmethod[_R_co], callable: Callable[..., _R_co]) -> None: ... + def __init__(self, callable: Callable[Concatenate[type[_T], _P], _R_co]) -> None: ... -class abstractstaticmethod(staticmethod[_R_co], Generic[_R_co]): +@deprecated("Deprecated, use 'staticmethod' with 'abstractmethod' instead") +class abstractstaticmethod(staticmethod[_P, _R_co]): __isabstractmethod__: Literal[True] - def __init__(self, callable: Callable[..., _R_co]) -> None: ... + def __init__(self, callable: Callable[_P, _R_co]) -> None: ... +@deprecated("Deprecated, use 'property' with 'abstractmethod' instead") class abstractproperty(property): __isabstractmethod__: Literal[True] -class ABC(metaclass=ABCMeta): ... +class ABC(metaclass=ABCMeta): + __slots__ = () def get_cache_token() -> object: ... diff --git a/mypy/typeshed/stdlib/aifc.pyi b/mypy/typeshed/stdlib/aifc.pyi index ab0c18ed6623..05bf53986b29 100644 --- a/mypy/typeshed/stdlib/aifc.pyi +++ b/mypy/typeshed/stdlib/aifc.pyi @@ -1,7 +1,7 @@ import sys from types import TracebackType -from typing import IO, Any, NamedTuple, overload -from typing_extensions import Literal, Self, TypeAlias +from typing import IO, Any, Literal, NamedTuple, overload +from typing_extensions import Self, TypeAlias if sys.version_info >= (3, 9): __all__ = ["Error", "open"] diff --git a/mypy/typeshed/stdlib/argparse.pyi b/mypy/typeshed/stdlib/argparse.pyi index 20d9dfa9d137..0701654734a4 100644 --- a/mypy/typeshed/stdlib/argparse.pyi +++ b/mypy/typeshed/stdlib/argparse.pyi @@ -1,8 +1,9 @@ import sys +from _typeshed import sentinel from collections.abc import Callable, Generator, Iterable, Sequence from re import Pattern -from typing import IO, Any, Generic, NewType, NoReturn, Protocol, TypeVar, overload -from typing_extensions import Literal, TypeAlias +from typing import IO, Any, Generic, Literal, NewType, NoReturn, Protocol, TypeVar, overload +from typing_extensions import Self, TypeAlias, deprecated __all__ = [ "ArgumentParser", @@ -85,7 +86,7 @@ class _ActionsContainer: self, *name_or_flags: str, action: _ActionStr | type[Action] = ..., - nargs: int | _NArgsStr | _SUPPRESS_T = ..., + nargs: int | _NArgsStr | _SUPPRESS_T | None = None, const: Any = ..., default: Any = ..., type: Callable[[str], _T] | FileType = ..., @@ -97,8 +98,16 @@ class _ActionsContainer: version: str = ..., **kwargs: Any, ) -> Action: ... - def add_argument_group(self, *args: Any, **kwargs: Any) -> _ArgumentGroup: ... - def add_mutually_exclusive_group(self, **kwargs: Any) -> _MutuallyExclusiveGroup: ... + def add_argument_group( + self, + title: str | None = None, + description: str | None = None, + *, + prefix_chars: str = ..., + argument_default: Any = ..., + conflict_handler: str = ..., + ) -> _ArgumentGroup: ... + def add_mutually_exclusive_group(self, *, required: bool = False) -> _MutuallyExclusiveGroup: ... def _add_action(self, action: _ActionT) -> _ActionT: ... def _remove_action(self, action: Action) -> None: ... def _add_container_actions(self, container: _ActionsContainer) -> None: ... @@ -111,7 +120,7 @@ class _ActionsContainer: def _handle_conflict_resolve(self, action: Action, conflicting_actions: Iterable[tuple[str, Action]]) -> None: ... class _FormatterClass(Protocol): - def __call__(self, prog: str) -> HelpFormatter: ... + def __call__(self, *, prog: str) -> HelpFormatter: ... class ArgumentParser(_AttributeHolder, _ActionsContainer): prog: str @@ -135,7 +144,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): usage: str | None = None, description: str | None = None, epilog: str | None = None, - parents: Sequence[ArgumentParser] = ..., + parents: Sequence[ArgumentParser] = [], formatter_class: _FormatterClass = ..., prefix_chars: str = "-", fromfile_prefix_chars: str | None = None, @@ -152,7 +161,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): usage: str | None = None, description: str | None = None, epilog: str | None = None, - parents: Sequence[ArgumentParser] = ..., + parents: Sequence[ArgumentParser] = [], formatter_class: _FormatterClass = ..., prefix_chars: str = "-", fromfile_prefix_chars: str | None = None, @@ -161,17 +170,12 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): add_help: bool = True, allow_abbrev: bool = True, ) -> None: ... - # The type-ignores in these overloads should be temporary. See: - # https://github.com/python/typeshed/pull/2643#issuecomment-442280277 - @overload - def parse_args(self, args: Sequence[str] | None = None) -> Namespace: ... + @overload - def parse_args(self, args: Sequence[str] | None, namespace: None) -> Namespace: ... # type: ignore[misc] + def parse_args(self, args: Sequence[str] | None = None, namespace: None = None) -> Namespace: ... @overload def parse_args(self, args: Sequence[str] | None, namespace: _N) -> _N: ... @overload - def parse_args(self, *, namespace: None) -> Namespace: ... # type: ignore[misc] - @overload def parse_args(self, *, namespace: _N) -> _N: ... @overload def add_subparsers( @@ -206,16 +210,29 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): def print_help(self, file: IO[str] | None = None) -> None: ... def format_usage(self) -> str: ... def format_help(self) -> str: ... - def parse_known_args( - self, args: Sequence[str] | None = None, namespace: Namespace | None = None - ) -> tuple[Namespace, list[str]]: ... + @overload + def parse_known_args(self, args: Sequence[str] | None = None, namespace: None = None) -> tuple[Namespace, list[str]]: ... + @overload + def parse_known_args(self, args: Sequence[str] | None, namespace: _N) -> tuple[_N, list[str]]: ... + @overload + def parse_known_args(self, *, namespace: _N) -> tuple[_N, list[str]]: ... def convert_arg_line_to_args(self, arg_line: str) -> list[str]: ... def exit(self, status: int = 0, message: str | None = None) -> NoReturn: ... def error(self, message: str) -> NoReturn: ... - def parse_intermixed_args(self, args: Sequence[str] | None = None, namespace: Namespace | None = None) -> Namespace: ... + @overload + def parse_intermixed_args(self, args: Sequence[str] | None = None, namespace: None = None) -> Namespace: ... + @overload + def parse_intermixed_args(self, args: Sequence[str] | None, namespace: _N) -> _N: ... + @overload + def parse_intermixed_args(self, *, namespace: _N) -> _N: ... + @overload def parse_known_intermixed_args( - self, args: Sequence[str] | None = None, namespace: Namespace | None = None + self, args: Sequence[str] | None = None, namespace: None = None ) -> tuple[Namespace, list[str]]: ... + @overload + def parse_known_intermixed_args(self, args: Sequence[str] | None, namespace: _N) -> tuple[_N, list[str]]: ... + @overload + def parse_known_intermixed_args(self, *, namespace: _N) -> tuple[_N, list[str]]: ... # undocumented def _get_optional_actions(self) -> list[Action]: ... def _get_positional_actions(self) -> list[Action]: ... @@ -241,11 +258,19 @@ class HelpFormatter: _current_indent: int _level: int _action_max_length: int - _root_section: Any - _current_section: Any + _root_section: _Section + _current_section: _Section _whitespace_matcher: Pattern[str] _long_break_matcher: Pattern[str] - _Section: type[Any] # Nested class + + class _Section: + formatter: HelpFormatter + heading: str | None + parent: Self | None + items: list[tuple[Callable[..., str], Iterable[Any]]] + def __init__(self, formatter: HelpFormatter, parent: Self | None, heading: str | None = None) -> None: ... + def format_help(self) -> str: ... + def __init__(self, prog: str, indent_increment: int = 2, max_help_position: int = 24, width: int | None = None) -> None: ... def _indent(self) -> None: ... def _dedent(self) -> None: ... @@ -254,16 +279,16 @@ class HelpFormatter: def end_section(self) -> None: ... def add_text(self, text: str | None) -> None: ... def add_usage( - self, usage: str | None, actions: Iterable[Action], groups: Iterable[_ArgumentGroup], prefix: str | None = None + self, usage: str | None, actions: Iterable[Action], groups: Iterable[_MutuallyExclusiveGroup], prefix: str | None = None ) -> None: ... def add_argument(self, action: Action) -> None: ... def add_arguments(self, actions: Iterable[Action]) -> None: ... def format_help(self) -> str: ... def _join_parts(self, part_strings: Iterable[str]) -> str: ... def _format_usage( - self, usage: str | None, actions: Iterable[Action], groups: Iterable[_ArgumentGroup], prefix: str | None + self, usage: str | None, actions: Iterable[Action], groups: Iterable[_MutuallyExclusiveGroup], prefix: str | None ) -> str: ... - def _format_actions_usage(self, actions: Iterable[Action], groups: Iterable[_ArgumentGroup]) -> str: ... + def _format_actions_usage(self, actions: Iterable[Action], groups: Iterable[_MutuallyExclusiveGroup]) -> str: ... def _format_text(self, text: str) -> str: ... def _format_action(self, action: Action) -> str: ... def _format_action_invocation(self, action: Action) -> str: ... @@ -312,13 +337,51 @@ class Action(_AttributeHolder): if sys.version_info >= (3, 9): def format_usage(self) -> str: ... -if sys.version_info >= (3, 9): +if sys.version_info >= (3, 12): class BooleanOptionalAction(Action): + @overload def __init__( self, option_strings: Sequence[str], dest: str, - default: _T | str | None = None, + default: bool | None = None, + *, + required: bool = False, + help: str | None = None, + ) -> None: ... + @overload + @deprecated("The `type`, `choices`, and `metavar` parameters are ignored and will be removed in Python 3.14.") + def __init__( + self, + option_strings: Sequence[str], + dest: str, + default: _T | bool | None = None, + type: Callable[[str], _T] | FileType | None = sentinel, + choices: Iterable[_T] | None = sentinel, + required: bool = False, + help: str | None = None, + metavar: str | tuple[str, ...] | None = sentinel, + ) -> None: ... + +elif sys.version_info >= (3, 9): + class BooleanOptionalAction(Action): + @overload + def __init__( + self, + option_strings: Sequence[str], + dest: str, + default: bool | None = None, + *, + required: bool = False, + help: str | None = None, + ) -> None: ... + @overload + @deprecated("The `type`, `choices`, and `metavar` parameters are ignored and will be removed in Python 3.14.") + def __init__( + self, + option_strings: Sequence[str], + dest: str, + default: _T | bool | None = None, type: Callable[[str], _T] | FileType | None = None, choices: Iterable[_T] | None = None, required: bool = False, @@ -329,7 +392,7 @@ if sys.version_info >= (3, 9): class Namespace(_AttributeHolder): def __init__(self, **kwargs: Any) -> None: ... def __getattr__(self, name: str) -> Any: ... - def __setattr__(self, __name: str, __value: Any) -> None: ... + def __setattr__(self, name: str, value: Any, /) -> None: ... def __contains__(self, key: str) -> bool: ... def __eq__(self, other: object) -> bool: ... @@ -347,7 +410,14 @@ class _ArgumentGroup(_ActionsContainer): title: str | None _group_actions: list[Action] def __init__( - self, container: _ActionsContainer, title: str | None = None, description: str | None = None, **kwargs: Any + self, + container: _ActionsContainer, + title: str | None = None, + description: str | None = None, + *, + prefix_chars: str = ..., + argument_default: Any = ..., + conflict_handler: str = ..., ) -> None: ... # undocumented @@ -400,8 +470,7 @@ class _StoreFalseAction(_StoreConstAction): class _AppendAction(Action): ... # undocumented -if sys.version_info >= (3, 8): - class _ExtendAction(_AppendAction): ... +class _ExtendAction(_AppendAction): ... # undocumented class _AppendConstAction(Action): diff --git a/mypy/typeshed/stdlib/array.pyi b/mypy/typeshed/stdlib/array.pyi index 827bbb97897f..1b7de1c7882d 100644 --- a/mypy/typeshed/stdlib/array.pyi +++ b/mypy/typeshed/stdlib/array.pyi @@ -3,8 +3,11 @@ from _typeshed import ReadableBuffer, SupportsRead, SupportsWrite from collections.abc import Iterable # pytype crashes if array inherits from collections.abc.MutableSequence instead of typing.MutableSequence -from typing import Any, Generic, MutableSequence, TypeVar, overload # noqa: Y022 -from typing_extensions import Literal, Self, SupportsIndex, TypeAlias +from typing import Any, Literal, MutableSequence, SupportsIndex, TypeVar, overload # noqa: Y022 +from typing_extensions import Self, TypeAlias + +if sys.version_info >= (3, 12): + from types import GenericAlias _IntTypeCode: TypeAlias = Literal["b", "B", "h", "H", "i", "I", "l", "L", "q", "Q"] _FloatTypeCode: TypeAlias = Literal["f", "d"] @@ -15,70 +18,75 @@ _T = TypeVar("_T", int, float, str) typecodes: str -class array(MutableSequence[_T], Generic[_T]): +class array(MutableSequence[_T]): @property def typecode(self) -> _TypeCode: ... @property def itemsize(self) -> int: ... @overload - def __init__(self: array[int], __typecode: _IntTypeCode, __initializer: bytes | bytearray | Iterable[int] = ...) -> None: ... + def __init__(self: array[int], typecode: _IntTypeCode, initializer: bytes | bytearray | Iterable[int] = ..., /) -> None: ... @overload def __init__( - self: array[float], __typecode: _FloatTypeCode, __initializer: bytes | bytearray | Iterable[float] = ... + self: array[float], typecode: _FloatTypeCode, initializer: bytes | bytearray | Iterable[float] = ..., / ) -> None: ... @overload def __init__( - self: array[str], __typecode: _UnicodeTypeCode, __initializer: bytes | bytearray | Iterable[str] = ... + self: array[str], typecode: _UnicodeTypeCode, initializer: bytes | bytearray | Iterable[str] = ..., / ) -> None: ... @overload - def __init__(self, __typecode: str, __initializer: Iterable[_T]) -> None: ... + def __init__(self, typecode: str, initializer: Iterable[_T], /) -> None: ... @overload - def __init__(self, __typecode: str, __initializer: bytes | bytearray = ...) -> None: ... - def append(self, __v: _T) -> None: ... + def __init__(self, typecode: str, initializer: bytes | bytearray = ..., /) -> None: ... + def append(self, v: _T, /) -> None: ... def buffer_info(self) -> tuple[int, int]: ... def byteswap(self) -> None: ... - def count(self, __v: _T) -> int: ... - def extend(self, __bb: Iterable[_T]) -> None: ... - def frombytes(self, __buffer: ReadableBuffer) -> None: ... - def fromfile(self, __f: SupportsRead[bytes], __n: int) -> None: ... - def fromlist(self, __list: list[_T]) -> None: ... - def fromunicode(self, __ustr: str) -> None: ... + def count(self, v: _T, /) -> int: ... + def extend(self, bb: Iterable[_T], /) -> None: ... + def frombytes(self, buffer: ReadableBuffer, /) -> None: ... + def fromfile(self, f: SupportsRead[bytes], n: int, /) -> None: ... + def fromlist(self, list: list[_T], /) -> None: ... + def fromunicode(self, ustr: str, /) -> None: ... if sys.version_info >= (3, 10): - def index(self, __v: _T, __start: int = 0, __stop: int = sys.maxsize) -> int: ... + def index(self, v: _T, start: int = 0, stop: int = sys.maxsize, /) -> int: ... else: - def index(self, __v: _T) -> int: ... # type: ignore[override] + def index(self, v: _T, /) -> int: ... # type: ignore[override] - def insert(self, __i: int, __v: _T) -> None: ... - def pop(self, __i: int = -1) -> _T: ... - def remove(self, __v: _T) -> None: ... + def insert(self, i: int, v: _T, /) -> None: ... + def pop(self, i: int = -1, /) -> _T: ... + def remove(self, v: _T, /) -> None: ... def tobytes(self) -> bytes: ... - def tofile(self, __f: SupportsWrite[bytes]) -> None: ... + def tofile(self, f: SupportsWrite[bytes], /) -> None: ... def tolist(self) -> list[_T]: ... def tounicode(self) -> str: ... if sys.version_info < (3, 9): - def fromstring(self, __buffer: str | ReadableBuffer) -> None: ... + def fromstring(self, buffer: str | ReadableBuffer, /) -> None: ... def tostring(self) -> bytes: ... def __len__(self) -> int: ... @overload - def __getitem__(self, __i: SupportsIndex) -> _T: ... + def __getitem__(self, key: SupportsIndex, /) -> _T: ... @overload - def __getitem__(self, __s: slice) -> array[_T]: ... + def __getitem__(self, key: slice, /) -> array[_T]: ... @overload # type: ignore[override] - def __setitem__(self, __i: SupportsIndex, __o: _T) -> None: ... + def __setitem__(self, key: SupportsIndex, value: _T, /) -> None: ... @overload - def __setitem__(self, __s: slice, __o: array[_T]) -> None: ... - def __delitem__(self, __i: SupportsIndex | slice) -> None: ... - def __add__(self, __x: array[_T]) -> array[_T]: ... - def __ge__(self, __other: array[_T]) -> bool: ... - def __gt__(self, __other: array[_T]) -> bool: ... - def __iadd__(self, __x: array[_T]) -> Self: ... # type: ignore[override] - def __imul__(self, __n: int) -> Self: ... - def __le__(self, __other: array[_T]) -> bool: ... - def __lt__(self, __other: array[_T]) -> bool: ... - def __mul__(self, __n: int) -> array[_T]: ... - def __rmul__(self, __n: int) -> array[_T]: ... + def __setitem__(self, key: slice, value: array[_T], /) -> None: ... + def __delitem__(self, key: SupportsIndex | slice, /) -> None: ... + def __add__(self, value: array[_T], /) -> array[_T]: ... + def __eq__(self, value: object, /) -> bool: ... + def __ge__(self, value: array[_T], /) -> bool: ... + def __gt__(self, value: array[_T], /) -> bool: ... + def __iadd__(self, value: array[_T], /) -> Self: ... # type: ignore[override] + def __imul__(self, value: int, /) -> Self: ... + def __le__(self, value: array[_T], /) -> bool: ... + def __lt__(self, value: array[_T], /) -> bool: ... + def __mul__(self, value: int, /) -> array[_T]: ... + def __rmul__(self, value: int, /) -> array[_T]: ... def __copy__(self) -> array[_T]: ... - def __deepcopy__(self, __unused: Any) -> array[_T]: ... + def __deepcopy__(self, unused: Any, /) -> array[_T]: ... + def __buffer__(self, flags: int, /) -> memoryview: ... + def __release_buffer__(self, buffer: memoryview, /) -> None: ... + if sys.version_info >= (3, 12): + def __class_getitem__(cls, item: Any) -> GenericAlias: ... ArrayType = array diff --git a/mypy/typeshed/stdlib/ast.pyi b/mypy/typeshed/stdlib/ast.pyi index ea899e150f97..2525c3642a6f 100644 --- a/mypy/typeshed/stdlib/ast.pyi +++ b/mypy/typeshed/stdlib/ast.pyi @@ -3,29 +3,34 @@ import sys from _ast import * from _typeshed import ReadableBuffer, Unused from collections.abc import Iterator -from typing import Any, TypeVar, overload -from typing_extensions import Literal +from typing import Any, Literal, TypeVar as _TypeVar, overload +from typing_extensions import deprecated -if sys.version_info >= (3, 8): - class _ABC(type): - if sys.version_info >= (3, 9): - def __init__(cls, *args: Unused) -> None: ... +class _ABC(type): + if sys.version_info >= (3, 9): + def __init__(cls, *args: Unused) -> None: ... - class Num(Constant, metaclass=_ABC): - value: int | float | complex +@deprecated("Replaced by ast.Constant; removal scheduled for Python 3.14") +class Num(Constant, metaclass=_ABC): + value: int | float | complex - class Str(Constant, metaclass=_ABC): - value: str - # Aliases for value, for backwards compatibility - s: str +@deprecated("Replaced by ast.Constant; removal scheduled for Python 3.14") +class Str(Constant, metaclass=_ABC): + value: str + # Aliases for value, for backwards compatibility + s: str - class Bytes(Constant, metaclass=_ABC): - value: bytes - # Aliases for value, for backwards compatibility - s: bytes +@deprecated("Replaced by ast.Constant; removal scheduled for Python 3.14") +class Bytes(Constant, metaclass=_ABC): + value: bytes + # Aliases for value, for backwards compatibility + s: bytes - class NameConstant(Constant, metaclass=_ABC): ... - class Ellipsis(Constant, metaclass=_ABC): ... +@deprecated("Replaced by ast.Constant; removal scheduled for Python 3.14") +class NameConstant(Constant, metaclass=_ABC): ... + +@deprecated("Replaced by ast.Constant; removal scheduled for Python 3.14") +class Ellipsis(Constant, metaclass=_ABC): ... if sys.version_info >= (3, 9): class slice(AST): ... @@ -87,10 +92,8 @@ class NodeVisitor: def visit_FormattedValue(self, node: FormattedValue) -> Any: ... def visit_JoinedStr(self, node: JoinedStr) -> Any: ... def visit_Constant(self, node: Constant) -> Any: ... - if sys.version_info >= (3, 8): - def visit_NamedExpr(self, node: NamedExpr) -> Any: ... - def visit_TypeIgnore(self, node: TypeIgnore) -> Any: ... - + def visit_NamedExpr(self, node: NamedExpr) -> Any: ... + def visit_TypeIgnore(self, node: TypeIgnore) -> Any: ... def visit_Attribute(self, node: Attribute) -> Any: ... def visit_Subscript(self, node: Subscript) -> Any: ... def visit_Starred(self, node: Starred) -> Any: ... @@ -138,8 +141,10 @@ class NodeVisitor: def visit_withitem(self, node: withitem) -> Any: ... if sys.version_info >= (3, 10): def visit_Match(self, node: Match) -> Any: ... + def visit_match_case(self, node: match_case) -> Any: ... def visit_MatchValue(self, node: MatchValue) -> Any: ... def visit_MatchSequence(self, node: MatchSequence) -> Any: ... + def visit_MatchSingleton(self, node: MatchSingleton) -> Any: ... def visit_MatchStar(self, node: MatchStar) -> Any: ... def visit_MatchMapping(self, node: MatchMapping) -> Any: ... def visit_MatchClass(self, node: MatchClass) -> Any: ... @@ -149,6 +154,12 @@ class NodeVisitor: if sys.version_info >= (3, 11): def visit_TryStar(self, node: TryStar) -> Any: ... + if sys.version_info >= (3, 12): + def visit_TypeVar(self, node: TypeVar) -> Any: ... + def visit_ParamSpec(self, node: ParamSpec) -> Any: ... + def visit_TypeVarTuple(self, node: TypeVarTuple) -> Any: ... + def visit_TypeAlias(self, node: TypeAlias) -> Any: ... + # visit methods for deprecated nodes def visit_ExtSlice(self, node: ExtSlice) -> Any: ... def visit_Index(self, node: Index) -> Any: ... @@ -168,9 +179,9 @@ class NodeTransformer(NodeVisitor): # The usual return type is AST | None, but Iterable[AST] # is also allowed in some cases -- this needs to be mapped. -_T = TypeVar("_T", bound=AST) +_T = _TypeVar("_T", bound=AST) -if sys.version_info >= (3, 8): +if sys.version_info >= (3, 13): @overload def parse( source: str | ReadableBuffer, @@ -179,6 +190,7 @@ if sys.version_info >= (3, 8): *, type_comments: bool = False, feature_version: None | int | tuple[int, int] = None, + optimize: Literal[-1, 0, 1, 2] = -1, ) -> Module: ... @overload def parse( @@ -188,6 +200,7 @@ if sys.version_info >= (3, 8): *, type_comments: bool = False, feature_version: None | int | tuple[int, int] = None, + optimize: Literal[-1, 0, 1, 2] = -1, ) -> Expression: ... @overload def parse( @@ -197,6 +210,7 @@ if sys.version_info >= (3, 8): *, type_comments: bool = False, feature_version: None | int | tuple[int, int] = None, + optimize: Literal[-1, 0, 1, 2] = -1, ) -> FunctionType: ... @overload def parse( @@ -206,6 +220,7 @@ if sys.version_info >= (3, 8): *, type_comments: bool = False, feature_version: None | int | tuple[int, int] = None, + optimize: Literal[-1, 0, 1, 2] = -1, ) -> Interactive: ... @overload def parse( @@ -214,6 +229,7 @@ if sys.version_info >= (3, 8): mode: Literal["eval"], type_comments: bool = False, feature_version: None | int | tuple[int, int] = None, + optimize: Literal[-1, 0, 1, 2] = -1, ) -> Expression: ... @overload def parse( @@ -222,6 +238,7 @@ if sys.version_info >= (3, 8): mode: Literal["func_type"], type_comments: bool = False, feature_version: None | int | tuple[int, int] = None, + optimize: Literal[-1, 0, 1, 2] = -1, ) -> FunctionType: ... @overload def parse( @@ -230,6 +247,7 @@ if sys.version_info >= (3, 8): mode: Literal["single"], type_comments: bool = False, feature_version: None | int | tuple[int, int] = None, + optimize: Literal[-1, 0, 1, 2] = -1, ) -> Interactive: ... @overload def parse( @@ -239,6 +257,7 @@ if sys.version_info >= (3, 8): *, type_comments: bool = False, feature_version: None | int | tuple[int, int] = None, + optimize: Literal[-1, 0, 1, 2] = -1, ) -> AST: ... else: @@ -247,22 +266,69 @@ else: source: str | ReadableBuffer, filename: str | ReadableBuffer | os.PathLike[Any] = "", mode: Literal["exec"] = "exec", + *, + type_comments: bool = False, + feature_version: None | int | tuple[int, int] = None, ) -> Module: ... @overload def parse( - source: str | ReadableBuffer, filename: str | ReadableBuffer | os.PathLike[Any], mode: Literal["eval"] + source: str | ReadableBuffer, + filename: str | ReadableBuffer | os.PathLike[Any], + mode: Literal["eval"], + *, + type_comments: bool = False, + feature_version: None | int | tuple[int, int] = None, ) -> Expression: ... @overload def parse( - source: str | ReadableBuffer, filename: str | ReadableBuffer | os.PathLike[Any], mode: Literal["single"] + source: str | ReadableBuffer, + filename: str | ReadableBuffer | os.PathLike[Any], + mode: Literal["func_type"], + *, + type_comments: bool = False, + feature_version: None | int | tuple[int, int] = None, + ) -> FunctionType: ... + @overload + def parse( + source: str | ReadableBuffer, + filename: str | ReadableBuffer | os.PathLike[Any], + mode: Literal["single"], + *, + type_comments: bool = False, + feature_version: None | int | tuple[int, int] = None, ) -> Interactive: ... @overload - def parse(source: str | ReadableBuffer, *, mode: Literal["eval"]) -> Expression: ... + def parse( + source: str | ReadableBuffer, + *, + mode: Literal["eval"], + type_comments: bool = False, + feature_version: None | int | tuple[int, int] = None, + ) -> Expression: ... + @overload + def parse( + source: str | ReadableBuffer, + *, + mode: Literal["func_type"], + type_comments: bool = False, + feature_version: None | int | tuple[int, int] = None, + ) -> FunctionType: ... @overload - def parse(source: str | ReadableBuffer, *, mode: Literal["single"]) -> Interactive: ... + def parse( + source: str | ReadableBuffer, + *, + mode: Literal["single"], + type_comments: bool = False, + feature_version: None | int | tuple[int, int] = None, + ) -> Interactive: ... @overload def parse( - source: str | ReadableBuffer, filename: str | ReadableBuffer | os.PathLike[Any] = "", mode: str = "exec" + source: str | ReadableBuffer, + filename: str | ReadableBuffer | os.PathLike[Any] = "", + mode: str = "exec", + *, + type_comments: bool = False, + feature_version: None | int | tuple[int, int] = None, ) -> AST: ... if sys.version_info >= (3, 9): @@ -270,7 +336,17 @@ if sys.version_info >= (3, 9): def copy_location(new_node: _T, old_node: AST) -> _T: ... -if sys.version_info >= (3, 9): +if sys.version_info >= (3, 13): + def dump( + node: AST, + annotate_fields: bool = True, + include_attributes: bool = False, + *, + indent: int | str | None = None, + show_empty: bool = False, + ) -> str: ... + +elif sys.version_info >= (3, 9): def dump( node: AST, annotate_fields: bool = True, include_attributes: bool = False, *, indent: int | str | None = None ) -> str: ... @@ -284,10 +360,7 @@ def increment_lineno(node: _T, n: int = 1) -> _T: ... def iter_child_nodes(node: AST) -> Iterator[AST]: ... def iter_fields(node: AST) -> Iterator[tuple[str, Any]]: ... def literal_eval(node_or_string: str | AST) -> Any: ... - -if sys.version_info >= (3, 8): - def get_source_segment(source: str, node: AST, *, padded: bool = False) -> str | None: ... - +def get_source_segment(source: str, node: AST, *, padded: bool = False) -> str | None: ... def walk(node: AST) -> Iterator[AST]: ... if sys.version_info >= (3, 9): diff --git a/mypy/typeshed/stdlib/asyncio/__init__.pyi b/mypy/typeshed/stdlib/asyncio/__init__.pyi index 4afcd37f5d4a..d5bbe8cb0642 100644 --- a/mypy/typeshed/stdlib/asyncio/__init__.pyi +++ b/mypy/typeshed/stdlib/asyncio/__init__.pyi @@ -1,9 +1,13 @@ import sys +from collections.abc import Awaitable, Coroutine, Generator +from typing import Any, TypeVar +from typing_extensions import TypeAlias # As at runtime, this depends on all submodules defining __all__ accurately. from .base_events import * from .coroutines import * from .events import * +from .exceptions import * from .futures import * from .locks import * from .protocols import * @@ -14,9 +18,6 @@ from .subprocess import * from .tasks import * from .transports import * -if sys.version_info >= (3, 8): - from .exceptions import * - if sys.version_info >= (3, 9): from .threads import * @@ -28,3 +29,13 @@ if sys.platform == "win32": from .windows_events import * else: from .unix_events import * + +_T = TypeVar("_T") + +# Aliases imported by multiple submodules in typeshed +if sys.version_info >= (3, 12): + _AwaitableLike: TypeAlias = Awaitable[_T] # noqa: Y047 + _CoroutineLike: TypeAlias = Coroutine[Any, Any, _T] # noqa: Y047 +else: + _AwaitableLike: TypeAlias = Generator[Any, None, _T] | Awaitable[_T] + _CoroutineLike: TypeAlias = Generator[Any, None, _T] | Coroutine[Any, Any, _T] diff --git a/mypy/typeshed/stdlib/asyncio/base_events.pyi b/mypy/typeshed/stdlib/asyncio/base_events.pyi index 3b8f286710b9..112cfeefa8f2 100644 --- a/mypy/typeshed/stdlib/asyncio/base_events.pyi +++ b/mypy/typeshed/stdlib/asyncio/base_events.pyi @@ -1,16 +1,17 @@ import ssl import sys from _typeshed import FileDescriptorLike, ReadableBuffer, WriteableBuffer +from asyncio import _AwaitableLike, _CoroutineLike from asyncio.events import AbstractEventLoop, AbstractServer, Handle, TimerHandle, _TaskFactory from asyncio.futures import Future from asyncio.protocols import BaseProtocol from asyncio.tasks import Task from asyncio.transports import BaseTransport, DatagramTransport, ReadTransport, SubprocessTransport, Transport, WriteTransport -from collections.abc import Awaitable, Callable, Coroutine, Generator, Iterable, Sequence +from collections.abc import Callable, Iterable, Sequence from contextvars import Context from socket import AddressFamily, SocketKind, _Address, _RetAddress, socket -from typing import IO, Any, TypeVar, overload -from typing_extensions import Literal, TypeAlias +from typing import IO, Any, Literal, TypeVar, overload +from typing_extensions import TypeAlias, TypeVarTuple, Unpack if sys.version_info >= (3, 9): __all__ = ("BaseEventLoop", "Server") @@ -18,6 +19,7 @@ else: __all__ = ("BaseEventLoop",) _T = TypeVar("_T") +_Ts = TypeVarTuple("_Ts") _ProtocolT = TypeVar("_ProtocolT", bound=BaseProtocol) _Context: TypeAlias = dict[str, Any] _ExceptionHandler: TypeAlias = Callable[[AbstractEventLoop, _Context], object] @@ -51,54 +53,45 @@ class Server(AbstractServer): def is_serving(self) -> bool: ... async def start_serving(self) -> None: ... async def serve_forever(self) -> None: ... - if sys.version_info >= (3, 8): - @property - def sockets(self) -> tuple[socket, ...]: ... - else: - @property - def sockets(self) -> list[socket]: ... - + @property + def sockets(self) -> tuple[socket, ...]: ... def close(self) -> None: ... async def wait_closed(self) -> None: ... class BaseEventLoop(AbstractEventLoop): def run_forever(self) -> None: ... - # Can't use a union, see mypy issue # 1873. - @overload - def run_until_complete(self, future: Generator[Any, None, _T]) -> _T: ... - @overload - def run_until_complete(self, future: Awaitable[_T]) -> _T: ... + def run_until_complete(self, future: _AwaitableLike[_T]) -> _T: ... def stop(self) -> None: ... def is_running(self) -> bool: ... def is_closed(self) -> bool: ... def close(self) -> None: ... async def shutdown_asyncgens(self) -> None: ... # Methods scheduling callbacks. All these return Handles. - def call_soon(self, callback: Callable[..., object], *args: Any, context: Context | None = None) -> Handle: ... + def call_soon( + self, callback: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts], context: Context | None = None + ) -> Handle: ... def call_later( - self, delay: float, callback: Callable[..., object], *args: Any, context: Context | None = None + self, delay: float, callback: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts], context: Context | None = None ) -> TimerHandle: ... def call_at( - self, when: float, callback: Callable[..., object], *args: Any, context: Context | None = None + self, when: float, callback: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts], context: Context | None = None ) -> TimerHandle: ... def time(self) -> float: ... # Future methods def create_future(self) -> Future[Any]: ... # Tasks methods if sys.version_info >= (3, 11): - def create_task( - self, coro: Coroutine[Any, Any, _T] | Generator[Any, None, _T], *, name: object = None, context: Context | None = None - ) -> Task[_T]: ... - elif sys.version_info >= (3, 8): - def create_task(self, coro: Coroutine[Any, Any, _T] | Generator[Any, None, _T], *, name: object = None) -> Task[_T]: ... + def create_task(self, coro: _CoroutineLike[_T], *, name: object = None, context: Context | None = None) -> Task[_T]: ... else: - def create_task(self, coro: Coroutine[Any, Any, _T] | Generator[Any, None, _T]) -> Task[_T]: ... + def create_task(self, coro: _CoroutineLike[_T], *, name: object = None) -> Task[_T]: ... def set_task_factory(self, factory: _TaskFactory | None) -> None: ... def get_task_factory(self) -> _TaskFactory | None: ... # Methods for interacting with threads - def call_soon_threadsafe(self, callback: Callable[..., object], *args: Any, context: Context | None = None) -> Handle: ... - def run_in_executor(self, executor: Any, func: Callable[..., _T], *args: Any) -> Future[_T]: ... + def call_soon_threadsafe( + self, callback: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts], context: Context | None = None + ) -> Handle: ... + def run_in_executor(self, executor: Any, func: Callable[[Unpack[_Ts]], _T], *args: Unpack[_Ts]) -> Future[_T]: ... def set_default_executor(self, executor: Any) -> None: ... # Network I/O methods returning Futures. async def getaddrinfo( @@ -112,7 +105,7 @@ class BaseEventLoop(AbstractEventLoop): flags: int = 0, ) -> list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int] | tuple[str, int, int, int]]]: ... async def getnameinfo(self, sockaddr: tuple[str, int] | tuple[str, int, int, int], flags: int = 0) -> tuple[str, str]: ... - if sys.version_info >= (3, 11): + if sys.version_info >= (3, 12): @overload async def create_connection( self, @@ -131,6 +124,7 @@ class BaseEventLoop(AbstractEventLoop): ssl_shutdown_timeout: float | None = None, happy_eyeballs_delay: float | None = None, interleave: int | None = None, + all_errors: bool = False, ) -> tuple[Transport, _ProtocolT]: ... @overload async def create_connection( @@ -150,8 +144,9 @@ class BaseEventLoop(AbstractEventLoop): ssl_shutdown_timeout: float | None = None, happy_eyeballs_delay: float | None = None, interleave: int | None = None, + all_errors: bool = False, ) -> tuple[Transport, _ProtocolT]: ... - elif sys.version_info >= (3, 8): + elif sys.version_info >= (3, 11): @overload async def create_connection( self, @@ -167,6 +162,7 @@ class BaseEventLoop(AbstractEventLoop): local_addr: tuple[str, int] | None = None, server_hostname: str | None = None, ssl_handshake_timeout: float | None = None, + ssl_shutdown_timeout: float | None = None, happy_eyeballs_delay: float | None = None, interleave: int | None = None, ) -> tuple[Transport, _ProtocolT]: ... @@ -185,6 +181,7 @@ class BaseEventLoop(AbstractEventLoop): local_addr: None = None, server_hostname: str | None = None, ssl_handshake_timeout: float | None = None, + ssl_shutdown_timeout: float | None = None, happy_eyeballs_delay: float | None = None, interleave: int | None = None, ) -> tuple[Transport, _ProtocolT]: ... @@ -204,6 +201,8 @@ class BaseEventLoop(AbstractEventLoop): local_addr: tuple[str, int] | None = None, server_hostname: str | None = None, ssl_handshake_timeout: float | None = None, + happy_eyeballs_delay: float | None = None, + interleave: int | None = None, ) -> tuple[Transport, _ProtocolT]: ... @overload async def create_connection( @@ -220,6 +219,8 @@ class BaseEventLoop(AbstractEventLoop): local_addr: None = None, server_hostname: str | None = None, ssl_handshake_timeout: float | None = None, + happy_eyeballs_delay: float | None = None, + interleave: int | None = None, ) -> tuple[Transport, _ProtocolT]: ... if sys.version_info >= (3, 11): @overload @@ -268,7 +269,7 @@ class BaseEventLoop(AbstractEventLoop): server_hostname: str | None = None, ssl_handshake_timeout: float | None = None, ssl_shutdown_timeout: float | None = None, - ) -> Transport: ... + ) -> Transport | None: ... async def connect_accepted_socket( self, protocol_factory: Callable[[], _ProtocolT], @@ -322,7 +323,7 @@ class BaseEventLoop(AbstractEventLoop): server_side: bool = False, server_hostname: str | None = None, ssl_handshake_timeout: float | None = None, - ) -> Transport: ... + ) -> Transport | None: ... async def connect_accepted_socket( self, protocol_factory: Callable[[], _ProtocolT], @@ -387,7 +388,7 @@ class BaseEventLoop(AbstractEventLoop): bufsize: Literal[0] = 0, encoding: None = None, errors: None = None, - text: Literal[False, None] = None, + text: Literal[False] | None = None, **kwargs: Any, ) -> tuple[SubprocessTransport, _ProtocolT]: ... async def subprocess_exec( @@ -405,9 +406,9 @@ class BaseEventLoop(AbstractEventLoop): errors: None = None, **kwargs: Any, ) -> tuple[SubprocessTransport, _ProtocolT]: ... - def add_reader(self, fd: FileDescriptorLike, callback: Callable[..., Any], *args: Any) -> None: ... + def add_reader(self, fd: FileDescriptorLike, callback: Callable[[Unpack[_Ts]], Any], *args: Unpack[_Ts]) -> None: ... def remove_reader(self, fd: FileDescriptorLike) -> bool: ... - def add_writer(self, fd: FileDescriptorLike, callback: Callable[..., Any], *args: Any) -> None: ... + def add_writer(self, fd: FileDescriptorLike, callback: Callable[[Unpack[_Ts]], Any], *args: Unpack[_Ts]) -> None: ... def remove_writer(self, fd: FileDescriptorLike) -> bool: ... # The sock_* methods (and probably some others) are not actually implemented on # BaseEventLoop, only on subclasses. We list them here for now for convenience. @@ -417,11 +418,11 @@ class BaseEventLoop(AbstractEventLoop): async def sock_connect(self, sock: socket, address: _Address) -> None: ... async def sock_accept(self, sock: socket) -> tuple[socket, _RetAddress]: ... if sys.version_info >= (3, 11): - async def sock_recvfrom(self, sock: socket, bufsize: int) -> bytes: ... - async def sock_recvfrom_into(self, sock: socket, buf: WriteableBuffer, nbytes: int = 0) -> int: ... - async def sock_sendto(self, sock: socket, data: ReadableBuffer, address: _Address) -> None: ... + async def sock_recvfrom(self, sock: socket, bufsize: int) -> tuple[bytes, _RetAddress]: ... + async def sock_recvfrom_into(self, sock: socket, buf: WriteableBuffer, nbytes: int = 0) -> tuple[int, _RetAddress]: ... + async def sock_sendto(self, sock: socket, data: ReadableBuffer, address: _Address) -> int: ... # Signal handling. - def add_signal_handler(self, sig: int, callback: Callable[..., Any], *args: Any) -> None: ... + def add_signal_handler(self, sig: int, callback: Callable[[Unpack[_Ts]], Any], *args: Unpack[_Ts]) -> None: ... def remove_signal_handler(self, sig: int) -> bool: ... # Error handlers. def set_exception_handler(self, handler: _ExceptionHandler | None) -> None: ... @@ -431,5 +432,9 @@ class BaseEventLoop(AbstractEventLoop): # Debug flag management. def get_debug(self) -> bool: ... def set_debug(self, enabled: bool) -> None: ... - if sys.version_info >= (3, 9): + if sys.version_info >= (3, 12): + async def shutdown_default_executor(self, timeout: float | None = None) -> None: ... + elif sys.version_info >= (3, 9): async def shutdown_default_executor(self) -> None: ... + + def __del__(self) -> None: ... diff --git a/mypy/typeshed/stdlib/asyncio/base_futures.pyi b/mypy/typeshed/stdlib/asyncio/base_futures.pyi index c51174ef23cd..231766200934 100644 --- a/mypy/typeshed/stdlib/asyncio/base_futures.pyi +++ b/mypy/typeshed/stdlib/asyncio/base_futures.pyi @@ -1,7 +1,6 @@ from collections.abc import Callable, Sequence from contextvars import Context -from typing import Any -from typing_extensions import Literal +from typing import Any, Literal from . import futures diff --git a/mypy/typeshed/stdlib/asyncio/base_subprocess.pyi b/mypy/typeshed/stdlib/asyncio/base_subprocess.pyi index 597c8302988e..a5fe24e8768b 100644 --- a/mypy/typeshed/stdlib/asyncio/base_subprocess.pyi +++ b/mypy/typeshed/stdlib/asyncio/base_subprocess.pyi @@ -46,7 +46,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport): def get_pid(self) -> int | None: ... # type: ignore[override] def get_pipe_transport(self, fd: int) -> _File: ... # type: ignore[override] def _check_proc(self) -> None: ... # undocumented - def send_signal(self, signal: int) -> None: ... # type: ignore[override] + def send_signal(self, signal: int) -> None: ... async def _connect_pipes(self, waiter: futures.Future[Any] | None) -> None: ... # undocumented def _call(self, cb: Callable[..., object], *data: Any) -> None: ... # undocumented def _pipe_connection_lost(self, fd: int, exc: BaseException | None) -> None: ... # undocumented @@ -55,6 +55,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport): async def _wait(self) -> int: ... # undocumented def _try_finish(self) -> None: ... # undocumented def _call_connection_lost(self, exc: BaseException | None) -> None: ... # undocumented + def __del__(self) -> None: ... class WriteSubprocessPipeProto(protocols.BaseProtocol): # undocumented def __init__(self, proc: BaseSubprocessTransport, fd: int) -> None: ... diff --git a/mypy/typeshed/stdlib/asyncio/constants.pyi b/mypy/typeshed/stdlib/asyncio/constants.pyi index af209fa9ee62..7759a2844953 100644 --- a/mypy/typeshed/stdlib/asyncio/constants.pyi +++ b/mypy/typeshed/stdlib/asyncio/constants.pyi @@ -1,6 +1,6 @@ import enum import sys -from typing_extensions import Literal +from typing import Literal LOG_THRESHOLD_FOR_CONNLOST_WRITES: Literal[5] ACCEPT_RETRY_DELAY: Literal[1] @@ -11,8 +11,10 @@ if sys.version_info >= (3, 11): SSL_SHUTDOWN_TIMEOUT: float FLOW_CONTROL_HIGH_WATER_SSL_READ: Literal[256] FLOW_CONTROL_HIGH_WATER_SSL_WRITE: Literal[512] +if sys.version_info >= (3, 12): + THREAD_JOIN_TIMEOUT: Literal[300] class _SendfileMode(enum.Enum): - UNSUPPORTED: int - TRY_NATIVE: int - FALLBACK: int + UNSUPPORTED = 1 + TRY_NATIVE = 2 + FALLBACK = 3 diff --git a/mypy/typeshed/stdlib/asyncio/coroutines.pyi b/mypy/typeshed/stdlib/asyncio/coroutines.pyi index 14fb627ae6fe..bc797de7fd51 100644 --- a/mypy/typeshed/stdlib/asyncio/coroutines.pyi +++ b/mypy/typeshed/stdlib/asyncio/coroutines.pyi @@ -1,7 +1,7 @@ import sys from collections.abc import Awaitable, Callable, Coroutine from typing import Any, TypeVar, overload -from typing_extensions import ParamSpec, TypeGuard +from typing_extensions import ParamSpec, TypeGuard, TypeIs if sys.version_info >= (3, 11): __all__ = ("iscoroutinefunction", "iscoroutine") @@ -23,6 +23,4 @@ def iscoroutinefunction(func: Callable[_P, Awaitable[_T]]) -> TypeGuard[Callable def iscoroutinefunction(func: Callable[_P, object]) -> TypeGuard[Callable[_P, Coroutine[Any, Any, Any]]]: ... @overload def iscoroutinefunction(func: object) -> TypeGuard[Callable[..., Coroutine[Any, Any, Any]]]: ... - -# Can actually be a generator-style coroutine on Python 3.7 -def iscoroutine(obj: object) -> TypeGuard[Coroutine[Any, Any, Any]]: ... +def iscoroutine(obj: object) -> TypeIs[Coroutine[Any, Any, Any]]: ... diff --git a/mypy/typeshed/stdlib/asyncio/events.pyi b/mypy/typeshed/stdlib/asyncio/events.pyi index f97afe873c9f..95de28c5021e 100644 --- a/mypy/typeshed/stdlib/asyncio/events.pyi +++ b/mypy/typeshed/stdlib/asyncio/events.pyi @@ -2,12 +2,13 @@ import ssl import sys from _typeshed import FileDescriptorLike, ReadableBuffer, StrPath, Unused, WriteableBuffer from abc import ABCMeta, abstractmethod -from collections.abc import Awaitable, Callable, Coroutine, Generator, Sequence +from collections.abc import Callable, Coroutine, Generator, Sequence from contextvars import Context from socket import AddressFamily, SocketKind, _Address, _RetAddress, socket -from typing import IO, Any, Protocol, TypeVar, overload -from typing_extensions import Literal, Self, TypeAlias +from typing import IO, Any, Literal, Protocol, TypeVar, overload +from typing_extensions import Self, TypeAlias, TypeVarTuple, Unpack, deprecated +from . import _AwaitableLike, _CoroutineLike from .base_events import Server from .futures import Future from .protocols import BaseProtocol @@ -15,46 +16,26 @@ from .tasks import Task from .transports import BaseTransport, DatagramTransport, ReadTransport, SubprocessTransport, Transport, WriteTransport from .unix_events import AbstractChildWatcher -if sys.version_info >= (3, 8): - __all__ = ( - "AbstractEventLoopPolicy", - "AbstractEventLoop", - "AbstractServer", - "Handle", - "TimerHandle", - "get_event_loop_policy", - "set_event_loop_policy", - "get_event_loop", - "set_event_loop", - "new_event_loop", - "get_child_watcher", - "set_child_watcher", - "_set_running_loop", - "get_running_loop", - "_get_running_loop", - ) - -else: - __all__ = ( - "AbstractEventLoopPolicy", - "AbstractEventLoop", - "AbstractServer", - "Handle", - "TimerHandle", - "SendfileNotAvailableError", - "get_event_loop_policy", - "set_event_loop_policy", - "get_event_loop", - "set_event_loop", - "new_event_loop", - "get_child_watcher", - "set_child_watcher", - "_set_running_loop", - "get_running_loop", - "_get_running_loop", - ) +__all__ = ( + "AbstractEventLoopPolicy", + "AbstractEventLoop", + "AbstractServer", + "Handle", + "TimerHandle", + "get_event_loop_policy", + "set_event_loop_policy", + "get_event_loop", + "set_event_loop", + "new_event_loop", + "get_child_watcher", + "set_child_watcher", + "_set_running_loop", + "get_running_loop", + "_get_running_loop", +) _T = TypeVar("_T") +_Ts = TypeVarTuple("_Ts") _ProtocolT = TypeVar("_ProtocolT", bound=BaseProtocol) _Context: TypeAlias = dict[str, Any] _ExceptionHandler: TypeAlias = Callable[[AbstractEventLoop, _Context], object] @@ -62,9 +43,7 @@ _ProtocolFactory: TypeAlias = Callable[[], BaseProtocol] _SSLContext: TypeAlias = bool | None | ssl.SSLContext class _TaskFactory(Protocol): - def __call__( - self, __loop: AbstractEventLoop, __factory: Coroutine[Any, Any, _T] | Generator[Any, None, _T] - ) -> Future[_T]: ... + def __call__(self, loop: AbstractEventLoop, factory: Coroutine[Any, Any, _T] | Generator[Any, None, _T], /) -> Future[_T]: ... class Handle: _cancelled: bool @@ -75,6 +54,8 @@ class Handle: def cancel(self) -> None: ... def _run(self) -> None: ... def cancelled(self) -> bool: ... + if sys.version_info >= (3, 12): + def get_context(self) -> Context: ... class TimerHandle(Handle): def __init__( @@ -85,6 +66,7 @@ class TimerHandle(Handle): loop: AbstractEventLoop, context: Context | None = None, ) -> None: ... + def __hash__(self) -> int: ... def when(self) -> float: ... def __lt__(self, other: TimerHandle) -> bool: ... def __le__(self, other: TimerHandle) -> bool: ... @@ -112,13 +94,8 @@ class AbstractEventLoop: slow_callback_duration: float @abstractmethod def run_forever(self) -> None: ... - # Can't use a union, see mypy issue # 1873. - @overload @abstractmethod - def run_until_complete(self, future: Generator[Any, None, _T]) -> _T: ... - @overload - @abstractmethod - def run_until_complete(self, future: Awaitable[_T]) -> _T: ... + def run_until_complete(self, future: _AwaitableLike[_T]) -> _T: ... @abstractmethod def stop(self) -> None: ... @abstractmethod @@ -132,22 +109,24 @@ class AbstractEventLoop: # Methods scheduling callbacks. All these return Handles. if sys.version_info >= (3, 9): # "context" added in 3.9.10/3.10.2 @abstractmethod - def call_soon(self, callback: Callable[..., object], *args: Any, context: Context | None = None) -> Handle: ... + def call_soon( + self, callback: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts], context: Context | None = None + ) -> Handle: ... @abstractmethod def call_later( - self, delay: float, callback: Callable[..., object], *args: Any, context: Context | None = None + self, delay: float, callback: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts], context: Context | None = None ) -> TimerHandle: ... @abstractmethod def call_at( - self, when: float, callback: Callable[..., object], *args: Any, context: Context | None = None + self, when: float, callback: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts], context: Context | None = None ) -> TimerHandle: ... else: @abstractmethod - def call_soon(self, callback: Callable[..., object], *args: Any) -> Handle: ... + def call_soon(self, callback: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts]) -> Handle: ... @abstractmethod - def call_later(self, delay: float, callback: Callable[..., object], *args: Any) -> TimerHandle: ... + def call_later(self, delay: float, callback: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts]) -> TimerHandle: ... @abstractmethod - def call_at(self, when: float, callback: Callable[..., object], *args: Any) -> TimerHandle: ... + def call_at(self, when: float, callback: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts]) -> TimerHandle: ... @abstractmethod def time(self) -> float: ... @@ -158,20 +137,11 @@ class AbstractEventLoop: if sys.version_info >= (3, 11): @abstractmethod def create_task( - self, - coro: Coroutine[Any, Any, _T] | Generator[Any, None, _T], - *, - name: str | None = None, - context: Context | None = None, - ) -> Task[_T]: ... - elif sys.version_info >= (3, 8): - @abstractmethod - def create_task( - self, coro: Coroutine[Any, Any, _T] | Generator[Any, None, _T], *, name: str | None = None + self, coro: _CoroutineLike[_T], *, name: str | None = None, context: Context | None = None ) -> Task[_T]: ... else: @abstractmethod - def create_task(self, coro: Coroutine[Any, Any, _T] | Generator[Any, None, _T]) -> Task[_T]: ... + def create_task(self, coro: _CoroutineLike[_T], *, name: str | None = None) -> Task[_T]: ... @abstractmethod def set_task_factory(self, factory: _TaskFactory | None) -> None: ... @@ -180,13 +150,15 @@ class AbstractEventLoop: # Methods for interacting with threads if sys.version_info >= (3, 9): # "context" added in 3.9.10/3.10.2 @abstractmethod - def call_soon_threadsafe(self, callback: Callable[..., object], *args: Any, context: Context | None = None) -> Handle: ... + def call_soon_threadsafe( + self, callback: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts], context: Context | None = None + ) -> Handle: ... else: @abstractmethod - def call_soon_threadsafe(self, callback: Callable[..., object], *args: Any) -> Handle: ... + def call_soon_threadsafe(self, callback: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts]) -> Handle: ... @abstractmethod - def run_in_executor(self, executor: Any, func: Callable[..., _T], *args: Any) -> Future[_T]: ... + def run_in_executor(self, executor: Any, func: Callable[[Unpack[_Ts]], _T], *args: Unpack[_Ts]) -> Future[_T]: ... @abstractmethod def set_default_executor(self, executor: Any) -> None: ... # Network I/O methods returning Futures. @@ -244,7 +216,7 @@ class AbstractEventLoop: happy_eyeballs_delay: float | None = None, interleave: int | None = None, ) -> tuple[Transport, _ProtocolT]: ... - elif sys.version_info >= (3, 8): + else: @overload @abstractmethod async def create_connection( @@ -283,41 +255,6 @@ class AbstractEventLoop: happy_eyeballs_delay: float | None = None, interleave: int | None = None, ) -> tuple[Transport, _ProtocolT]: ... - else: - @overload - @abstractmethod - async def create_connection( - self, - protocol_factory: Callable[[], _ProtocolT], - host: str = ..., - port: int = ..., - *, - ssl: _SSLContext = None, - family: int = 0, - proto: int = 0, - flags: int = 0, - sock: None = None, - local_addr: tuple[str, int] | None = None, - server_hostname: str | None = None, - ssl_handshake_timeout: float | None = None, - ) -> tuple[Transport, _ProtocolT]: ... - @overload - @abstractmethod - async def create_connection( - self, - protocol_factory: Callable[[], _ProtocolT], - host: None = None, - port: None = None, - *, - ssl: _SSLContext = None, - family: int = 0, - proto: int = 0, - flags: int = 0, - sock: socket, - local_addr: None = None, - server_hostname: str | None = None, - ssl_handshake_timeout: float | None = None, - ) -> tuple[Transport, _ProtocolT]: ... if sys.version_info >= (3, 11): @overload @abstractmethod @@ -368,7 +305,7 @@ class AbstractEventLoop: server_hostname: str | None = None, ssl_handshake_timeout: float | None = None, ssl_shutdown_timeout: float | None = None, - ) -> Transport: ... + ) -> Transport | None: ... async def create_unix_server( self, protocol_factory: _ProtocolFactory, @@ -428,7 +365,7 @@ class AbstractEventLoop: server_side: bool = False, server_hostname: str | None = None, ssl_handshake_timeout: float | None = None, - ) -> Transport: ... + ) -> Transport | None: ... async def create_unix_server( self, protocol_factory: _ProtocolFactory, @@ -529,7 +466,7 @@ class AbstractEventLoop: bufsize: Literal[0] = 0, encoding: None = None, errors: None = None, - text: Literal[False, None] = ..., + text: Literal[False] | None = ..., **kwargs: Any, ) -> tuple[SubprocessTransport, _ProtocolT]: ... @abstractmethod @@ -549,14 +486,13 @@ class AbstractEventLoop: **kwargs: Any, ) -> tuple[SubprocessTransport, _ProtocolT]: ... @abstractmethod - def add_reader(self, fd: FileDescriptorLike, callback: Callable[..., Any], *args: Any) -> None: ... + def add_reader(self, fd: FileDescriptorLike, callback: Callable[[Unpack[_Ts]], Any], *args: Unpack[_Ts]) -> None: ... @abstractmethod def remove_reader(self, fd: FileDescriptorLike) -> bool: ... @abstractmethod - def add_writer(self, fd: FileDescriptorLike, callback: Callable[..., Any], *args: Any) -> None: ... + def add_writer(self, fd: FileDescriptorLike, callback: Callable[[Unpack[_Ts]], Any], *args: Unpack[_Ts]) -> None: ... @abstractmethod def remove_writer(self, fd: FileDescriptorLike) -> bool: ... - # Completion based I/O methods returning Futures prior to 3.7 @abstractmethod async def sock_recv(self, sock: socket, nbytes: int) -> bytes: ... @abstractmethod @@ -569,14 +505,14 @@ class AbstractEventLoop: async def sock_accept(self, sock: socket) -> tuple[socket, _RetAddress]: ... if sys.version_info >= (3, 11): @abstractmethod - async def sock_recvfrom(self, sock: socket, bufsize: int) -> bytes: ... + async def sock_recvfrom(self, sock: socket, bufsize: int) -> tuple[bytes, _RetAddress]: ... @abstractmethod - async def sock_recvfrom_into(self, sock: socket, buf: WriteableBuffer, nbytes: int = 0) -> int: ... + async def sock_recvfrom_into(self, sock: socket, buf: WriteableBuffer, nbytes: int = 0) -> tuple[int, _RetAddress]: ... @abstractmethod - async def sock_sendto(self, sock: socket, data: ReadableBuffer, address: _Address) -> None: ... + async def sock_sendto(self, sock: socket, data: ReadableBuffer, address: _Address) -> int: ... # Signal handling. @abstractmethod - def add_signal_handler(self, sig: int, callback: Callable[..., object], *args: Any) -> None: ... + def add_signal_handler(self, sig: int, callback: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts]) -> None: ... @abstractmethod def remove_signal_handler(self, sig: int) -> bool: ... # Error handlers. @@ -605,10 +541,18 @@ class AbstractEventLoopPolicy: @abstractmethod def new_event_loop(self) -> AbstractEventLoop: ... # Child processes handling (Unix only). - @abstractmethod - def get_child_watcher(self) -> AbstractChildWatcher: ... - @abstractmethod - def set_child_watcher(self, watcher: AbstractChildWatcher) -> None: ... + if sys.version_info >= (3, 12): + @abstractmethod + @deprecated("Deprecated as of Python 3.12; will be removed in Python 3.14") + def get_child_watcher(self) -> AbstractChildWatcher: ... + @abstractmethod + @deprecated("Deprecated as of Python 3.12; will be removed in Python 3.14") + def set_child_watcher(self, watcher: AbstractChildWatcher) -> None: ... + else: + @abstractmethod + def get_child_watcher(self) -> AbstractChildWatcher: ... + @abstractmethod + def set_child_watcher(self, watcher: AbstractChildWatcher) -> None: ... class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy, metaclass=ABCMeta): def get_event_loop(self) -> AbstractEventLoop: ... @@ -620,11 +564,17 @@ def set_event_loop_policy(policy: AbstractEventLoopPolicy | None) -> None: ... def get_event_loop() -> AbstractEventLoop: ... def set_event_loop(loop: AbstractEventLoop | None) -> None: ... def new_event_loop() -> AbstractEventLoop: ... -def get_child_watcher() -> AbstractChildWatcher: ... -def set_child_watcher(watcher: AbstractChildWatcher) -> None: ... -def _set_running_loop(__loop: AbstractEventLoop | None) -> None: ... + +if sys.version_info >= (3, 12): + @deprecated("Deprecated as of Python 3.12; will be removed in Python 3.14") + def get_child_watcher() -> AbstractChildWatcher: ... + @deprecated("Deprecated as of Python 3.12; will be removed in Python 3.14") + def set_child_watcher(watcher: AbstractChildWatcher) -> None: ... + +else: + def get_child_watcher() -> AbstractChildWatcher: ... + def set_child_watcher(watcher: AbstractChildWatcher) -> None: ... + +def _set_running_loop(loop: AbstractEventLoop | None, /) -> None: ... def _get_running_loop() -> AbstractEventLoop: ... def get_running_loop() -> AbstractEventLoop: ... - -if sys.version_info < (3, 8): - class SendfileNotAvailableError(RuntimeError): ... diff --git a/mypy/typeshed/stdlib/asyncio/exceptions.pyi b/mypy/typeshed/stdlib/asyncio/exceptions.pyi index 075fbb805bb9..0746394d582f 100644 --- a/mypy/typeshed/stdlib/asyncio/exceptions.pyi +++ b/mypy/typeshed/stdlib/asyncio/exceptions.pyi @@ -21,7 +21,12 @@ else: ) class CancelledError(BaseException): ... -class TimeoutError(Exception): ... + +if sys.version_info >= (3, 11): + from builtins import TimeoutError as TimeoutError +else: + class TimeoutError(Exception): ... + class InvalidStateError(Exception): ... class SendfileNotAvailableError(RuntimeError): ... diff --git a/mypy/typeshed/stdlib/asyncio/futures.pyi b/mypy/typeshed/stdlib/asyncio/futures.pyi index 79209f5ed4fb..a3953cdaf8c7 100644 --- a/mypy/typeshed/stdlib/asyncio/futures.pyi +++ b/mypy/typeshed/stdlib/asyncio/futures.pyi @@ -1,37 +1,28 @@ import sys from collections.abc import Awaitable, Callable, Generator, Iterable -from concurrent.futures._base import Error, Future as _ConcurrentFuture -from typing import Any, TypeVar -from typing_extensions import Literal, Self, TypeGuard +from concurrent.futures._base import Future as _ConcurrentFuture +from contextvars import Context +from typing import Any, Literal, TypeVar +from typing_extensions import Self, TypeIs from .events import AbstractEventLoop -if sys.version_info < (3, 8): - from concurrent.futures import CancelledError as CancelledError, TimeoutError as TimeoutError - - class InvalidStateError(Error): ... - -from contextvars import Context - if sys.version_info >= (3, 9): from types import GenericAlias -if sys.version_info >= (3, 8): - __all__ = ("Future", "wrap_future", "isfuture") -else: - __all__ = ("CancelledError", "TimeoutError", "InvalidStateError", "Future", "wrap_future", "isfuture") +__all__ = ("Future", "wrap_future", "isfuture") _T = TypeVar("_T") # asyncio defines 'isfuture()' in base_futures.py and re-imports it in futures.py # but it leads to circular import error in pytype tool. # That's why the import order is reversed. -def isfuture(obj: object) -> TypeGuard[Future[Any]]: ... +def isfuture(obj: object) -> TypeIs[Future[Any]]: ... class Future(Awaitable[_T], Iterable[_T]): _state: str @property - def _exception(self) -> BaseException: ... + def _exception(self) -> BaseException | None: ... _blocking: bool @property def _log_traceback(self) -> bool: ... @@ -43,7 +34,7 @@ class Future(Awaitable[_T], Iterable[_T]): def get_loop(self) -> AbstractEventLoop: ... @property def _callbacks(self) -> list[tuple[Callable[[Self], Any], Context]]: ... - def add_done_callback(self, __fn: Callable[[Self], object], *, context: Context | None = None) -> None: ... + def add_done_callback(self, fn: Callable[[Self], object], /, *, context: Context | None = None) -> None: ... if sys.version_info >= (3, 9): def cancel(self, msg: Any | None = None) -> bool: ... else: @@ -53,9 +44,9 @@ class Future(Awaitable[_T], Iterable[_T]): def done(self) -> bool: ... def result(self) -> _T: ... def exception(self) -> BaseException | None: ... - def remove_done_callback(self, __fn: Callable[[Self], object]) -> int: ... - def set_result(self, __result: _T) -> None: ... - def set_exception(self, __exception: type | BaseException) -> None: ... + def remove_done_callback(self, fn: Callable[[Self], object], /) -> int: ... + def set_result(self, result: _T, /) -> None: ... + def set_exception(self, exception: type | BaseException, /) -> None: ... def __iter__(self) -> Generator[Any, None, _T]: ... def __await__(self) -> Generator[Any, None, _T]: ... @property diff --git a/mypy/typeshed/stdlib/asyncio/locks.pyi b/mypy/typeshed/stdlib/asyncio/locks.pyi index ab4e63ab59b1..0114aeb23329 100644 --- a/mypy/typeshed/stdlib/asyncio/locks.pyi +++ b/mypy/typeshed/stdlib/asyncio/locks.pyi @@ -4,14 +4,16 @@ from _typeshed import Unused from collections import deque from collections.abc import Callable, Generator from types import TracebackType -from typing import Any, TypeVar -from typing_extensions import Literal, Self +from typing import Any, Literal, TypeVar +from typing_extensions import Self from .events import AbstractEventLoop from .futures import Future -if sys.version_info >= (3, 11): +if sys.version_info >= (3, 10): from .mixins import _LoopBoundMixin +else: + _LoopBoundMixin = object if sys.version_info >= (3, 11): __all__ = ("Lock", "Event", "Condition", "Semaphore", "BoundedSemaphore", "Barrier") @@ -44,7 +46,8 @@ else: self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None ) -> None: ... -class Lock(_ContextManagerMixin): +class Lock(_ContextManagerMixin, _LoopBoundMixin): + _waiters: deque[Future[Any]] | None if sys.version_info >= (3, 10): def __init__(self) -> None: ... else: @@ -54,7 +57,8 @@ class Lock(_ContextManagerMixin): async def acquire(self) -> Literal[True]: ... def release(self) -> None: ... -class Event: +class Event(_LoopBoundMixin): + _waiters: deque[Future[Any]] if sys.version_info >= (3, 10): def __init__(self) -> None: ... else: @@ -65,7 +69,8 @@ class Event: def clear(self) -> None: ... async def wait(self) -> Literal[True]: ... -class Condition(_ContextManagerMixin): +class Condition(_ContextManagerMixin, _LoopBoundMixin): + _waiters: deque[Future[Any]] if sys.version_info >= (3, 10): def __init__(self, lock: Lock | None = None) -> None: ... else: @@ -79,9 +84,9 @@ class Condition(_ContextManagerMixin): def notify(self, n: int = 1) -> None: ... def notify_all(self) -> None: ... -class Semaphore(_ContextManagerMixin): +class Semaphore(_ContextManagerMixin, _LoopBoundMixin): _value: int - _waiters: deque[Future[Any]] + _waiters: deque[Future[Any]] | None if sys.version_info >= (3, 10): def __init__(self, value: int = 1) -> None: ... else: @@ -96,10 +101,10 @@ class BoundedSemaphore(Semaphore): ... if sys.version_info >= (3, 11): class _BarrierState(enum.Enum): # undocumented - FILLING: str - DRAINING: str - RESETTING: str - BROKEN: str + FILLING = "filling" + DRAINING = "draining" + RESETTING = "resetting" + BROKEN = "broken" class Barrier(_LoopBoundMixin): def __init__(self, parties: int) -> None: ... diff --git a/mypy/typeshed/stdlib/asyncio/proactor_events.pyi b/mypy/typeshed/stdlib/asyncio/proactor_events.pyi index 33fdf84ade4a..957fdd6ce255 100644 --- a/mypy/typeshed/stdlib/asyncio/proactor_events.pyi +++ b/mypy/typeshed/stdlib/asyncio/proactor_events.pyi @@ -1,19 +1,12 @@ import sys from collections.abc import Mapping from socket import socket -from typing import Any, ClassVar, Protocol -from typing_extensions import Literal +from typing import Any, ClassVar, Literal from . import base_events, constants, events, futures, streams, transports __all__ = ("BaseProactorEventLoop",) -if sys.version_info >= (3, 8): - class _WarnCallbackProtocol(Protocol): - def __call__( - self, message: str, category: type[Warning] | None = ..., stacklevel: int = ..., source: Any | None = ... - ) -> object: ... - class _ProactorBasePipeTransport(transports._FlowControlMixin, transports.BaseTransport): def __init__( self, @@ -24,10 +17,7 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin, transports.BaseTr extra: Mapping[Any, Any] | None = None, server: events.AbstractServer | None = None, ) -> None: ... - if sys.version_info >= (3, 8): - def __del__(self, _warn: _WarnCallbackProtocol = ...) -> None: ... - else: - def __del__(self) -> None: ... + def __del__(self) -> None: ... class _ProactorReadPipeTransport(_ProactorBasePipeTransport, transports.ReadTransport): if sys.version_info >= (3, 10): diff --git a/mypy/typeshed/stdlib/asyncio/queues.pyi b/mypy/typeshed/stdlib/asyncio/queues.pyi index f56a09524e71..bb4ee71f9267 100644 --- a/mypy/typeshed/stdlib/asyncio/queues.pyi +++ b/mypy/typeshed/stdlib/asyncio/queues.pyi @@ -5,6 +5,11 @@ from typing import Any, Generic, TypeVar if sys.version_info >= (3, 9): from types import GenericAlias +if sys.version_info >= (3, 10): + from .mixins import _LoopBoundMixin +else: + _LoopBoundMixin = object + __all__ = ("Queue", "PriorityQueue", "LifoQueue", "QueueFull", "QueueEmpty") class QueueEmpty(Exception): ... @@ -12,7 +17,9 @@ class QueueFull(Exception): ... _T = TypeVar("_T") -class Queue(Generic[_T]): +# If Generic[_T] is last and _LoopBoundMixin is object, pyright is unhappy. +# We can remove the noqa pragma when dropping 3.9 support. +class Queue(Generic[_T], _LoopBoundMixin): # noqa: Y059 if sys.version_info >= (3, 10): def __init__(self, maxsize: int = 0) -> None: ... else: diff --git a/mypy/typeshed/stdlib/asyncio/runners.pyi b/mypy/typeshed/stdlib/asyncio/runners.pyi index 847072b633ac..37a85b709cdc 100644 --- a/mypy/typeshed/stdlib/asyncio/runners.pyi +++ b/mypy/typeshed/stdlib/asyncio/runners.pyi @@ -2,8 +2,8 @@ import sys from _typeshed import Unused from collections.abc import Callable, Coroutine from contextvars import Context -from typing import Any, TypeVar -from typing_extensions import Self, final +from typing import Any, TypeVar, final +from typing_extensions import Self from .events import AbstractEventLoop @@ -28,8 +28,5 @@ if sys.version_info >= (3, 12): main: Coroutine[Any, Any, _T], *, debug: bool | None = ..., loop_factory: Callable[[], AbstractEventLoop] | None = ... ) -> _T: ... -elif sys.version_info >= (3, 8): - def run(main: Coroutine[Any, Any, _T], *, debug: bool | None = None) -> _T: ... - else: - def run(main: Coroutine[Any, Any, _T], *, debug: bool = False) -> _T: ... + def run(main: Coroutine[Any, Any, _T], *, debug: bool | None = None) -> _T: ... diff --git a/mypy/typeshed/stdlib/asyncio/sslproto.pyi b/mypy/typeshed/stdlib/asyncio/sslproto.pyi index aadc7d32b40f..e904d7395cdc 100644 --- a/mypy/typeshed/stdlib/asyncio/sslproto.pyi +++ b/mypy/typeshed/stdlib/asyncio/sslproto.pyi @@ -3,8 +3,8 @@ import sys from collections import deque from collections.abc import Callable from enum import Enum -from typing import Any, ClassVar -from typing_extensions import Literal, TypeAlias +from typing import Any, ClassVar, Literal +from typing_extensions import TypeAlias from . import constants, events, futures, protocols, transports @@ -14,17 +14,18 @@ if sys.version_info >= (3, 11): SSLAgainErrors: tuple[type[ssl.SSLWantReadError], type[ssl.SSLSyscallError]] class SSLProtocolState(Enum): - UNWRAPPED: str - DO_HANDSHAKE: str - WRAPPED: str - FLUSHING: str - SHUTDOWN: str + UNWRAPPED = "UNWRAPPED" + DO_HANDSHAKE = "DO_HANDSHAKE" + WRAPPED = "WRAPPED" + FLUSHING = "FLUSHING" + SHUTDOWN = "SHUTDOWN" class AppProtocolState(Enum): - STATE_INIT: str - STATE_CON_MADE: str - STATE_EOF: str - STATE_CON_LOST: str + STATE_INIT = "STATE_INIT" + STATE_CON_MADE = "STATE_CON_MADE" + STATE_EOF = "STATE_EOF" + STATE_CON_LOST = "STATE_CON_LOST" + def add_flowcontrol_defaults(high: int | None, low: int | None, kb: int) -> tuple[int, int]: ... else: @@ -66,7 +67,10 @@ class _SSLProtocolTransport(transports._FlowControlMixin, transports.Transport): _sendfile_compatible: ClassVar[constants._SendfileMode] _loop: events.AbstractEventLoop - _ssl_protocol: SSLProtocol + if sys.version_info >= (3, 11): + _ssl_protocol: SSLProtocol | None + else: + _ssl_protocol: SSLProtocol _closed: bool def __init__(self, loop: events.AbstractEventLoop, ssl_protocol: SSLProtocol) -> None: ... def get_extra_info(self, name: str, default: Any | None = None) -> dict[str, Any]: ... @@ -80,6 +84,8 @@ class _SSLProtocolTransport(transports._FlowControlMixin, transports.Transport): def set_read_buffer_limits(self, high: int | None = None, low: int | None = None) -> None: ... def get_read_buffer_size(self) -> int: ... + def __del__(self) -> None: ... + if sys.version_info >= (3, 11): _SSLProtocolBase: TypeAlias = protocols.BufferedProtocol else: @@ -150,9 +156,10 @@ class SSLProtocol(_SSLProtocolBase): def _check_handshake_timeout(self) -> None: ... def _on_handshake_complete(self, handshake_exc: BaseException | None) -> None: ... def _fatal_error(self, exc: BaseException, message: str = "Fatal error on transport") -> None: ... - def _abort(self) -> None: ... if sys.version_info >= (3, 11): + def _abort(self, exc: BaseException | None) -> None: ... def get_buffer(self, n: int) -> memoryview: ... else: + def _abort(self) -> None: ... def _finalize(self) -> None: ... def _process_write_backlog(self) -> None: ... diff --git a/mypy/typeshed/stdlib/asyncio/streams.pyi b/mypy/typeshed/stdlib/asyncio/streams.pyi index f30c57305d93..c3cc7b8c9e5a 100644 --- a/mypy/typeshed/stdlib/asyncio/streams.pyi +++ b/mypy/typeshed/stdlib/asyncio/streams.pyi @@ -1,61 +1,29 @@ import ssl import sys -from _typeshed import StrPath -from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Sequence -from typing import Any -from typing_extensions import Self, SupportsIndex, TypeAlias +from _typeshed import ReadableBuffer, StrPath +from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Sequence, Sized +from typing import Any, Protocol, SupportsIndex +from typing_extensions import Self, TypeAlias from . import events, protocols, transports from .base_events import Server if sys.platform == "win32": - if sys.version_info >= (3, 8): - __all__ = ("StreamReader", "StreamWriter", "StreamReaderProtocol", "open_connection", "start_server") - else: - __all__ = ( - "StreamReader", - "StreamWriter", - "StreamReaderProtocol", - "open_connection", - "start_server", - "IncompleteReadError", - "LimitOverrunError", - ) + __all__ = ("StreamReader", "StreamWriter", "StreamReaderProtocol", "open_connection", "start_server") else: - if sys.version_info >= (3, 8): - __all__ = ( - "StreamReader", - "StreamWriter", - "StreamReaderProtocol", - "open_connection", - "start_server", - "open_unix_connection", - "start_unix_server", - ) - else: - __all__ = ( - "StreamReader", - "StreamWriter", - "StreamReaderProtocol", - "open_connection", - "start_server", - "IncompleteReadError", - "LimitOverrunError", - "open_unix_connection", - "start_unix_server", - ) + __all__ = ( + "StreamReader", + "StreamWriter", + "StreamReaderProtocol", + "open_connection", + "start_server", + "open_unix_connection", + "start_unix_server", + ) _ClientConnectedCallback: TypeAlias = Callable[[StreamReader, StreamWriter], Awaitable[None] | None] -if sys.version_info < (3, 8): - class IncompleteReadError(EOFError): - expected: int | None - partial: bytes - def __init__(self, partial: bytes, expected: int | None) -> None: ... - - class LimitOverrunError(Exception): - consumed: int - def __init__(self, message: str, consumed: int) -> None: ... +class _ReaduntilBuffer(ReadableBuffer, Sized, Protocol): ... if sys.version_info >= (3, 10): async def open_connection( @@ -128,6 +96,7 @@ class StreamReaderProtocol(FlowControlMixin, protocols.Protocol): client_connected_cb: _ClientConnectedCallback | None = None, loop: events.AbstractEventLoop | None = None, ) -> None: ... + def __del__(self) -> None: ... class StreamWriter: def __init__( @@ -148,10 +117,21 @@ class StreamWriter: async def wait_closed(self) -> None: ... def get_extra_info(self, name: str, default: Any = None) -> Any: ... async def drain(self) -> None: ... - if sys.version_info >= (3, 11): + if sys.version_info >= (3, 12): + async def start_tls( + self, + sslcontext: ssl.SSLContext, + *, + server_hostname: str | None = None, + ssl_handshake_timeout: float | None = None, + ssl_shutdown_timeout: float | None = None, + ) -> None: ... + elif sys.version_info >= (3, 11): async def start_tls( self, sslcontext: ssl.SSLContext, *, server_hostname: str | None = None, ssl_handshake_timeout: float | None = None ) -> None: ... + if sys.version_info >= (3, 11): + def __del__(self) -> None: ... class StreamReader(AsyncIterator[bytes]): def __init__(self, limit: int = 65536, loop: events.AbstractEventLoop | None = None) -> None: ... @@ -162,8 +142,11 @@ class StreamReader(AsyncIterator[bytes]): def at_eof(self) -> bool: ... def feed_data(self, data: Iterable[SupportsIndex]) -> None: ... async def readline(self) -> bytes: ... - # Can be any buffer that supports len(); consider changing to a Protocol if PEP 688 is accepted - async def readuntil(self, separator: bytes | bytearray | memoryview = b"\n") -> bytes: ... + if sys.version_info >= (3, 13): + async def readuntil(self, separator: _ReaduntilBuffer | tuple[_ReaduntilBuffer, ...] = b"\n") -> bytes: ... + else: + async def readuntil(self, separator: _ReaduntilBuffer = b"\n") -> bytes: ... + async def read(self, n: int = -1) -> bytes: ... async def readexactly(self, n: int) -> bytes: ... def __aiter__(self) -> Self: ... diff --git a/mypy/typeshed/stdlib/asyncio/subprocess.pyi b/mypy/typeshed/stdlib/asyncio/subprocess.pyi index 10a414f24537..19452d4eb469 100644 --- a/mypy/typeshed/stdlib/asyncio/subprocess.pyi +++ b/mypy/typeshed/stdlib/asyncio/subprocess.pyi @@ -3,16 +3,10 @@ import sys from _typeshed import StrOrBytesPath from asyncio import events, protocols, streams, transports from collections.abc import Callable, Collection -from typing import IO, Any -from typing_extensions import Literal, TypeAlias +from typing import IO, Any, Literal __all__ = ("create_subprocess_exec", "create_subprocess_shell") -if sys.version_info >= (3, 8): - _ExecArg: TypeAlias = StrOrBytesPath -else: - _ExecArg: TypeAlias = str | bytes - PIPE: int STDOUT: int DEVNULL: int @@ -54,56 +48,56 @@ if sys.version_info >= (3, 11): bufsize: Literal[0] = 0, encoding: None = None, errors: None = None, - text: Literal[False, None] = ..., + text: Literal[False] | None = None, # These parameters are taken by subprocess.Popen, which this ultimately delegates to - executable: StrOrBytesPath | None = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: subprocess._ENV | None = ..., - startupinfo: Any | None = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + executable: StrOrBytesPath | None = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + cwd: StrOrBytesPath | None = None, + env: subprocess._ENV | None = None, + startupinfo: Any | None = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., - group: None | str | int = ..., - extra_groups: None | Collection[str | int] = ..., - user: None | str | int = ..., - umask: int = ..., - process_group: int | None = ..., - pipesize: int = ..., + group: None | str | int = None, + extra_groups: None | Collection[str | int] = None, + user: None | str | int = None, + umask: int = -1, + process_group: int | None = None, + pipesize: int = -1, ) -> Process: ... async def create_subprocess_exec( - program: _ExecArg, - *args: _ExecArg, + program: StrOrBytesPath, + *args: StrOrBytesPath, stdin: int | IO[Any] | None = None, stdout: int | IO[Any] | None = None, stderr: int | IO[Any] | None = None, limit: int = 65536, - # These parameters are forced to these values by BaseEventLoop.subprocess_shell + # These parameters are forced to these values by BaseEventLoop.subprocess_exec universal_newlines: Literal[False] = False, - shell: Literal[True] = True, + shell: Literal[False] = False, bufsize: Literal[0] = 0, encoding: None = None, errors: None = None, + text: Literal[False] | None = None, # These parameters are taken by subprocess.Popen, which this ultimately delegates to - text: bool | None = ..., - executable: StrOrBytesPath | None = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: subprocess._ENV | None = ..., - startupinfo: Any | None = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + executable: StrOrBytesPath | None = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + cwd: StrOrBytesPath | None = None, + env: subprocess._ENV | None = None, + startupinfo: Any | None = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., - group: None | str | int = ..., - extra_groups: None | Collection[str | int] = ..., - user: None | str | int = ..., - umask: int = ..., - process_group: int | None = ..., - pipesize: int = ..., + group: None | str | int = None, + extra_groups: None | Collection[str | int] = None, + user: None | str | int = None, + umask: int = -1, + process_group: int | None = None, + pipesize: int = -1, ) -> Process: ... elif sys.version_info >= (3, 10): @@ -120,54 +114,54 @@ elif sys.version_info >= (3, 10): bufsize: Literal[0] = 0, encoding: None = None, errors: None = None, - text: Literal[False, None] = ..., + text: Literal[False] | None = None, # These parameters are taken by subprocess.Popen, which this ultimately delegates to - executable: StrOrBytesPath | None = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: subprocess._ENV | None = ..., - startupinfo: Any | None = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + executable: StrOrBytesPath | None = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + cwd: StrOrBytesPath | None = None, + env: subprocess._ENV | None = None, + startupinfo: Any | None = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., - group: None | str | int = ..., - extra_groups: None | Collection[str | int] = ..., - user: None | str | int = ..., - umask: int = ..., - pipesize: int = ..., + group: None | str | int = None, + extra_groups: None | Collection[str | int] = None, + user: None | str | int = None, + umask: int = -1, + pipesize: int = -1, ) -> Process: ... async def create_subprocess_exec( - program: _ExecArg, - *args: _ExecArg, + program: StrOrBytesPath, + *args: StrOrBytesPath, stdin: int | IO[Any] | None = None, stdout: int | IO[Any] | None = None, stderr: int | IO[Any] | None = None, limit: int = 65536, - # These parameters are forced to these values by BaseEventLoop.subprocess_shell + # These parameters are forced to these values by BaseEventLoop.subprocess_exec universal_newlines: Literal[False] = False, - shell: Literal[True] = True, + shell: Literal[False] = False, bufsize: Literal[0] = 0, encoding: None = None, errors: None = None, + text: Literal[False] | None = None, # These parameters are taken by subprocess.Popen, which this ultimately delegates to - text: bool | None = ..., - executable: StrOrBytesPath | None = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: subprocess._ENV | None = ..., - startupinfo: Any | None = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + executable: StrOrBytesPath | None = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + cwd: StrOrBytesPath | None = None, + env: subprocess._ENV | None = None, + startupinfo: Any | None = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., - group: None | str | int = ..., - extra_groups: None | Collection[str | int] = ..., - user: None | str | int = ..., - umask: int = ..., - pipesize: int = ..., + group: None | str | int = None, + extra_groups: None | Collection[str | int] = None, + user: None | str | int = None, + umask: int = -1, + pipesize: int = -1, ) -> Process: ... else: # >= 3.9 @@ -185,51 +179,51 @@ else: # >= 3.9 bufsize: Literal[0] = 0, encoding: None = None, errors: None = None, - text: Literal[False, None] = ..., + text: Literal[False] | None = None, # These parameters are taken by subprocess.Popen, which this ultimately delegates to - executable: StrOrBytesPath | None = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: subprocess._ENV | None = ..., - startupinfo: Any | None = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + executable: StrOrBytesPath | None = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + cwd: StrOrBytesPath | None = None, + env: subprocess._ENV | None = None, + startupinfo: Any | None = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., - group: None | str | int = ..., - extra_groups: None | Collection[str | int] = ..., - user: None | str | int = ..., - umask: int = ..., + group: None | str | int = None, + extra_groups: None | Collection[str | int] = None, + user: None | str | int = None, + umask: int = -1, ) -> Process: ... async def create_subprocess_exec( - program: _ExecArg, - *args: _ExecArg, + program: StrOrBytesPath, + *args: StrOrBytesPath, stdin: int | IO[Any] | None = None, stdout: int | IO[Any] | None = None, stderr: int | IO[Any] | None = None, loop: events.AbstractEventLoop | None = None, limit: int = 65536, - # These parameters are forced to these values by BaseEventLoop.subprocess_shell + # These parameters are forced to these values by BaseEventLoop.subprocess_exec universal_newlines: Literal[False] = False, - shell: Literal[True] = True, + shell: Literal[False] = False, bufsize: Literal[0] = 0, encoding: None = None, errors: None = None, + text: Literal[False] | None = None, # These parameters are taken by subprocess.Popen, which this ultimately delegates to - text: bool | None = ..., - executable: StrOrBytesPath | None = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: subprocess._ENV | None = ..., - startupinfo: Any | None = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + executable: StrOrBytesPath | None = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + cwd: StrOrBytesPath | None = None, + env: subprocess._ENV | None = None, + startupinfo: Any | None = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., - group: None | str | int = ..., - extra_groups: None | Collection[str | int] = ..., - user: None | str | int = ..., - umask: int = ..., + group: None | str | int = None, + extra_groups: None | Collection[str | int] = None, + user: None | str | int = None, + umask: int = -1, ) -> Process: ... diff --git a/mypy/typeshed/stdlib/asyncio/taskgroups.pyi b/mypy/typeshed/stdlib/asyncio/taskgroups.pyi index 8daa96f1ede0..aec3f1127f15 100644 --- a/mypy/typeshed/stdlib/asyncio/taskgroups.pyi +++ b/mypy/typeshed/stdlib/asyncio/taskgroups.pyi @@ -1,20 +1,25 @@ -# This only exists in 3.11+. See VERSIONS. - -from collections.abc import Coroutine, Generator +import sys from contextvars import Context from types import TracebackType from typing import Any, TypeVar from typing_extensions import Self +from . import _CoroutineLike +from .events import AbstractEventLoop from .tasks import Task -__all__ = ["TaskGroup"] +if sys.version_info >= (3, 12): + __all__ = ("TaskGroup",) +else: + __all__ = ["TaskGroup"] _T = TypeVar("_T") class TaskGroup: + _loop: AbstractEventLoop | None + _tasks: set[Task[Any]] + async def __aenter__(self) -> Self: ... async def __aexit__(self, et: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None: ... - def create_task( - self, coro: Generator[Any, None, _T] | Coroutine[Any, Any, _T], *, name: str | None = None, context: Context | None = None - ) -> Task[_T]: ... + def create_task(self, coro: _CoroutineLike[_T], *, name: str | None = None, context: Context | None = None) -> Task[_T]: ... + def _on_task_done(self, task: Task[object]) -> None: ... diff --git a/mypy/typeshed/stdlib/asyncio/tasks.pyi b/mypy/typeshed/stdlib/asyncio/tasks.pyi index 0a44255a3ac8..67291071d512 100644 --- a/mypy/typeshed/stdlib/asyncio/tasks.pyi +++ b/mypy/typeshed/stdlib/asyncio/tasks.pyi @@ -2,9 +2,10 @@ import concurrent.futures import sys from collections.abc import Awaitable, Coroutine, Generator, Iterable, Iterator from types import FrameType -from typing import Any, Generic, TextIO, TypeVar, overload -from typing_extensions import Literal, TypeAlias +from typing import Any, Literal, Protocol, TextIO, TypeVar, overload +from typing_extensions import TypeAlias +from . import _CoroutineLike from .events import AbstractEventLoop from .futures import Future @@ -13,27 +14,52 @@ if sys.version_info >= (3, 9): if sys.version_info >= (3, 11): from contextvars import Context -__all__ = ( - "Task", - "create_task", - "FIRST_COMPLETED", - "FIRST_EXCEPTION", - "ALL_COMPLETED", - "wait", - "wait_for", - "as_completed", - "sleep", - "gather", - "shield", - "ensure_future", - "run_coroutine_threadsafe", - "current_task", - "all_tasks", - "_register_task", - "_unregister_task", - "_enter_task", - "_leave_task", -) +if sys.version_info >= (3, 12): + __all__ = ( + "Task", + "create_task", + "FIRST_COMPLETED", + "FIRST_EXCEPTION", + "ALL_COMPLETED", + "wait", + "wait_for", + "as_completed", + "sleep", + "gather", + "shield", + "ensure_future", + "run_coroutine_threadsafe", + "current_task", + "all_tasks", + "create_eager_task_factory", + "eager_task_factory", + "_register_task", + "_unregister_task", + "_enter_task", + "_leave_task", + ) +else: + __all__ = ( + "Task", + "create_task", + "FIRST_COMPLETED", + "FIRST_EXCEPTION", + "ALL_COMPLETED", + "wait", + "wait_for", + "as_completed", + "sleep", + "gather", + "shield", + "ensure_future", + "run_coroutine_threadsafe", + "current_task", + "all_tasks", + "_register_task", + "_unregister_task", + "_enter_task", + "_leave_task", + ) _T = TypeVar("_T") _T_co = TypeVar("_T_co", covariant=True) @@ -42,6 +68,7 @@ _T2 = TypeVar("_T2") _T3 = TypeVar("_T3") _T4 = TypeVar("_T4") _T5 = TypeVar("_T5") +_T6 = TypeVar("_T6") _FT = TypeVar("_FT", bound=Future[Any]) _FutureLike: TypeAlias = Future[_T] | Generator[Any, None, _T] | Awaitable[_T] _TaskYieldType: TypeAlias = Future[object] | None @@ -59,7 +86,7 @@ else: ) -> Iterator[Future[_T]]: ... @overload -def ensure_future(coro_or_future: _FT, *, loop: AbstractEventLoop | None = None) -> _FT: ... # type: ignore[misc] +def ensure_future(coro_or_future: _FT, *, loop: AbstractEventLoop | None = None) -> _FT: ... # type: ignore[overload-overlap] @overload def ensure_future(coro_or_future: Awaitable[_T], *, loop: AbstractEventLoop | None = None) -> Task[_T]: ... @@ -68,171 +95,244 @@ def ensure_future(coro_or_future: Awaitable[_T], *, loop: AbstractEventLoop | No # zip() because typing does not support variadic type variables. See # typing PR #1550 for discussion. # -# The many type: ignores here are because the overloads overlap, -# but having overlapping overloads is the only way to get acceptable type inference in all edge cases. +# N.B. Having overlapping overloads is the only way to get acceptable type inference in all edge cases. if sys.version_info >= (3, 10): @overload - def gather(__coro_or_future1: _FutureLike[_T1], *, return_exceptions: Literal[False] = False) -> Future[tuple[_T1]]: ... # type: ignore[misc] + def gather(coro_or_future1: _FutureLike[_T1], /, *, return_exceptions: Literal[False] = False) -> Future[tuple[_T1]]: ... # type: ignore[overload-overlap] @overload - def gather( # type: ignore[misc] - __coro_or_future1: _FutureLike[_T1], __coro_or_future2: _FutureLike[_T2], *, return_exceptions: Literal[False] = False + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], coro_or_future2: _FutureLike[_T2], /, *, return_exceptions: Literal[False] = False ) -> Future[tuple[_T1, _T2]]: ... @overload - def gather( # type: ignore[misc] - __coro_or_future1: _FutureLike[_T1], - __coro_or_future2: _FutureLike[_T2], - __coro_or_future3: _FutureLike[_T3], + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + /, *, return_exceptions: Literal[False] = False, ) -> Future[tuple[_T1, _T2, _T3]]: ... @overload - def gather( # type: ignore[misc] - __coro_or_future1: _FutureLike[_T1], - __coro_or_future2: _FutureLike[_T2], - __coro_or_future3: _FutureLike[_T3], - __coro_or_future4: _FutureLike[_T4], + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + coro_or_future4: _FutureLike[_T4], + /, *, return_exceptions: Literal[False] = False, ) -> Future[tuple[_T1, _T2, _T3, _T4]]: ... @overload - def gather( # type: ignore[misc] - __coro_or_future1: _FutureLike[_T1], - __coro_or_future2: _FutureLike[_T2], - __coro_or_future3: _FutureLike[_T3], - __coro_or_future4: _FutureLike[_T4], - __coro_or_future5: _FutureLike[_T5], + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + coro_or_future4: _FutureLike[_T4], + coro_or_future5: _FutureLike[_T5], + /, *, return_exceptions: Literal[False] = False, ) -> Future[tuple[_T1, _T2, _T3, _T4, _T5]]: ... @overload - def gather(__coro_or_future1: _FutureLike[_T1], *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException]]: ... # type: ignore[misc] + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + coro_or_future4: _FutureLike[_T4], + coro_or_future5: _FutureLike[_T5], + coro_or_future6: _FutureLike[_T6], + /, + *, + return_exceptions: Literal[False] = False, + ) -> Future[tuple[_T1, _T2, _T3, _T4, _T5, _T6]]: ... + @overload + def gather(*coros_or_futures: _FutureLike[_T], return_exceptions: Literal[False] = False) -> Future[list[_T]]: ... # type: ignore[overload-overlap] @overload - def gather( # type: ignore[misc] - __coro_or_future1: _FutureLike[_T1], __coro_or_future2: _FutureLike[_T2], *, return_exceptions: bool + def gather(coro_or_future1: _FutureLike[_T1], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException]]: ... # type: ignore[overload-overlap] + @overload + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], coro_or_future2: _FutureLike[_T2], /, *, return_exceptions: bool ) -> Future[tuple[_T1 | BaseException, _T2 | BaseException]]: ... @overload - def gather( # type: ignore[misc] - __coro_or_future1: _FutureLike[_T1], - __coro_or_future2: _FutureLike[_T2], - __coro_or_future3: _FutureLike[_T3], + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + /, *, return_exceptions: bool, ) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException]]: ... @overload - def gather( # type: ignore[misc] - __coro_or_future1: _FutureLike[_T1], - __coro_or_future2: _FutureLike[_T2], - __coro_or_future3: _FutureLike[_T3], - __coro_or_future4: _FutureLike[_T4], + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + coro_or_future4: _FutureLike[_T4], + /, *, return_exceptions: bool, ) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException]]: ... @overload - def gather( # type: ignore[misc] - __coro_or_future1: _FutureLike[_T1], - __coro_or_future2: _FutureLike[_T2], - __coro_or_future3: _FutureLike[_T3], - __coro_or_future4: _FutureLike[_T4], - __coro_or_future5: _FutureLike[_T5], + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + coro_or_future4: _FutureLike[_T4], + coro_or_future5: _FutureLike[_T5], + /, *, return_exceptions: bool, ) -> Future[ tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException, _T5 | BaseException] ]: ... @overload - def gather(*coros_or_futures: _FutureLike[Any], return_exceptions: bool = False) -> Future[list[Any]]: ... # type: ignore[misc] + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + coro_or_future4: _FutureLike[_T4], + coro_or_future5: _FutureLike[_T5], + coro_or_future6: _FutureLike[_T6], + /, + *, + return_exceptions: bool, + ) -> Future[ + tuple[ + _T1 | BaseException, + _T2 | BaseException, + _T3 | BaseException, + _T4 | BaseException, + _T5 | BaseException, + _T6 | BaseException, + ] + ]: ... + @overload + def gather(*coros_or_futures: _FutureLike[_T], return_exceptions: bool) -> Future[list[_T | BaseException]]: ... else: @overload - def gather( # type: ignore[misc] - __coro_or_future1: _FutureLike[_T1], *, loop: AbstractEventLoop | None = None, return_exceptions: Literal[False] = False + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], /, *, loop: AbstractEventLoop | None = None, return_exceptions: Literal[False] = False ) -> Future[tuple[_T1]]: ... @overload - def gather( # type: ignore[misc] - __coro_or_future1: _FutureLike[_T1], - __coro_or_future2: _FutureLike[_T2], + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + /, *, loop: AbstractEventLoop | None = None, return_exceptions: Literal[False] = False, ) -> Future[tuple[_T1, _T2]]: ... @overload - def gather( # type: ignore[misc] - __coro_or_future1: _FutureLike[_T1], - __coro_or_future2: _FutureLike[_T2], - __coro_or_future3: _FutureLike[_T3], + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + /, *, loop: AbstractEventLoop | None = None, return_exceptions: Literal[False] = False, ) -> Future[tuple[_T1, _T2, _T3]]: ... @overload - def gather( # type: ignore[misc] - __coro_or_future1: _FutureLike[_T1], - __coro_or_future2: _FutureLike[_T2], - __coro_or_future3: _FutureLike[_T3], - __coro_or_future4: _FutureLike[_T4], + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + coro_or_future4: _FutureLike[_T4], + /, *, loop: AbstractEventLoop | None = None, return_exceptions: Literal[False] = False, ) -> Future[tuple[_T1, _T2, _T3, _T4]]: ... @overload - def gather( # type: ignore[misc] - __coro_or_future1: _FutureLike[_T1], - __coro_or_future2: _FutureLike[_T2], - __coro_or_future3: _FutureLike[_T3], - __coro_or_future4: _FutureLike[_T4], - __coro_or_future5: _FutureLike[_T5], + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + coro_or_future4: _FutureLike[_T4], + coro_or_future5: _FutureLike[_T5], + /, *, loop: AbstractEventLoop | None = None, return_exceptions: Literal[False] = False, ) -> Future[tuple[_T1, _T2, _T3, _T4, _T5]]: ... @overload - def gather( # type: ignore[misc] - __coro_or_future1: _FutureLike[_T1], *, loop: AbstractEventLoop | None = None, return_exceptions: bool + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + coro_or_future4: _FutureLike[_T4], + coro_or_future5: _FutureLike[_T5], + coro_or_future6: _FutureLike[_T6], + /, + *, + loop: AbstractEventLoop | None = None, + return_exceptions: Literal[False] = False, + ) -> Future[tuple[_T1, _T2, _T3, _T4, _T5, _T6]]: ... + @overload + def gather( # type: ignore[overload-overlap] + *coros_or_futures: _FutureLike[_T], loop: AbstractEventLoop | None = None, return_exceptions: Literal[False] = False + ) -> Future[list[_T]]: ... + @overload + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], /, *, loop: AbstractEventLoop | None = None, return_exceptions: bool ) -> Future[tuple[_T1 | BaseException]]: ... @overload - def gather( # type: ignore[misc] - __coro_or_future1: _FutureLike[_T1], - __coro_or_future2: _FutureLike[_T2], + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + /, *, loop: AbstractEventLoop | None = None, return_exceptions: bool, ) -> Future[tuple[_T1 | BaseException, _T2 | BaseException]]: ... @overload - def gather( # type: ignore[misc] - __coro_or_future1: _FutureLike[_T1], - __coro_or_future2: _FutureLike[_T2], - __coro_or_future3: _FutureLike[_T3], + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + /, *, loop: AbstractEventLoop | None = None, return_exceptions: bool, ) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException]]: ... @overload - def gather( # type: ignore[misc] - __coro_or_future1: _FutureLike[_T1], - __coro_or_future2: _FutureLike[_T2], - __coro_or_future3: _FutureLike[_T3], - __coro_or_future4: _FutureLike[_T4], + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + coro_or_future4: _FutureLike[_T4], + /, *, loop: AbstractEventLoop | None = None, return_exceptions: bool, ) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException]]: ... @overload - def gather( # type: ignore[misc] - __coro_or_future1: _FutureLike[_T1], - __coro_or_future2: _FutureLike[_T2], - __coro_or_future3: _FutureLike[_T3], - __coro_or_future4: _FutureLike[_T4], - __coro_or_future5: _FutureLike[_T5], + def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + coro_or_future4: _FutureLike[_T4], + coro_or_future5: _FutureLike[_T5], + coro_or_future6: _FutureLike[_T6], + /, *, loop: AbstractEventLoop | None = None, return_exceptions: bool, ) -> Future[ - tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException, _T5 | BaseException] + tuple[ + _T1 | BaseException, + _T2 | BaseException, + _T3 | BaseException, + _T4 | BaseException, + _T5 | BaseException, + _T6 | BaseException, + ] ]: ... @overload - def gather( # type: ignore[misc] - *coros_or_futures: _FutureLike[Any], loop: AbstractEventLoop | None = None, return_exceptions: bool = False - ) -> Future[list[Any]]: ... + def gather( + *coros_or_futures: _FutureLike[_T], loop: AbstractEventLoop | None = None, return_exceptions: bool + ) -> Future[list[_T | BaseException]]: ... def run_coroutine_threadsafe(coro: _FutureLike[_T], loop: AbstractEventLoop) -> concurrent.futures.Future[_T]: ... @@ -242,12 +342,6 @@ if sys.version_info >= (3, 10): async def sleep(delay: float) -> None: ... @overload async def sleep(delay: float, result: _T) -> _T: ... - @overload - async def wait(fs: Iterable[_FT], *, timeout: float | None = None, return_when: str = "ALL_COMPLETED") -> tuple[set[_FT], set[_FT]]: ... # type: ignore[misc] - @overload - async def wait( - fs: Iterable[Awaitable[_T]], *, timeout: float | None = None, return_when: str = "ALL_COMPLETED" - ) -> tuple[set[Task[_T]], set[Task[_T]]]: ... async def wait_for(fut: _FutureLike[_T], timeout: float | None) -> _T: ... else: @@ -256,8 +350,31 @@ else: async def sleep(delay: float, *, loop: AbstractEventLoop | None = None) -> None: ... @overload async def sleep(delay: float, result: _T, *, loop: AbstractEventLoop | None = None) -> _T: ... + async def wait_for(fut: _FutureLike[_T], timeout: float | None, *, loop: AbstractEventLoop | None = None) -> _T: ... + +if sys.version_info >= (3, 11): + @overload + async def wait( + fs: Iterable[_FT], *, timeout: float | None = None, return_when: str = "ALL_COMPLETED" + ) -> tuple[set[_FT], set[_FT]]: ... + @overload + async def wait( + fs: Iterable[Task[_T]], *, timeout: float | None = None, return_when: str = "ALL_COMPLETED" + ) -> tuple[set[Task[_T]], set[Task[_T]]]: ... + +elif sys.version_info >= (3, 10): + @overload + async def wait( # type: ignore[overload-overlap] + fs: Iterable[_FT], *, timeout: float | None = None, return_when: str = "ALL_COMPLETED" + ) -> tuple[set[_FT], set[_FT]]: ... @overload - async def wait( # type: ignore[misc] + async def wait( + fs: Iterable[Awaitable[_T]], *, timeout: float | None = None, return_when: str = "ALL_COMPLETED" + ) -> tuple[set[Task[_T]], set[Task[_T]]]: ... + +else: + @overload + async def wait( # type: ignore[overload-overlap] fs: Iterable[_FT], *, loop: AbstractEventLoop | None = None, @@ -272,29 +389,48 @@ else: timeout: float | None = None, return_when: str = "ALL_COMPLETED", ) -> tuple[set[Task[_T]], set[Task[_T]]]: ... - async def wait_for(fut: _FutureLike[_T], timeout: float | None, *, loop: AbstractEventLoop | None = None) -> _T: ... + +if sys.version_info >= (3, 12): + _TaskCompatibleCoro: TypeAlias = Coroutine[Any, Any, _T_co] +elif sys.version_info >= (3, 9): + _TaskCompatibleCoro: TypeAlias = Generator[_TaskYieldType, None, _T_co] | Coroutine[Any, Any, _T_co] +else: + _TaskCompatibleCoro: TypeAlias = Generator[_TaskYieldType, None, _T_co] | Awaitable[_T_co] # mypy and pyright complain that a subclass of an invariant class shouldn't be covariant. # While this is true in general, here it's sort-of okay to have a covariant subclass, # since the only reason why `asyncio.Future` is invariant is the `set_result()` method, # and `asyncio.Task.set_result()` always raises. -class Task(Future[_T_co], Generic[_T_co]): # type: ignore[type-var] # pyright: ignore[reportGeneralTypeIssues] - if sys.version_info >= (3, 8): +class Task(Future[_T_co]): # type: ignore[type-var] # pyright: ignore[reportInvalidTypeArguments] + if sys.version_info >= (3, 12): + def __init__( + self, + coro: _TaskCompatibleCoro[_T_co], + *, + loop: AbstractEventLoop = ..., + name: str | None = ..., + context: Context | None = None, + eager_start: bool = False, + ) -> None: ... + elif sys.version_info >= (3, 11): def __init__( self, - coro: Generator[_TaskYieldType, None, _T_co] | Awaitable[_T_co], + coro: _TaskCompatibleCoro[_T_co], *, loop: AbstractEventLoop = ..., name: str | None = ..., + context: Context | None = None, ) -> None: ... else: def __init__( - self, coro: Generator[_TaskYieldType, None, _T_co] | Awaitable[_T_co], *, loop: AbstractEventLoop = ... + self, coro: _TaskCompatibleCoro[_T_co], *, loop: AbstractEventLoop = ..., name: str | None = ... ) -> None: ... - if sys.version_info >= (3, 8): - def get_coro(self) -> Generator[_TaskYieldType, None, _T_co] | Awaitable[_T_co]: ... - def get_name(self) -> str: ... - def set_name(self, __value: object) -> None: ... + + def get_coro(self) -> _TaskCompatibleCoro[_T_co]: ... + def get_name(self) -> str: ... + def set_name(self, value: object, /) -> None: ... + if sys.version_info >= (3, 12): + def get_context(self) -> Context: ... def get_stack(self, *, limit: int | None = None) -> list[FrameType]: ... def print_stack(self, *, limit: int | None = None, file: TextIO | None = None) -> None: ... @@ -312,18 +448,50 @@ class Task(Future[_T_co], Generic[_T_co]): # type: ignore[type-var] # pyright: def all_tasks(loop: AbstractEventLoop | None = None) -> set[Task[Any]]: ... if sys.version_info >= (3, 11): - def create_task( - coro: Generator[Any, None, _T] | Coroutine[Any, Any, _T], *, name: str | None = None, context: Context | None = None - ) -> Task[_T]: ... - -elif sys.version_info >= (3, 8): - def create_task(coro: Generator[Any, None, _T] | Coroutine[Any, Any, _T], *, name: str | None = None) -> Task[_T]: ... + def create_task(coro: _CoroutineLike[_T], *, name: str | None = None, context: Context | None = None) -> Task[_T]: ... else: - def create_task(coro: Generator[Any, None, _T] | Coroutine[Any, Any, _T]) -> Task[_T]: ... + def create_task(coro: _CoroutineLike[_T], *, name: str | None = None) -> Task[_T]: ... def current_task(loop: AbstractEventLoop | None = None) -> Task[Any] | None: ... def _enter_task(loop: AbstractEventLoop, task: Task[Any]) -> None: ... def _leave_task(loop: AbstractEventLoop, task: Task[Any]) -> None: ... + +if sys.version_info >= (3, 12): + _TaskT_co = TypeVar("_TaskT_co", bound=Task[Any], covariant=True) + + class _CustomTaskConstructor(Protocol[_TaskT_co]): + def __call__( + self, + coro: _TaskCompatibleCoro[Any], + /, + *, + loop: AbstractEventLoop, + name: str | None, + context: Context | None, + eager_start: bool, + ) -> _TaskT_co: ... + + class _EagerTaskFactoryType(Protocol[_TaskT_co]): + def __call__( + self, + loop: AbstractEventLoop, + coro: _TaskCompatibleCoro[Any], + *, + name: str | None = None, + context: Context | None = None, + ) -> _TaskT_co: ... + + def create_eager_task_factory( + custom_task_constructor: _CustomTaskConstructor[_TaskT_co], + ) -> _EagerTaskFactoryType[_TaskT_co]: ... + def eager_task_factory( + loop: AbstractEventLoop | None, + coro: _TaskCompatibleCoro[_T_co], + *, + name: str | None = None, + context: Context | None = None, + ) -> Task[_T_co]: ... + def _register_task(task: Task[Any]) -> None: ... def _unregister_task(task: Task[Any]) -> None: ... diff --git a/mypy/typeshed/stdlib/asyncio/threads.pyi b/mypy/typeshed/stdlib/asyncio/threads.pyi index 88c4fddcaa3f..799efd25fea4 100644 --- a/mypy/typeshed/stdlib/asyncio/threads.pyi +++ b/mypy/typeshed/stdlib/asyncio/threads.pyi @@ -6,4 +6,4 @@ __all__ = ("to_thread",) _P = ParamSpec("_P") _R = TypeVar("_R") -async def to_thread(__func: Callable[_P, _R], *args: _P.args, **kwargs: _P.kwargs) -> _R: ... +async def to_thread(func: Callable[_P, _R], /, *args: _P.args, **kwargs: _P.kwargs) -> _R: ... diff --git a/mypy/typeshed/stdlib/asyncio/timeouts.pyi b/mypy/typeshed/stdlib/asyncio/timeouts.pyi index 2d31b777b77d..2f0e40e25680 100644 --- a/mypy/typeshed/stdlib/asyncio/timeouts.pyi +++ b/mypy/typeshed/stdlib/asyncio/timeouts.pyi @@ -1,5 +1,6 @@ from types import TracebackType -from typing_extensions import Self, final +from typing import final +from typing_extensions import Self __all__ = ("Timeout", "timeout", "timeout_at") diff --git a/mypy/typeshed/stdlib/asyncio/trsock.pyi b/mypy/typeshed/stdlib/asyncio/trsock.pyi index 742216a84ccd..e74cf6fd4e05 100644 --- a/mypy/typeshed/stdlib/asyncio/trsock.pyi +++ b/mypy/typeshed/stdlib/asyncio/trsock.pyi @@ -51,7 +51,7 @@ class TransportSocket: else: def ioctl(self, control: int, option: int | tuple[int, int, int] | bool) -> NoReturn: ... - def listen(self, __backlog: int = ...) -> None: ... + def listen(self, backlog: int = ..., /) -> None: ... def makefile(self) -> BinaryIO: ... def sendfile(self, file: BinaryIO, offset: int = ..., count: int | None = ...) -> int: ... def close(self) -> None: ... @@ -66,11 +66,7 @@ class TransportSocket: ) -> NoReturn: ... def sendmsg( - self, - __buffers: Iterable[ReadableBuffer], - __ancdata: Iterable[_CMSG] = ..., - __flags: int = ..., - __address: _Address = ..., + self, buffers: Iterable[ReadableBuffer], ancdata: Iterable[_CMSG] = ..., flags: int = ..., address: _Address = ..., / ) -> int: ... @overload def sendto(self, data: ReadableBuffer, address: _Address) -> int: ... @@ -87,9 +83,9 @@ class TransportSocket: def recv_into(self, buffer: _WriteBuffer, nbytes: int = ..., flags: int = ...) -> int: ... def recvfrom_into(self, buffer: _WriteBuffer, nbytes: int = ..., flags: int = ...) -> tuple[int, _RetAddress]: ... def recvmsg_into( - self, __buffers: Iterable[_WriteBuffer], __ancbufsize: int = ..., __flags: int = ... + self, buffers: Iterable[_WriteBuffer], ancbufsize: int = ..., flags: int = ..., / ) -> tuple[int, list[_CMSG], int, Any]: ... - def recvmsg(self, __bufsize: int, __ancbufsize: int = ..., __flags: int = ...) -> tuple[bytes, list[_CMSG], int, Any]: ... + def recvmsg(self, bufsize: int, ancbufsize: int = ..., flags: int = ..., /) -> tuple[bytes, list[_CMSG], int, Any]: ... def recvfrom(self, bufsize: int, flags: int = ...) -> tuple[bytes, _RetAddress]: ... def recv(self, bufsize: int, flags: int = ...) -> bytes: ... def __enter__(self) -> socket.socket: ... diff --git a/mypy/typeshed/stdlib/asyncio/unix_events.pyi b/mypy/typeshed/stdlib/asyncio/unix_events.pyi index e28d64b5287b..e9274b853290 100644 --- a/mypy/typeshed/stdlib/asyncio/unix_events.pyi +++ b/mypy/typeshed/stdlib/asyncio/unix_events.pyi @@ -2,29 +2,57 @@ import sys import types from abc import ABCMeta, abstractmethod from collections.abc import Callable -from typing import Any -from typing_extensions import Literal, Self +from typing import Literal +from typing_extensions import Self, TypeVarTuple, Unpack, deprecated from .events import AbstractEventLoop, BaseDefaultEventLoopPolicy from .selector_events import BaseSelectorEventLoop +_Ts = TypeVarTuple("_Ts") + # This is also technically not available on Win, # but other parts of typeshed need this definition. # So, it is special cased. -class AbstractChildWatcher: - @abstractmethod - def add_child_handler(self, pid: int, callback: Callable[..., object], *args: Any) -> None: ... - @abstractmethod - def remove_child_handler(self, pid: int) -> bool: ... - @abstractmethod - def attach_loop(self, loop: AbstractEventLoop | None) -> None: ... - @abstractmethod - def close(self) -> None: ... - @abstractmethod - def __enter__(self) -> Self: ... - @abstractmethod - def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: types.TracebackType | None) -> None: ... - if sys.version_info >= (3, 8): +if sys.version_info >= (3, 12): + @deprecated("Deprecated as of Python 3.12; will be removed in Python 3.14") + class AbstractChildWatcher: + @abstractmethod + def add_child_handler( + self, pid: int, callback: Callable[[int, int, Unpack[_Ts]], object], *args: Unpack[_Ts] + ) -> None: ... + @abstractmethod + def remove_child_handler(self, pid: int) -> bool: ... + @abstractmethod + def attach_loop(self, loop: AbstractEventLoop | None) -> None: ... + @abstractmethod + def close(self) -> None: ... + @abstractmethod + def __enter__(self) -> Self: ... + @abstractmethod + def __exit__( + self, typ: type[BaseException] | None, exc: BaseException | None, tb: types.TracebackType | None + ) -> None: ... + @abstractmethod + def is_active(self) -> bool: ... + +else: + class AbstractChildWatcher: + @abstractmethod + def add_child_handler( + self, pid: int, callback: Callable[[int, int, Unpack[_Ts]], object], *args: Unpack[_Ts] + ) -> None: ... + @abstractmethod + def remove_child_handler(self, pid: int) -> bool: ... + @abstractmethod + def attach_loop(self, loop: AbstractEventLoop | None) -> None: ... + @abstractmethod + def close(self) -> None: ... + @abstractmethod + def __enter__(self) -> Self: ... + @abstractmethod + def __exit__( + self, typ: type[BaseException] | None, exc: BaseException | None, tb: types.TracebackType | None + ) -> None: ... @abstractmethod def is_active(self) -> bool: ... @@ -40,7 +68,7 @@ if sys.platform != "win32": "ThreadedChildWatcher", "DefaultEventLoopPolicy", ) - elif sys.version_info >= (3, 8): + else: __all__ = ( "SelectorEventLoop", "AbstractChildWatcher", @@ -50,47 +78,68 @@ if sys.platform != "win32": "ThreadedChildWatcher", "DefaultEventLoopPolicy", ) - else: - __all__ = ("SelectorEventLoop", "AbstractChildWatcher", "SafeChildWatcher", "FastChildWatcher", "DefaultEventLoopPolicy") # Doesn't actually have ABCMeta metaclass at runtime, but mypy complains if we don't have it in the stub. # See discussion in #7412 class BaseChildWatcher(AbstractChildWatcher, metaclass=ABCMeta): def close(self) -> None: ... - if sys.version_info >= (3, 8): - def is_active(self) -> bool: ... - + def is_active(self) -> bool: ... def attach_loop(self, loop: AbstractEventLoop | None) -> None: ... - class SafeChildWatcher(BaseChildWatcher): - def __enter__(self) -> Self: ... - def __exit__(self, a: type[BaseException] | None, b: BaseException | None, c: types.TracebackType | None) -> None: ... - def add_child_handler(self, pid: int, callback: Callable[..., object], *args: Any) -> None: ... - def remove_child_handler(self, pid: int) -> bool: ... + if sys.version_info >= (3, 12): + @deprecated("Deprecated as of Python 3.12; will be removed in Python 3.14") + class SafeChildWatcher(BaseChildWatcher): + def __enter__(self) -> Self: ... + def __exit__(self, a: type[BaseException] | None, b: BaseException | None, c: types.TracebackType | None) -> None: ... + def add_child_handler( + self, pid: int, callback: Callable[[int, int, Unpack[_Ts]], object], *args: Unpack[_Ts] + ) -> None: ... + def remove_child_handler(self, pid: int) -> bool: ... - class FastChildWatcher(BaseChildWatcher): - def __enter__(self) -> Self: ... - def __exit__(self, a: type[BaseException] | None, b: BaseException | None, c: types.TracebackType | None) -> None: ... - def add_child_handler(self, pid: int, callback: Callable[..., object], *args: Any) -> None: ... - def remove_child_handler(self, pid: int) -> bool: ... + @deprecated("Deprecated as of Python 3.12; will be removed in Python 3.14") + class FastChildWatcher(BaseChildWatcher): + def __enter__(self) -> Self: ... + def __exit__(self, a: type[BaseException] | None, b: BaseException | None, c: types.TracebackType | None) -> None: ... + def add_child_handler( + self, pid: int, callback: Callable[[int, int, Unpack[_Ts]], object], *args: Unpack[_Ts] + ) -> None: ... + def remove_child_handler(self, pid: int) -> bool: ... + + else: + class SafeChildWatcher(BaseChildWatcher): + def __enter__(self) -> Self: ... + def __exit__(self, a: type[BaseException] | None, b: BaseException | None, c: types.TracebackType | None) -> None: ... + def add_child_handler( + self, pid: int, callback: Callable[[int, int, Unpack[_Ts]], object], *args: Unpack[_Ts] + ) -> None: ... + def remove_child_handler(self, pid: int) -> bool: ... + + class FastChildWatcher(BaseChildWatcher): + def __enter__(self) -> Self: ... + def __exit__(self, a: type[BaseException] | None, b: BaseException | None, c: types.TracebackType | None) -> None: ... + def add_child_handler( + self, pid: int, callback: Callable[[int, int, Unpack[_Ts]], object], *args: Unpack[_Ts] + ) -> None: ... + def remove_child_handler(self, pid: int) -> bool: ... class _UnixSelectorEventLoop(BaseSelectorEventLoop): ... class _UnixDefaultEventLoopPolicy(BaseDefaultEventLoopPolicy): - def get_child_watcher(self) -> AbstractChildWatcher: ... - def set_child_watcher(self, watcher: AbstractChildWatcher | None) -> None: ... + if sys.version_info >= (3, 12): + @deprecated("Deprecated as of Python 3.12; will be removed in Python 3.14") + def get_child_watcher(self) -> AbstractChildWatcher: ... + @deprecated("Deprecated as of Python 3.12; will be removed in Python 3.14") + def set_child_watcher(self, watcher: AbstractChildWatcher | None) -> None: ... + else: + def get_child_watcher(self) -> AbstractChildWatcher: ... + def set_child_watcher(self, watcher: AbstractChildWatcher | None) -> None: ... + SelectorEventLoop = _UnixSelectorEventLoop DefaultEventLoopPolicy = _UnixDefaultEventLoopPolicy - if sys.version_info >= (3, 8): - from typing import Protocol - - class _Warn(Protocol): - def __call__( - self, message: str, category: type[Warning] | None = ..., stacklevel: int = ..., source: Any | None = ... - ) -> object: ... - + if sys.version_info >= (3, 12): + @deprecated("Deprecated as of Python 3.12; will be removed in Python 3.14") class MultiLoopChildWatcher(AbstractChildWatcher): def is_active(self) -> bool: ... def close(self) -> None: ... @@ -98,22 +147,40 @@ if sys.platform != "win32": def __exit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: types.TracebackType | None ) -> None: ... - def add_child_handler(self, pid: int, callback: Callable[..., object], *args: Any) -> None: ... + def add_child_handler( + self, pid: int, callback: Callable[[int, int, Unpack[_Ts]], object], *args: Unpack[_Ts] + ) -> None: ... def remove_child_handler(self, pid: int) -> bool: ... def attach_loop(self, loop: AbstractEventLoop | None) -> None: ... - class ThreadedChildWatcher(AbstractChildWatcher): - def is_active(self) -> Literal[True]: ... + else: + class MultiLoopChildWatcher(AbstractChildWatcher): + def is_active(self) -> bool: ... def close(self) -> None: ... def __enter__(self) -> Self: ... def __exit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: types.TracebackType | None ) -> None: ... - def __del__(self, _warn: _Warn = ...) -> None: ... - def add_child_handler(self, pid: int, callback: Callable[..., object], *args: Any) -> None: ... + def add_child_handler( + self, pid: int, callback: Callable[[int, int, Unpack[_Ts]], object], *args: Unpack[_Ts] + ) -> None: ... def remove_child_handler(self, pid: int) -> bool: ... def attach_loop(self, loop: AbstractEventLoop | None) -> None: ... + class ThreadedChildWatcher(AbstractChildWatcher): + def is_active(self) -> Literal[True]: ... + def close(self) -> None: ... + def __enter__(self) -> Self: ... + def __exit__( + self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: types.TracebackType | None + ) -> None: ... + def __del__(self) -> None: ... + def add_child_handler( + self, pid: int, callback: Callable[[int, int, Unpack[_Ts]], object], *args: Unpack[_Ts] + ) -> None: ... + def remove_child_handler(self, pid: int) -> bool: ... + def attach_loop(self, loop: AbstractEventLoop | None) -> None: ... + if sys.version_info >= (3, 9): class PidfdChildWatcher(AbstractChildWatcher): def __enter__(self) -> Self: ... @@ -123,5 +190,7 @@ if sys.platform != "win32": def is_active(self) -> bool: ... def close(self) -> None: ... def attach_loop(self, loop: AbstractEventLoop | None) -> None: ... - def add_child_handler(self, pid: int, callback: Callable[..., object], *args: Any) -> None: ... + def add_child_handler( + self, pid: int, callback: Callable[[int, int, Unpack[_Ts]], object], *args: Unpack[_Ts] + ) -> None: ... def remove_child_handler(self, pid: int) -> bool: ... diff --git a/mypy/typeshed/stdlib/asyncio/windows_events.pyi b/mypy/typeshed/stdlib/asyncio/windows_events.pyi index 2942a25c0ac4..9c150ee16beb 100644 --- a/mypy/typeshed/stdlib/asyncio/windows_events.pyi +++ b/mypy/typeshed/stdlib/asyncio/windows_events.pyi @@ -1,9 +1,8 @@ import socket import sys -from _typeshed import Incomplete, WriteableBuffer +from _typeshed import Incomplete, ReadableBuffer, WriteableBuffer from collections.abc import Callable -from typing import IO, Any, ClassVar, NoReturn -from typing_extensions import Literal +from typing import IO, Any, ClassVar, Literal, NoReturn from . import events, futures, proactor_events, selector_events, streams, windows_utils @@ -48,6 +47,12 @@ if sys.platform == "win32": def select(self, timeout: int | None = None) -> list[futures.Future[Any]]: ... def recv(self, conn: socket.socket, nbytes: int, flags: int = 0) -> futures.Future[bytes]: ... def recv_into(self, conn: socket.socket, buf: WriteableBuffer, flags: int = 0) -> futures.Future[Any]: ... + def recvfrom( + self, conn: socket.socket, nbytes: int, flags: int = 0 + ) -> futures.Future[tuple[bytes, socket._RetAddress]]: ... + def sendto( + self, conn: socket.socket, buf: ReadableBuffer, flags: int = 0, addr: socket._Address | None = None + ) -> futures.Future[int]: ... def send(self, conn: socket.socket, buf: WriteableBuffer, flags: int = 0) -> futures.Future[Any]: ... def accept(self, listener: socket.socket) -> futures.Future[Any]: ... def connect( @@ -60,6 +65,11 @@ if sys.platform == "win32": async def connect_pipe(self, address: str) -> windows_utils.PipeHandle: ... def wait_for_handle(self, handle: windows_utils.PipeHandle, timeout: int | None = None) -> bool: ... def close(self) -> None: ... + if sys.version_info >= (3, 11): + def recvfrom_into( + self, conn: socket.socket, buf: WriteableBuffer, flags: int = 0 + ) -> futures.Future[tuple[int, socket._RetAddress]]: ... + SelectorEventLoop = _WindowsSelectorEventLoop class WindowsSelectorEventLoopPolicy(events.BaseDefaultEventLoopPolicy): @@ -71,4 +81,5 @@ if sys.platform == "win32": _loop_factory: ClassVar[type[ProactorEventLoop]] def get_child_watcher(self) -> NoReturn: ... def set_child_watcher(self, watcher: Any) -> NoReturn: ... + DefaultEventLoopPolicy = WindowsSelectorEventLoopPolicy diff --git a/mypy/typeshed/stdlib/asyncio/windows_utils.pyi b/mypy/typeshed/stdlib/asyncio/windows_utils.pyi index f3a82e2b8462..6b3589adc3cb 100644 --- a/mypy/typeshed/stdlib/asyncio/windows_utils.pyi +++ b/mypy/typeshed/stdlib/asyncio/windows_utils.pyi @@ -2,28 +2,20 @@ import subprocess import sys from collections.abc import Callable from types import TracebackType -from typing import Any, AnyStr, Protocol -from typing_extensions import Literal, Self +from typing import Any, AnyStr, Literal +from typing_extensions import Self if sys.platform == "win32": __all__ = ("pipe", "Popen", "PIPE", "PipeHandle") - class _WarnFunction(Protocol): - def __call__( - self, message: str, category: type[Warning] = ..., stacklevel: int = ..., source: PipeHandle = ... - ) -> object: ... BUFSIZE: Literal[8192] PIPE = subprocess.PIPE STDOUT = subprocess.STDOUT - def pipe(*, duplex: bool = False, overlapped: tuple[bool, bool] = ..., bufsize: int = 8192) -> tuple[int, int]: ... + def pipe(*, duplex: bool = False, overlapped: tuple[bool, bool] = (True, True), bufsize: int = 8192) -> tuple[int, int]: ... class PipeHandle: def __init__(self, handle: int) -> None: ... - if sys.version_info >= (3, 8): - def __del__(self, _warn: _WarnFunction = ...) -> None: ... - else: - def __del__(self) -> None: ... - + def __del__(self) -> None: ... def __enter__(self) -> Self: ... def __exit__(self, t: type[BaseException] | None, v: BaseException | None, tb: TracebackType | None) -> None: ... @property diff --git a/mypy/typeshed/stdlib/asyncore.pyi b/mypy/typeshed/stdlib/asyncore.pyi index 47c8e2207022..36d1862fdda7 100644 --- a/mypy/typeshed/stdlib/asyncore.pyi +++ b/mypy/typeshed/stdlib/asyncore.pyi @@ -83,6 +83,7 @@ if sys.platform != "win32": def write(self, data: bytes, flags: int = ...) -> int: ... def close(self) -> None: ... def fileno(self) -> int: ... + def __del__(self) -> None: ... class file_dispatcher(dispatcher): def __init__(self, fd: FileDescriptorLike, map: _MapType | None = None) -> None: ... diff --git a/mypy/typeshed/stdlib/audioop.pyi b/mypy/typeshed/stdlib/audioop.pyi index b5934516e40f..f3ce78ccb7fa 100644 --- a/mypy/typeshed/stdlib/audioop.pyi +++ b/mypy/typeshed/stdlib/audioop.pyi @@ -1,42 +1,43 @@ -from typing_extensions import TypeAlias +from typing_extensions import Buffer, TypeAlias _AdpcmState: TypeAlias = tuple[int, int] _RatecvState: TypeAlias = tuple[int, tuple[tuple[int, int], ...]] class error(Exception): ... -def add(__fragment1: bytes, __fragment2: bytes, __width: int) -> bytes: ... -def adpcm2lin(__fragment: bytes, __width: int, __state: _AdpcmState | None) -> tuple[bytes, _AdpcmState]: ... -def alaw2lin(__fragment: bytes, __width: int) -> bytes: ... -def avg(__fragment: bytes, __width: int) -> int: ... -def avgpp(__fragment: bytes, __width: int) -> int: ... -def bias(__fragment: bytes, __width: int, __bias: int) -> bytes: ... -def byteswap(__fragment: bytes, __width: int) -> bytes: ... -def cross(__fragment: bytes, __width: int) -> int: ... -def findfactor(__fragment: bytes, __reference: bytes) -> float: ... -def findfit(__fragment: bytes, __reference: bytes) -> tuple[int, float]: ... -def findmax(__fragment: bytes, __length: int) -> int: ... -def getsample(__fragment: bytes, __width: int, __index: int) -> int: ... -def lin2adpcm(__fragment: bytes, __width: int, __state: _AdpcmState | None) -> tuple[bytes, _AdpcmState]: ... -def lin2alaw(__fragment: bytes, __width: int) -> bytes: ... -def lin2lin(__fragment: bytes, __width: int, __newwidth: int) -> bytes: ... -def lin2ulaw(__fragment: bytes, __width: int) -> bytes: ... -def max(__fragment: bytes, __width: int) -> int: ... -def maxpp(__fragment: bytes, __width: int) -> int: ... -def minmax(__fragment: bytes, __width: int) -> tuple[int, int]: ... -def mul(__fragment: bytes, __width: int, __factor: float) -> bytes: ... +def add(fragment1: Buffer, fragment2: Buffer, width: int, /) -> bytes: ... +def adpcm2lin(fragment: Buffer, width: int, state: _AdpcmState | None, /) -> tuple[bytes, _AdpcmState]: ... +def alaw2lin(fragment: Buffer, width: int, /) -> bytes: ... +def avg(fragment: Buffer, width: int, /) -> int: ... +def avgpp(fragment: Buffer, width: int, /) -> int: ... +def bias(fragment: Buffer, width: int, bias: int, /) -> bytes: ... +def byteswap(fragment: Buffer, width: int, /) -> bytes: ... +def cross(fragment: Buffer, width: int, /) -> int: ... +def findfactor(fragment: Buffer, reference: Buffer, /) -> float: ... +def findfit(fragment: Buffer, reference: Buffer, /) -> tuple[int, float]: ... +def findmax(fragment: Buffer, length: int, /) -> int: ... +def getsample(fragment: Buffer, width: int, index: int, /) -> int: ... +def lin2adpcm(fragment: Buffer, width: int, state: _AdpcmState | None, /) -> tuple[bytes, _AdpcmState]: ... +def lin2alaw(fragment: Buffer, width: int, /) -> bytes: ... +def lin2lin(fragment: Buffer, width: int, newwidth: int, /) -> bytes: ... +def lin2ulaw(fragment: Buffer, width: int, /) -> bytes: ... +def max(fragment: Buffer, width: int, /) -> int: ... +def maxpp(fragment: Buffer, width: int, /) -> int: ... +def minmax(fragment: Buffer, width: int, /) -> tuple[int, int]: ... +def mul(fragment: Buffer, width: int, factor: float, /) -> bytes: ... def ratecv( - __fragment: bytes, - __width: int, - __nchannels: int, - __inrate: int, - __outrate: int, - __state: _RatecvState | None, - __weightA: int = 1, - __weightB: int = 0, + fragment: Buffer, + width: int, + nchannels: int, + inrate: int, + outrate: int, + state: _RatecvState | None, + weightA: int = 1, + weightB: int = 0, + /, ) -> tuple[bytes, _RatecvState]: ... -def reverse(__fragment: bytes, __width: int) -> bytes: ... -def rms(__fragment: bytes, __width: int) -> int: ... -def tomono(__fragment: bytes, __width: int, __lfactor: float, __rfactor: float) -> bytes: ... -def tostereo(__fragment: bytes, __width: int, __lfactor: float, __rfactor: float) -> bytes: ... -def ulaw2lin(__fragment: bytes, __width: int) -> bytes: ... +def reverse(fragment: Buffer, width: int, /) -> bytes: ... +def rms(fragment: Buffer, width: int, /) -> int: ... +def tomono(fragment: Buffer, width: int, lfactor: float, rfactor: float, /) -> bytes: ... +def tostereo(fragment: Buffer, width: int, lfactor: float, rfactor: float, /) -> bytes: ... +def ulaw2lin(fragment: Buffer, width: int, /) -> bytes: ... diff --git a/mypy/typeshed/stdlib/base64.pyi b/mypy/typeshed/stdlib/base64.pyi index 24830cbfba04..4629c95d0949 100644 --- a/mypy/typeshed/stdlib/base64.pyi +++ b/mypy/typeshed/stdlib/base64.pyi @@ -27,13 +27,13 @@ if sys.version_info >= (3, 10): __all__ += ["b32hexencode", "b32hexdecode"] def b64encode(s: ReadableBuffer, altchars: ReadableBuffer | None = None) -> bytes: ... -def b64decode(s: str | ReadableBuffer, altchars: ReadableBuffer | None = None, validate: bool = False) -> bytes: ... +def b64decode(s: str | ReadableBuffer, altchars: str | ReadableBuffer | None = None, validate: bool = False) -> bytes: ... def standard_b64encode(s: ReadableBuffer) -> bytes: ... def standard_b64decode(s: str | ReadableBuffer) -> bytes: ... def urlsafe_b64encode(s: ReadableBuffer) -> bytes: ... def urlsafe_b64decode(s: str | ReadableBuffer) -> bytes: ... def b32encode(s: ReadableBuffer) -> bytes: ... -def b32decode(s: str | ReadableBuffer, casefold: bool = False, map01: bytes | None = None) -> bytes: ... +def b32decode(s: str | ReadableBuffer, casefold: bool = False, map01: str | ReadableBuffer | None = None) -> bytes: ... def b16encode(s: ReadableBuffer) -> bytes: ... def b16decode(s: str | ReadableBuffer, casefold: bool = False) -> bytes: ... diff --git a/mypy/typeshed/stdlib/bdb.pyi b/mypy/typeshed/stdlib/bdb.pyi index 2a1fdddff7e9..a72e986728a7 100644 --- a/mypy/typeshed/stdlib/bdb.pyi +++ b/mypy/typeshed/stdlib/bdb.pyi @@ -2,8 +2,8 @@ import sys from _typeshed import ExcInfo, TraceFunction from collections.abc import Callable, Iterable, Mapping from types import CodeType, FrameType, TracebackType -from typing import IO, Any, SupportsInt, TypeVar -from typing_extensions import Literal, ParamSpec +from typing import IO, Any, Literal, SupportsInt, TypeVar +from typing_extensions import ParamSpec __all__ = ["BdbQuit", "Bdb", "Breakpoint"] @@ -50,11 +50,11 @@ class Bdb: def set_quit(self) -> None: ... def set_break( self, filename: str, lineno: int, temporary: bool = False, cond: str | None = None, funcname: str | None = None - ) -> None: ... - def clear_break(self, filename: str, lineno: int) -> None: ... - def clear_bpbynumber(self, arg: SupportsInt) -> None: ... - def clear_all_file_breaks(self, filename: str) -> None: ... - def clear_all_breaks(self) -> None: ... + ) -> str | None: ... + def clear_break(self, filename: str, lineno: int) -> str | None: ... + def clear_bpbynumber(self, arg: SupportsInt) -> str | None: ... + def clear_all_file_breaks(self, filename: str) -> str | None: ... + def clear_all_breaks(self) -> str | None: ... def get_bpbynumber(self, arg: SupportsInt) -> Breakpoint: ... def get_break(self, filename: str, lineno: int) -> bool: ... def get_breaks(self, filename: str, lineno: int) -> list[Breakpoint]: ... @@ -67,7 +67,7 @@ class Bdb: ) -> None: ... def runeval(self, expr: str, globals: dict[str, Any] | None = None, locals: Mapping[str, Any] | None = None) -> None: ... def runctx(self, cmd: str | CodeType, globals: dict[str, Any] | None, locals: Mapping[str, Any] | None) -> None: ... - def runcall(self, __func: Callable[_P, _T], *args: _P.args, **kwds: _P.kwargs) -> _T | None: ... + def runcall(self, func: Callable[_P, _T], /, *args: _P.args, **kwds: _P.kwargs) -> _T | None: ... class Breakpoint: next: int diff --git a/mypy/typeshed/stdlib/binascii.pyi b/mypy/typeshed/stdlib/binascii.pyi index 759b6c39399a..32e018c653cb 100644 --- a/mypy/typeshed/stdlib/binascii.pyi +++ b/mypy/typeshed/stdlib/binascii.pyi @@ -6,39 +6,31 @@ from typing_extensions import TypeAlias # or ASCII-only strings. _AsciiBuffer: TypeAlias = str | ReadableBuffer -def a2b_uu(__data: _AsciiBuffer) -> bytes: ... -def b2a_uu(__data: ReadableBuffer, *, backtick: bool = False) -> bytes: ... +def a2b_uu(data: _AsciiBuffer, /) -> bytes: ... +def b2a_uu(data: ReadableBuffer, /, *, backtick: bool = False) -> bytes: ... if sys.version_info >= (3, 11): - def a2b_base64(__data: _AsciiBuffer, *, strict_mode: bool = False) -> bytes: ... + def a2b_base64(data: _AsciiBuffer, /, *, strict_mode: bool = False) -> bytes: ... else: - def a2b_base64(__data: _AsciiBuffer) -> bytes: ... + def a2b_base64(data: _AsciiBuffer, /) -> bytes: ... -def b2a_base64(__data: ReadableBuffer, *, newline: bool = True) -> bytes: ... +def b2a_base64(data: ReadableBuffer, /, *, newline: bool = True) -> bytes: ... def a2b_qp(data: _AsciiBuffer, header: bool = False) -> bytes: ... def b2a_qp(data: ReadableBuffer, quotetabs: bool = False, istext: bool = True, header: bool = False) -> bytes: ... if sys.version_info < (3, 11): - def a2b_hqx(__data: _AsciiBuffer) -> bytes: ... - def rledecode_hqx(__data: ReadableBuffer) -> bytes: ... - def rlecode_hqx(__data: ReadableBuffer) -> bytes: ... - def b2a_hqx(__data: ReadableBuffer) -> bytes: ... - -def crc_hqx(__data: ReadableBuffer, __crc: int) -> int: ... -def crc32(__data: ReadableBuffer, __crc: int = 0) -> int: ... - -if sys.version_info >= (3, 8): - # sep must be str or bytes, not bytearray or any other buffer - def b2a_hex(data: ReadableBuffer, sep: str | bytes = ..., bytes_per_sep: int = ...) -> bytes: ... - def hexlify(data: ReadableBuffer, sep: str | bytes = ..., bytes_per_sep: int = ...) -> bytes: ... - -else: - def b2a_hex(__data: ReadableBuffer) -> bytes: ... - def hexlify(__data: ReadableBuffer) -> bytes: ... - -def a2b_hex(__hexstr: _AsciiBuffer) -> bytes: ... -def unhexlify(__hexstr: _AsciiBuffer) -> bytes: ... + def a2b_hqx(data: _AsciiBuffer, /) -> bytes: ... + def rledecode_hqx(data: ReadableBuffer, /) -> bytes: ... + def rlecode_hqx(data: ReadableBuffer, /) -> bytes: ... + def b2a_hqx(data: ReadableBuffer, /) -> bytes: ... + +def crc_hqx(data: ReadableBuffer, crc: int, /) -> int: ... +def crc32(data: ReadableBuffer, crc: int = 0, /) -> int: ... +def b2a_hex(data: ReadableBuffer, sep: str | bytes = ..., bytes_per_sep: int = ...) -> bytes: ... +def hexlify(data: ReadableBuffer, sep: str | bytes = ..., bytes_per_sep: int = ...) -> bytes: ... +def a2b_hex(hexstr: _AsciiBuffer, /) -> bytes: ... +def unhexlify(hexstr: _AsciiBuffer, /) -> bytes: ... class Error(ValueError): ... class Incomplete(Exception): ... diff --git a/mypy/typeshed/stdlib/binhex.pyi b/mypy/typeshed/stdlib/binhex.pyi index e0993c840ce7..d514be3b9b26 100644 --- a/mypy/typeshed/stdlib/binhex.pyi +++ b/mypy/typeshed/stdlib/binhex.pyi @@ -1,6 +1,6 @@ -from _typeshed import _BufferWithLen -from typing import IO, Any -from typing_extensions import Literal, TypeAlias +from _typeshed import SizedBuffer +from typing import IO, Any, Literal +from typing_extensions import TypeAlias __all__ = ["binhex", "hexbin", "Error"] @@ -28,9 +28,9 @@ class openrsrc: class BinHex: def __init__(self, name_finfo_dlen_rlen: _FileInfoTuple, ofp: _FileHandleUnion) -> None: ... - def write(self, data: _BufferWithLen) -> None: ... + def write(self, data: SizedBuffer) -> None: ... def close_data(self) -> None: ... - def write_rsrc(self, data: _BufferWithLen) -> None: ... + def write_rsrc(self, data: SizedBuffer) -> None: ... def close(self) -> None: ... def binhex(inp: str, out: str) -> None: ... diff --git a/mypy/typeshed/stdlib/builtins.pyi b/mypy/typeshed/stdlib/builtins.pyi index 7b8e25084c91..4c47a0736e2e 100644 --- a/mypy/typeshed/stdlib/builtins.pyi +++ b/mypy/typeshed/stdlib/builtins.pyi @@ -5,6 +5,8 @@ import types from _collections_abc import dict_items, dict_keys, dict_values from _typeshed import ( AnyStr_co, + ConvertibleToFloat, + ConvertibleToInt, FileDescriptorOrPath, OpenBinaryMode, OpenBinaryModeReading, @@ -16,6 +18,7 @@ from _typeshed import ( SupportsAiter, SupportsAnext, SupportsDivMod, + SupportsFlush, SupportsIter, SupportsKeysAndGetItem, SupportsLenAndGetItem, @@ -24,19 +27,17 @@ from _typeshed import ( SupportsRDivMod, SupportsRichComparison, SupportsRichComparisonT, - SupportsTrunc, SupportsWrite, ) from collections.abc import Awaitable, Callable, Iterable, Iterator, MutableSet, Reversible, Set as AbstractSet, Sized from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper -from types import CodeType, TracebackType, _Cell +from types import CellType, CodeType, TracebackType # mypy crashes if any of {ByteString, Sequence, MutableSequence, Mapping, MutableMapping} are imported from collections.abc in builtins.pyi from typing import ( # noqa: Y022 IO, Any, BinaryIO, - ByteString, ClassVar, Generic, Mapping, @@ -49,12 +50,25 @@ from typing import ( # noqa: Y022 SupportsBytes, SupportsComplex, SupportsFloat, - SupportsInt, + SupportsIndex, TypeVar, + final, overload, type_check_only, ) -from typing_extensions import Literal, Self, SupportsIndex, TypeAlias, TypeGuard, final + +# we can't import `Literal` from typing or mypy crashes: see #11247 +from typing_extensions import ( # noqa: Y023 + Concatenate, + Literal, + ParamSpec, + Self, + TypeAlias, + TypeGuard, + TypeIs, + TypeVarTuple, + deprecated, +) if sys.version_info >= (3, 9): from types import GenericAlias @@ -75,6 +89,7 @@ _SupportsNextT = TypeVar("_SupportsNextT", bound=SupportsNext[Any], covariant=Tr _SupportsAnextT = TypeVar("_SupportsAnextT", bound=SupportsAnext[Any], covariant=True) _AwaitableT = TypeVar("_AwaitableT", bound=Awaitable[Any]) _AwaitableT_co = TypeVar("_AwaitableT_co", bound=Awaitable[Any], covariant=True) +_P = ParamSpec("_P") class object: __doc__: str | None @@ -83,64 +98,71 @@ class object: __annotations__: dict[str, Any] @property def __class__(self) -> type[Self]: ... - # Ignore errors about type mismatch between property getter and setter @__class__.setter - def __class__(self, __type: type[object]) -> None: ... # noqa: F811 + def __class__(self, type: type[object], /) -> None: ... def __init__(self) -> None: ... def __new__(cls) -> Self: ... # N.B. `object.__setattr__` and `object.__delattr__` are heavily special-cased by type checkers. # Overriding them in subclasses has different semantics, even if the override has an identical signature. - def __setattr__(self, __name: str, __value: Any) -> None: ... - def __delattr__(self, __name: str) -> None: ... - def __eq__(self, __o: object) -> bool: ... - def __ne__(self, __o: object) -> bool: ... + def __setattr__(self, name: str, value: Any, /) -> None: ... + def __delattr__(self, name: str, /) -> None: ... + def __eq__(self, value: object, /) -> bool: ... + def __ne__(self, value: object, /) -> bool: ... def __str__(self) -> str: ... # noqa: Y029 def __repr__(self) -> str: ... # noqa: Y029 def __hash__(self) -> int: ... - def __format__(self, __format_spec: str) -> str: ... - def __getattribute__(self, __name: str) -> Any: ... + def __format__(self, format_spec: str, /) -> str: ... + def __getattribute__(self, name: str, /) -> Any: ... def __sizeof__(self) -> int: ... # return type of pickle methods is rather hard to express in the current type system # see #6661 and https://docs.python.org/3/library/pickle.html#object.__reduce__ def __reduce__(self) -> str | tuple[Any, ...]: ... - if sys.version_info >= (3, 8): - def __reduce_ex__(self, __protocol: SupportsIndex) -> str | tuple[Any, ...]: ... - else: - def __reduce_ex__(self, __protocol: int) -> str | tuple[Any, ...]: ... + def __reduce_ex__(self, protocol: SupportsIndex, /) -> str | tuple[Any, ...]: ... + if sys.version_info >= (3, 11): + def __getstate__(self) -> object: ... def __dir__(self) -> Iterable[str]: ... def __init_subclass__(cls) -> None: ... + @classmethod + def __subclasshook__(cls, subclass: type, /) -> bool: ... -class staticmethod(Generic[_R_co]): +class staticmethod(Generic[_P, _R_co]): @property - def __func__(self) -> Callable[..., _R_co]: ... + def __func__(self) -> Callable[_P, _R_co]: ... @property def __isabstractmethod__(self) -> bool: ... - def __init__(self: staticmethod[_R_co], __f: Callable[..., _R_co]) -> None: ... - def __get__(self, __obj: _T, __type: type[_T] | None = ...) -> Callable[..., _R_co]: ... + def __init__(self, f: Callable[_P, _R_co], /) -> None: ... + @overload + def __get__(self, instance: None, owner: type, /) -> Callable[_P, _R_co]: ... + @overload + def __get__(self, instance: _T, owner: type[_T] | None = None, /) -> Callable[_P, _R_co]: ... if sys.version_info >= (3, 10): __name__: str __qualname__: str @property - def __wrapped__(self) -> Callable[..., _R_co]: ... - def __call__(self, *args: Any, **kwargs: Any) -> _R_co: ... + def __wrapped__(self) -> Callable[_P, _R_co]: ... + def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _R_co: ... -class classmethod(Generic[_R_co]): +class classmethod(Generic[_T, _P, _R_co]): @property - def __func__(self) -> Callable[..., _R_co]: ... + def __func__(self) -> Callable[Concatenate[type[_T], _P], _R_co]: ... @property def __isabstractmethod__(self) -> bool: ... - def __init__(self: classmethod[_R_co], __f: Callable[..., _R_co]) -> None: ... - def __get__(self, __obj: _T, __type: type[_T] | None = ...) -> Callable[..., _R_co]: ... + def __init__(self, f: Callable[Concatenate[type[_T], _P], _R_co], /) -> None: ... + @overload + def __get__(self, instance: _T, owner: type[_T] | None = None, /) -> Callable[_P, _R_co]: ... + @overload + def __get__(self, instance: None, owner: type[_T], /) -> Callable[_P, _R_co]: ... if sys.version_info >= (3, 10): __name__: str __qualname__: str @property - def __wrapped__(self) -> Callable[..., _R_co]: ... + def __wrapped__(self) -> Callable[Concatenate[type[_T], _P], _R_co]: ... class type: + # object.__base__ is None. Otherwise, it would be a type. @property - def __base__(self) -> type: ... + def __base__(self) -> type | None: ... __bases__: tuple[type, ...] @property def __basicsize__(self) -> int: ... @@ -162,33 +184,35 @@ class type: @property def __weakrefoffset__(self) -> int: ... @overload - def __init__(self, __o: object) -> None: ... + def __init__(self, o: object, /) -> None: ... @overload - def __init__(self, __name: str, __bases: tuple[type, ...], __dict: dict[str, Any], **kwds: Any) -> None: ... + def __init__(self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None: ... @overload - def __new__(cls, __o: object) -> type: ... + def __new__(cls, o: object, /) -> type: ... @overload def __new__( - cls: type[_typeshed.Self], __name: str, __bases: tuple[type, ...], __namespace: dict[str, Any], **kwds: Any + cls: type[_typeshed.Self], name: str, bases: tuple[type, ...], namespace: dict[str, Any], /, **kwds: Any ) -> _typeshed.Self: ... def __call__(self, *args: Any, **kwds: Any) -> Any: ... def __subclasses__(self: _typeshed.Self) -> list[_typeshed.Self]: ... # Note: the documentation doesn't specify what the return type is, the standard # implementation seems to be returning a list. def mro(self) -> list[type]: ... - def __instancecheck__(self, __instance: Any) -> bool: ... - def __subclasscheck__(self, __subclass: type) -> bool: ... + def __instancecheck__(self, instance: Any, /) -> bool: ... + def __subclasscheck__(self, subclass: type, /) -> bool: ... @classmethod - def __prepare__(metacls, __name: str, __bases: tuple[type, ...], **kwds: Any) -> Mapping[str, object]: ... + def __prepare__(metacls, name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]: ... if sys.version_info >= (3, 10): - def __or__(self, __t: Any) -> types.UnionType: ... - def __ror__(self, __t: Any) -> types.UnionType: ... + def __or__(self, value: Any, /) -> types.UnionType: ... + def __ror__(self, value: Any, /) -> types.UnionType: ... + if sys.version_info >= (3, 12): + __type_params__: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] class super: @overload - def __init__(self, __t: Any, __obj: Any) -> None: ... + def __init__(self, t: Any, obj: Any, /) -> None: ... @overload - def __init__(self, __t: Any) -> None: ... + def __init__(self, t: Any, /) -> None: ... @overload def __init__(self) -> None: ... @@ -198,12 +222,10 @@ _LiteralInteger = _PositiveInteger | _NegativeInteger | Literal[0] # noqa: Y026 class int: @overload - def __new__(cls, __x: str | ReadableBuffer | SupportsInt | SupportsIndex | SupportsTrunc = ...) -> Self: ... + def __new__(cls, x: ConvertibleToInt = ..., /) -> Self: ... @overload - def __new__(cls, __x: str | bytes | bytearray, base: SupportsIndex) -> Self: ... - if sys.version_info >= (3, 8): - def as_integer_ratio(self) -> tuple[int, Literal[1]]: ... - + def __new__(cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self: ... + def as_integer_ratio(self) -> tuple[int, Literal[1]]: ... @property def real(self) -> int: ... @property @@ -240,104 +262,108 @@ class int: signed: bool = False, ) -> Self: ... - def __add__(self, __x: int) -> int: ... - def __sub__(self, __x: int) -> int: ... - def __mul__(self, __x: int) -> int: ... - def __floordiv__(self, __x: int) -> int: ... - def __truediv__(self, __x: int) -> float: ... - def __mod__(self, __x: int) -> int: ... - def __divmod__(self, __x: int) -> tuple[int, int]: ... - def __radd__(self, __x: int) -> int: ... - def __rsub__(self, __x: int) -> int: ... - def __rmul__(self, __x: int) -> int: ... - def __rfloordiv__(self, __x: int) -> int: ... - def __rtruediv__(self, __x: int) -> float: ... - def __rmod__(self, __x: int) -> int: ... - def __rdivmod__(self, __x: int) -> tuple[int, int]: ... - @overload - def __pow__(self, __x: Literal[0]) -> Literal[1]: ... - @overload - def __pow__(self, __x: Literal[0], __modulo: None) -> Literal[1]: ... - @overload - def __pow__(self, __x: _PositiveInteger, __modulo: None = None) -> int: ... - @overload - def __pow__(self, __x: _NegativeInteger, __modulo: None = None) -> float: ... - # positive x -> int; negative x -> float + if sys.version_info >= (3, 12): + def is_integer(self) -> Literal[True]: ... + + def __add__(self, value: int, /) -> int: ... + def __sub__(self, value: int, /) -> int: ... + def __mul__(self, value: int, /) -> int: ... + def __floordiv__(self, value: int, /) -> int: ... + def __truediv__(self, value: int, /) -> float: ... + def __mod__(self, value: int, /) -> int: ... + def __divmod__(self, value: int, /) -> tuple[int, int]: ... + def __radd__(self, value: int, /) -> int: ... + def __rsub__(self, value: int, /) -> int: ... + def __rmul__(self, value: int, /) -> int: ... + def __rfloordiv__(self, value: int, /) -> int: ... + def __rtruediv__(self, value: int, /) -> float: ... + def __rmod__(self, value: int, /) -> int: ... + def __rdivmod__(self, value: int, /) -> tuple[int, int]: ... + @overload + def __pow__(self, x: Literal[0], /) -> Literal[1]: ... + @overload + def __pow__(self, value: Literal[0], mod: None, /) -> Literal[1]: ... + @overload + def __pow__(self, value: _PositiveInteger, mod: None = None, /) -> int: ... + @overload + def __pow__(self, value: _NegativeInteger, mod: None = None, /) -> float: ... + # positive __value -> int; negative __value -> float # return type must be Any as `int | float` causes too many false-positive errors @overload - def __pow__(self, __x: int, __modulo: None = None) -> Any: ... - @overload - def __pow__(self, __x: int, __modulo: int) -> int: ... - def __rpow__(self, __x: int, __mod: int | None = None) -> Any: ... - def __and__(self, __n: int) -> int: ... - def __or__(self, __n: int) -> int: ... - def __xor__(self, __n: int) -> int: ... - def __lshift__(self, __n: int) -> int: ... - def __rshift__(self, __n: int) -> int: ... - def __rand__(self, __n: int) -> int: ... - def __ror__(self, __n: int) -> int: ... - def __rxor__(self, __n: int) -> int: ... - def __rlshift__(self, __n: int) -> int: ... - def __rrshift__(self, __n: int) -> int: ... + def __pow__(self, value: int, mod: None = None, /) -> Any: ... + @overload + def __pow__(self, value: int, mod: int, /) -> int: ... + def __rpow__(self, value: int, mod: int | None = None, /) -> Any: ... + def __and__(self, value: int, /) -> int: ... + def __or__(self, value: int, /) -> int: ... + def __xor__(self, value: int, /) -> int: ... + def __lshift__(self, value: int, /) -> int: ... + def __rshift__(self, value: int, /) -> int: ... + def __rand__(self, value: int, /) -> int: ... + def __ror__(self, value: int, /) -> int: ... + def __rxor__(self, value: int, /) -> int: ... + def __rlshift__(self, value: int, /) -> int: ... + def __rrshift__(self, value: int, /) -> int: ... def __neg__(self) -> int: ... def __pos__(self) -> int: ... def __invert__(self) -> int: ... def __trunc__(self) -> int: ... def __ceil__(self) -> int: ... def __floor__(self) -> int: ... - def __round__(self, __ndigits: SupportsIndex = ...) -> int: ... + def __round__(self, ndigits: SupportsIndex = ..., /) -> int: ... def __getnewargs__(self) -> tuple[int]: ... - def __eq__(self, __x: object) -> bool: ... - def __ne__(self, __x: object) -> bool: ... - def __lt__(self, __x: int) -> bool: ... - def __le__(self, __x: int) -> bool: ... - def __gt__(self, __x: int) -> bool: ... - def __ge__(self, __x: int) -> bool: ... + def __eq__(self, value: object, /) -> bool: ... + def __ne__(self, value: object, /) -> bool: ... + def __lt__(self, value: int, /) -> bool: ... + def __le__(self, value: int, /) -> bool: ... + def __gt__(self, value: int, /) -> bool: ... + def __ge__(self, value: int, /) -> bool: ... def __float__(self) -> float: ... def __int__(self) -> int: ... def __abs__(self) -> int: ... + def __hash__(self) -> int: ... def __bool__(self) -> bool: ... def __index__(self) -> int: ... class float: - def __new__(cls, __x: SupportsFloat | SupportsIndex | str | ReadableBuffer = ...) -> Self: ... + def __new__(cls, x: ConvertibleToFloat = ..., /) -> Self: ... def as_integer_ratio(self) -> tuple[int, int]: ... def hex(self) -> str: ... def is_integer(self) -> bool: ... @classmethod - def fromhex(cls, __s: str) -> Self: ... + def fromhex(cls, string: str, /) -> Self: ... @property def real(self) -> float: ... @property def imag(self) -> float: ... def conjugate(self) -> float: ... - def __add__(self, __x: float) -> float: ... - def __sub__(self, __x: float) -> float: ... - def __mul__(self, __x: float) -> float: ... - def __floordiv__(self, __x: float) -> float: ... - def __truediv__(self, __x: float) -> float: ... - def __mod__(self, __x: float) -> float: ... - def __divmod__(self, __x: float) -> tuple[float, float]: ... - @overload - def __pow__(self, __x: int, __mod: None = None) -> float: ... - # positive x -> float; negative x -> complex + def __add__(self, value: float, /) -> float: ... + def __sub__(self, value: float, /) -> float: ... + def __mul__(self, value: float, /) -> float: ... + def __floordiv__(self, value: float, /) -> float: ... + def __truediv__(self, value: float, /) -> float: ... + def __mod__(self, value: float, /) -> float: ... + def __divmod__(self, value: float, /) -> tuple[float, float]: ... + @overload + def __pow__(self, value: int, mod: None = None, /) -> float: ... + # positive __value -> float; negative __value -> complex # return type must be Any as `float | complex` causes too many false-positive errors @overload - def __pow__(self, __x: float, __mod: None = None) -> Any: ... - def __radd__(self, __x: float) -> float: ... - def __rsub__(self, __x: float) -> float: ... - def __rmul__(self, __x: float) -> float: ... - def __rfloordiv__(self, __x: float) -> float: ... - def __rtruediv__(self, __x: float) -> float: ... - def __rmod__(self, __x: float) -> float: ... - def __rdivmod__(self, __x: float) -> tuple[float, float]: ... + def __pow__(self, value: float, mod: None = None, /) -> Any: ... + def __radd__(self, value: float, /) -> float: ... + def __rsub__(self, value: float, /) -> float: ... + def __rmul__(self, value: float, /) -> float: ... + def __rfloordiv__(self, value: float, /) -> float: ... + def __rtruediv__(self, value: float, /) -> float: ... + def __rmod__(self, value: float, /) -> float: ... + def __rdivmod__(self, value: float, /) -> tuple[float, float]: ... @overload - def __rpow__(self, __x: _PositiveInteger, __modulo: None = None) -> float: ... + def __rpow__(self, value: _PositiveInteger, mod: None = None, /) -> float: ... @overload - def __rpow__(self, __x: _NegativeInteger, __mod: None = None) -> complex: ... + def __rpow__(self, value: _NegativeInteger, mod: None = None, /) -> complex: ... # Returning `complex` for the general case gives too many false-positive errors. @overload - def __rpow__(self, __x: float, __mod: None = None) -> Any: ... + def __rpow__(self, value: float, mod: None = None, /) -> Any: ... def __getnewargs__(self) -> tuple[float]: ... def __trunc__(self) -> int: ... if sys.version_info >= (3, 9): @@ -345,68 +371,63 @@ class float: def __floor__(self) -> int: ... @overload - def __round__(self, __ndigits: None = None) -> int: ... + def __round__(self, ndigits: None = None, /) -> int: ... @overload - def __round__(self, __ndigits: SupportsIndex) -> float: ... - def __eq__(self, __x: object) -> bool: ... - def __ne__(self, __x: object) -> bool: ... - def __lt__(self, __x: float) -> bool: ... - def __le__(self, __x: float) -> bool: ... - def __gt__(self, __x: float) -> bool: ... - def __ge__(self, __x: float) -> bool: ... + def __round__(self, ndigits: SupportsIndex, /) -> float: ... + def __eq__(self, value: object, /) -> bool: ... + def __ne__(self, value: object, /) -> bool: ... + def __lt__(self, value: float, /) -> bool: ... + def __le__(self, value: float, /) -> bool: ... + def __gt__(self, value: float, /) -> bool: ... + def __ge__(self, value: float, /) -> bool: ... def __neg__(self) -> float: ... def __pos__(self) -> float: ... def __int__(self) -> int: ... def __float__(self) -> float: ... def __abs__(self) -> float: ... + def __hash__(self) -> int: ... def __bool__(self) -> bool: ... class complex: - if sys.version_info >= (3, 8): - # Python doesn't currently accept SupportsComplex for the second argument - @overload - def __new__( - cls, - real: complex | SupportsComplex | SupportsFloat | SupportsIndex = ..., - imag: complex | SupportsFloat | SupportsIndex = ..., - ) -> Self: ... - @overload - def __new__(cls, real: str | SupportsComplex | SupportsFloat | SupportsIndex | complex) -> Self: ... - else: - @overload - def __new__(cls, real: complex | SupportsComplex | SupportsFloat = ..., imag: complex | SupportsFloat = ...) -> Self: ... - @overload - def __new__(cls, real: str | SupportsComplex | SupportsFloat | complex) -> Self: ... - + # Python doesn't currently accept SupportsComplex for the second argument + @overload + def __new__( + cls, + real: complex | SupportsComplex | SupportsFloat | SupportsIndex = ..., + imag: complex | SupportsFloat | SupportsIndex = ..., + ) -> Self: ... + @overload + def __new__(cls, real: str | SupportsComplex | SupportsFloat | SupportsIndex | complex) -> Self: ... @property def real(self) -> float: ... @property def imag(self) -> float: ... def conjugate(self) -> complex: ... - def __add__(self, __x: complex) -> complex: ... - def __sub__(self, __x: complex) -> complex: ... - def __mul__(self, __x: complex) -> complex: ... - def __pow__(self, __x: complex, __mod: None = None) -> complex: ... - def __truediv__(self, __x: complex) -> complex: ... - def __radd__(self, __x: complex) -> complex: ... - def __rsub__(self, __x: complex) -> complex: ... - def __rmul__(self, __x: complex) -> complex: ... - def __rpow__(self, __x: complex, __mod: None = None) -> complex: ... - def __rtruediv__(self, __x: complex) -> complex: ... - def __eq__(self, __x: object) -> bool: ... - def __ne__(self, __x: object) -> bool: ... + def __add__(self, value: complex, /) -> complex: ... + def __sub__(self, value: complex, /) -> complex: ... + def __mul__(self, value: complex, /) -> complex: ... + def __pow__(self, value: complex, mod: None = None, /) -> complex: ... + def __truediv__(self, value: complex, /) -> complex: ... + def __radd__(self, value: complex, /) -> complex: ... + def __rsub__(self, value: complex, /) -> complex: ... + def __rmul__(self, value: complex, /) -> complex: ... + def __rpow__(self, value: complex, mod: None = None, /) -> complex: ... + def __rtruediv__(self, value: complex, /) -> complex: ... + def __eq__(self, value: object, /) -> bool: ... + def __ne__(self, value: object, /) -> bool: ... def __neg__(self) -> complex: ... def __pos__(self) -> complex: ... def __abs__(self) -> float: ... + def __hash__(self) -> int: ... def __bool__(self) -> bool: ... if sys.version_info >= (3, 11): def __complex__(self) -> complex: ... class _FormatMapMapping(Protocol): - def __getitem__(self, __key: str) -> Any: ... + def __getitem__(self, key: str, /) -> Any: ... class _TranslateTable(Protocol): - def __getitem__(self, __key: int) -> str | int | None: ... + def __getitem__(self, key: int, /) -> str | int | None: ... class str(Sequence[str]): @overload @@ -415,21 +436,17 @@ class str(Sequence[str]): def __new__(cls, object: ReadableBuffer, encoding: str = ..., errors: str = ...) -> Self: ... def capitalize(self) -> str: ... # type: ignore[misc] def casefold(self) -> str: ... # type: ignore[misc] - def center(self, __width: SupportsIndex, __fillchar: str = " ") -> str: ... # type: ignore[misc] - def count(self, x: str, __start: SupportsIndex | None = ..., __end: SupportsIndex | None = ...) -> int: ... + def center(self, width: SupportsIndex, fillchar: str = " ", /) -> str: ... # type: ignore[misc] + def count(self, sub: str, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., /) -> int: ... def encode(self, encoding: str = "utf-8", errors: str = "strict") -> bytes: ... def endswith( - self, __suffix: str | tuple[str, ...], __start: SupportsIndex | None = ..., __end: SupportsIndex | None = ... + self, suffix: str | tuple[str, ...], start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., / ) -> bool: ... - if sys.version_info >= (3, 8): - def expandtabs(self, tabsize: SupportsIndex = 8) -> str: ... # type: ignore[misc] - else: - def expandtabs(self, tabsize: int = 8) -> str: ... # type: ignore[misc] - - def find(self, __sub: str, __start: SupportsIndex | None = ..., __end: SupportsIndex | None = ...) -> int: ... - def format(self, *args: object, **kwargs: object) -> str: ... # type: ignore[misc] + def expandtabs(self, tabsize: SupportsIndex = 8) -> str: ... # type: ignore[misc] + def find(self, sub: str, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., /) -> int: ... + def format(self, *args: object, **kwargs: object) -> str: ... def format_map(self, map: _FormatMapMapping) -> str: ... - def index(self, __sub: str, __start: SupportsIndex | None = ..., __end: SupportsIndex | None = ...) -> int: ... + def index(self, sub: str, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., /) -> int: ... def isalnum(self) -> bool: ... def isalpha(self) -> bool: ... def isascii(self) -> bool: ... @@ -442,93 +459,87 @@ class str(Sequence[str]): def isspace(self) -> bool: ... def istitle(self) -> bool: ... def isupper(self) -> bool: ... - def join(self, __iterable: Iterable[str]) -> str: ... # type: ignore[misc] - def ljust(self, __width: SupportsIndex, __fillchar: str = " ") -> str: ... # type: ignore[misc] + def join(self, iterable: Iterable[str], /) -> str: ... # type: ignore[misc] + def ljust(self, width: SupportsIndex, fillchar: str = " ", /) -> str: ... # type: ignore[misc] def lower(self) -> str: ... # type: ignore[misc] - def lstrip(self, __chars: str | None = None) -> str: ... # type: ignore[misc] - def partition(self, __sep: str) -> tuple[str, str, str]: ... # type: ignore[misc] - def replace(self, __old: str, __new: str, __count: SupportsIndex = -1) -> str: ... # type: ignore[misc] + def lstrip(self, chars: str | None = None, /) -> str: ... # type: ignore[misc] + def partition(self, sep: str, /) -> tuple[str, str, str]: ... # type: ignore[misc] + def replace(self, old: str, new: str, count: SupportsIndex = -1, /) -> str: ... # type: ignore[misc] if sys.version_info >= (3, 9): - def removeprefix(self, __prefix: str) -> str: ... # type: ignore[misc] - def removesuffix(self, __suffix: str) -> str: ... # type: ignore[misc] + def removeprefix(self, prefix: str, /) -> str: ... # type: ignore[misc] + def removesuffix(self, suffix: str, /) -> str: ... # type: ignore[misc] - def rfind(self, __sub: str, __start: SupportsIndex | None = ..., __end: SupportsIndex | None = ...) -> int: ... - def rindex(self, __sub: str, __start: SupportsIndex | None = ..., __end: SupportsIndex | None = ...) -> int: ... - def rjust(self, __width: SupportsIndex, __fillchar: str = " ") -> str: ... # type: ignore[misc] - def rpartition(self, __sep: str) -> tuple[str, str, str]: ... # type: ignore[misc] + def rfind(self, sub: str, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., /) -> int: ... + def rindex(self, sub: str, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., /) -> int: ... + def rjust(self, width: SupportsIndex, fillchar: str = " ", /) -> str: ... # type: ignore[misc] + def rpartition(self, sep: str, /) -> tuple[str, str, str]: ... # type: ignore[misc] def rsplit(self, sep: str | None = None, maxsplit: SupportsIndex = -1) -> list[str]: ... # type: ignore[misc] - def rstrip(self, __chars: str | None = None) -> str: ... # type: ignore[misc] + def rstrip(self, chars: str | None = None, /) -> str: ... # type: ignore[misc] def split(self, sep: str | None = None, maxsplit: SupportsIndex = -1) -> list[str]: ... # type: ignore[misc] def splitlines(self, keepends: bool = False) -> list[str]: ... # type: ignore[misc] def startswith( - self, __prefix: str | tuple[str, ...], __start: SupportsIndex | None = ..., __end: SupportsIndex | None = ... + self, prefix: str | tuple[str, ...], start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., / ) -> bool: ... - def strip(self, __chars: str | None = None) -> str: ... # type: ignore[misc] + def strip(self, chars: str | None = None, /) -> str: ... # type: ignore[misc] def swapcase(self) -> str: ... # type: ignore[misc] def title(self) -> str: ... # type: ignore[misc] - def translate(self, __table: _TranslateTable) -> str: ... + def translate(self, table: _TranslateTable, /) -> str: ... def upper(self) -> str: ... # type: ignore[misc] - def zfill(self, __width: SupportsIndex) -> str: ... # type: ignore[misc] + def zfill(self, width: SupportsIndex, /) -> str: ... # type: ignore[misc] @staticmethod @overload - def maketrans(__x: dict[int, _T] | dict[str, _T] | dict[str | int, _T]) -> dict[int, _T]: ... + def maketrans(x: dict[int, _T] | dict[str, _T] | dict[str | int, _T], /) -> dict[int, _T]: ... @staticmethod @overload - def maketrans(__x: str, __y: str) -> dict[int, int]: ... + def maketrans(x: str, y: str, /) -> dict[int, int]: ... @staticmethod @overload - def maketrans(__x: str, __y: str, __z: str) -> dict[int, int | None]: ... - def __add__(self, __s: str) -> str: ... # type: ignore[misc] + def maketrans(x: str, y: str, z: str, /) -> dict[int, int | None]: ... + def __add__(self, value: str, /) -> str: ... # type: ignore[misc] # Incompatible with Sequence.__contains__ - def __contains__(self, __o: str) -> bool: ... # type: ignore[override] - def __eq__(self, __x: object) -> bool: ... - def __ge__(self, __x: str) -> bool: ... - def __getitem__(self, __i: SupportsIndex | slice) -> str: ... - def __gt__(self, __x: str) -> bool: ... + def __contains__(self, key: str, /) -> bool: ... # type: ignore[override] + def __eq__(self, value: object, /) -> bool: ... + def __ge__(self, value: str, /) -> bool: ... + def __getitem__(self, key: SupportsIndex | slice, /) -> str: ... + def __gt__(self, value: str, /) -> bool: ... + def __hash__(self) -> int: ... def __iter__(self) -> Iterator[str]: ... # type: ignore[misc] - def __le__(self, __x: str) -> bool: ... + def __le__(self, value: str, /) -> bool: ... def __len__(self) -> int: ... - def __lt__(self, __x: str) -> bool: ... - def __mod__(self, __x: Any) -> str: ... # type: ignore[misc] - def __mul__(self, __n: SupportsIndex) -> str: ... # type: ignore[misc] - def __ne__(self, __x: object) -> bool: ... - def __rmul__(self, __n: SupportsIndex) -> str: ... # type: ignore[misc] + def __lt__(self, value: str, /) -> bool: ... + def __mod__(self, value: Any, /) -> str: ... + def __mul__(self, value: SupportsIndex, /) -> str: ... # type: ignore[misc] + def __ne__(self, value: object, /) -> bool: ... + def __rmul__(self, value: SupportsIndex, /) -> str: ... # type: ignore[misc] def __getnewargs__(self) -> tuple[str]: ... -class bytes(ByteString): +class bytes(Sequence[int]): @overload - def __new__(cls, __o: Iterable[SupportsIndex] | SupportsIndex | SupportsBytes | ReadableBuffer) -> Self: ... + def __new__(cls, o: Iterable[SupportsIndex] | SupportsIndex | SupportsBytes | ReadableBuffer, /) -> Self: ... @overload - def __new__(cls, __string: str, encoding: str, errors: str = ...) -> Self: ... + def __new__(cls, string: str, /, encoding: str, errors: str = ...) -> Self: ... @overload def __new__(cls) -> Self: ... def capitalize(self) -> bytes: ... - def center(self, __width: SupportsIndex, __fillchar: bytes = b" ") -> bytes: ... + def center(self, width: SupportsIndex, fillchar: bytes = b" ", /) -> bytes: ... def count( - self, __sub: ReadableBuffer | SupportsIndex, __start: SupportsIndex | None = ..., __end: SupportsIndex | None = ... + self, sub: ReadableBuffer | SupportsIndex, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., / ) -> int: ... def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str: ... def endswith( self, - __suffix: ReadableBuffer | tuple[ReadableBuffer, ...], - __start: SupportsIndex | None = ..., - __end: SupportsIndex | None = ..., + suffix: ReadableBuffer | tuple[ReadableBuffer, ...], + start: SupportsIndex | None = ..., + end: SupportsIndex | None = ..., + /, ) -> bool: ... - if sys.version_info >= (3, 8): - def expandtabs(self, tabsize: SupportsIndex = 8) -> bytes: ... - else: - def expandtabs(self, tabsize: int = ...) -> bytes: ... - + def expandtabs(self, tabsize: SupportsIndex = 8) -> bytes: ... def find( - self, __sub: ReadableBuffer | SupportsIndex, __start: SupportsIndex | None = ..., __end: SupportsIndex | None = ... + self, sub: ReadableBuffer | SupportsIndex, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., / ) -> int: ... - if sys.version_info >= (3, 8): - def hex(self, sep: str | bytes = ..., bytes_per_sep: SupportsIndex = ...) -> str: ... - else: - def hex(self) -> str: ... - + def hex(self, sep: str | bytes = ..., bytes_per_sep: SupportsIndex = ...) -> str: ... def index( - self, __sub: ReadableBuffer | SupportsIndex, __start: SupportsIndex | None = ..., __end: SupportsIndex | None = ... + self, sub: ReadableBuffer | SupportsIndex, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., / ) -> int: ... def isalnum(self) -> bool: ... def isalpha(self) -> bool: ... @@ -538,105 +549,102 @@ class bytes(ByteString): def isspace(self) -> bool: ... def istitle(self) -> bool: ... def isupper(self) -> bool: ... - def join(self, __iterable_of_bytes: Iterable[ReadableBuffer]) -> bytes: ... - def ljust(self, __width: SupportsIndex, __fillchar: bytes | bytearray = b" ") -> bytes: ... + def join(self, iterable_of_bytes: Iterable[ReadableBuffer], /) -> bytes: ... + def ljust(self, width: SupportsIndex, fillchar: bytes | bytearray = b" ", /) -> bytes: ... def lower(self) -> bytes: ... - def lstrip(self, __bytes: ReadableBuffer | None = None) -> bytes: ... - def partition(self, __sep: ReadableBuffer) -> tuple[bytes, bytes, bytes]: ... - def replace(self, __old: ReadableBuffer, __new: ReadableBuffer, __count: SupportsIndex = -1) -> bytes: ... + def lstrip(self, bytes: ReadableBuffer | None = None, /) -> bytes: ... + def partition(self, sep: ReadableBuffer, /) -> tuple[bytes, bytes, bytes]: ... + def replace(self, old: ReadableBuffer, new: ReadableBuffer, count: SupportsIndex = -1, /) -> bytes: ... if sys.version_info >= (3, 9): - def removeprefix(self, __prefix: ReadableBuffer) -> bytes: ... - def removesuffix(self, __suffix: ReadableBuffer) -> bytes: ... + def removeprefix(self, prefix: ReadableBuffer, /) -> bytes: ... + def removesuffix(self, suffix: ReadableBuffer, /) -> bytes: ... def rfind( - self, __sub: ReadableBuffer | SupportsIndex, __start: SupportsIndex | None = ..., __end: SupportsIndex | None = ... + self, sub: ReadableBuffer | SupportsIndex, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., / ) -> int: ... def rindex( - self, __sub: ReadableBuffer | SupportsIndex, __start: SupportsIndex | None = ..., __end: SupportsIndex | None = ... + self, sub: ReadableBuffer | SupportsIndex, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., / ) -> int: ... - def rjust(self, __width: SupportsIndex, __fillchar: bytes | bytearray = b" ") -> bytes: ... - def rpartition(self, __sep: ReadableBuffer) -> tuple[bytes, bytes, bytes]: ... + def rjust(self, width: SupportsIndex, fillchar: bytes | bytearray = b" ", /) -> bytes: ... + def rpartition(self, sep: ReadableBuffer, /) -> tuple[bytes, bytes, bytes]: ... def rsplit(self, sep: ReadableBuffer | None = None, maxsplit: SupportsIndex = -1) -> list[bytes]: ... - def rstrip(self, __bytes: ReadableBuffer | None = None) -> bytes: ... + def rstrip(self, bytes: ReadableBuffer | None = None, /) -> bytes: ... def split(self, sep: ReadableBuffer | None = None, maxsplit: SupportsIndex = -1) -> list[bytes]: ... def splitlines(self, keepends: bool = False) -> list[bytes]: ... def startswith( self, - __prefix: ReadableBuffer | tuple[ReadableBuffer, ...], - __start: SupportsIndex | None = ..., - __end: SupportsIndex | None = ..., + prefix: ReadableBuffer | tuple[ReadableBuffer, ...], + start: SupportsIndex | None = ..., + end: SupportsIndex | None = ..., + /, ) -> bool: ... - def strip(self, __bytes: ReadableBuffer | None = None) -> bytes: ... + def strip(self, bytes: ReadableBuffer | None = None, /) -> bytes: ... def swapcase(self) -> bytes: ... def title(self) -> bytes: ... - def translate(self, __table: ReadableBuffer | None, delete: bytes = b"") -> bytes: ... + def translate(self, table: ReadableBuffer | None, /, delete: bytes = b"") -> bytes: ... def upper(self) -> bytes: ... - def zfill(self, __width: SupportsIndex) -> bytes: ... + def zfill(self, width: SupportsIndex, /) -> bytes: ... @classmethod - def fromhex(cls, __s: str) -> Self: ... + def fromhex(cls, string: str, /) -> Self: ... @staticmethod - def maketrans(__frm: ReadableBuffer, __to: ReadableBuffer) -> bytes: ... + def maketrans(frm: ReadableBuffer, to: ReadableBuffer, /) -> bytes: ... def __len__(self) -> int: ... def __iter__(self) -> Iterator[int]: ... + def __hash__(self) -> int: ... @overload - def __getitem__(self, __i: SupportsIndex) -> int: ... + def __getitem__(self, key: SupportsIndex, /) -> int: ... @overload - def __getitem__(self, __s: slice) -> bytes: ... - def __add__(self, __s: ReadableBuffer) -> bytes: ... - def __mul__(self, __n: SupportsIndex) -> bytes: ... - def __rmul__(self, __n: SupportsIndex) -> bytes: ... - def __mod__(self, __value: Any) -> bytes: ... + def __getitem__(self, key: slice, /) -> bytes: ... + def __add__(self, value: ReadableBuffer, /) -> bytes: ... + def __mul__(self, value: SupportsIndex, /) -> bytes: ... + def __rmul__(self, value: SupportsIndex, /) -> bytes: ... + def __mod__(self, value: Any, /) -> bytes: ... # Incompatible with Sequence.__contains__ - def __contains__(self, __o: SupportsIndex | ReadableBuffer) -> bool: ... # type: ignore[override] - def __eq__(self, __x: object) -> bool: ... - def __ne__(self, __x: object) -> bool: ... - def __lt__(self, __x: bytes) -> bool: ... - def __le__(self, __x: bytes) -> bool: ... - def __gt__(self, __x: bytes) -> bool: ... - def __ge__(self, __x: bytes) -> bool: ... + def __contains__(self, key: SupportsIndex | ReadableBuffer, /) -> bool: ... # type: ignore[override] + def __eq__(self, value: object, /) -> bool: ... + def __ne__(self, value: object, /) -> bool: ... + def __lt__(self, value: bytes, /) -> bool: ... + def __le__(self, value: bytes, /) -> bool: ... + def __gt__(self, value: bytes, /) -> bool: ... + def __ge__(self, value: bytes, /) -> bool: ... def __getnewargs__(self) -> tuple[bytes]: ... if sys.version_info >= (3, 11): def __bytes__(self) -> bytes: ... -class bytearray(MutableSequence[int], ByteString): + def __buffer__(self, flags: int, /) -> memoryview: ... + +class bytearray(MutableSequence[int]): @overload def __init__(self) -> None: ... @overload - def __init__(self, __ints: Iterable[SupportsIndex] | SupportsIndex | ReadableBuffer) -> None: ... + def __init__(self, ints: Iterable[SupportsIndex] | SupportsIndex | ReadableBuffer, /) -> None: ... @overload - def __init__(self, __string: str, encoding: str, errors: str = ...) -> None: ... - def append(self, __item: SupportsIndex) -> None: ... + def __init__(self, string: str, /, encoding: str, errors: str = ...) -> None: ... + def append(self, item: SupportsIndex, /) -> None: ... def capitalize(self) -> bytearray: ... - def center(self, __width: SupportsIndex, __fillchar: bytes = b" ") -> bytearray: ... + def center(self, width: SupportsIndex, fillchar: bytes = b" ", /) -> bytearray: ... def count( - self, __sub: ReadableBuffer | SupportsIndex, __start: SupportsIndex | None = ..., __end: SupportsIndex | None = ... + self, sub: ReadableBuffer | SupportsIndex, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., / ) -> int: ... def copy(self) -> bytearray: ... def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str: ... def endswith( self, - __suffix: ReadableBuffer | tuple[ReadableBuffer, ...], - __start: SupportsIndex | None = ..., - __end: SupportsIndex | None = ..., + suffix: ReadableBuffer | tuple[ReadableBuffer, ...], + start: SupportsIndex | None = ..., + end: SupportsIndex | None = ..., + /, ) -> bool: ... - if sys.version_info >= (3, 8): - def expandtabs(self, tabsize: SupportsIndex = 8) -> bytearray: ... - else: - def expandtabs(self, tabsize: int = ...) -> bytearray: ... - - def extend(self, __iterable_of_ints: Iterable[SupportsIndex]) -> None: ... + def expandtabs(self, tabsize: SupportsIndex = 8) -> bytearray: ... + def extend(self, iterable_of_ints: Iterable[SupportsIndex], /) -> None: ... def find( - self, __sub: ReadableBuffer | SupportsIndex, __start: SupportsIndex | None = ..., __end: SupportsIndex | None = ... + self, sub: ReadableBuffer | SupportsIndex, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., / ) -> int: ... - if sys.version_info >= (3, 8): - def hex(self, sep: str | bytes = ..., bytes_per_sep: SupportsIndex = ...) -> str: ... - else: - def hex(self) -> str: ... - + def hex(self, sep: str | bytes = ..., bytes_per_sep: SupportsIndex = ...) -> str: ... def index( - self, __sub: ReadableBuffer | SupportsIndex, __start: SupportsIndex | None = ..., __end: SupportsIndex | None = ... + self, sub: ReadableBuffer | SupportsIndex, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., / ) -> int: ... - def insert(self, __index: SupportsIndex, __item: SupportsIndex) -> None: ... + def insert(self, index: SupportsIndex, item: SupportsIndex, /) -> None: ... def isalnum(self) -> bool: ... def isalpha(self) -> bool: ... def isascii(self) -> bool: ... @@ -645,74 +653,77 @@ class bytearray(MutableSequence[int], ByteString): def isspace(self) -> bool: ... def istitle(self) -> bool: ... def isupper(self) -> bool: ... - def join(self, __iterable_of_bytes: Iterable[ReadableBuffer]) -> bytearray: ... - def ljust(self, __width: SupportsIndex, __fillchar: bytes | bytearray = b" ") -> bytearray: ... + def join(self, iterable_of_bytes: Iterable[ReadableBuffer], /) -> bytearray: ... + def ljust(self, width: SupportsIndex, fillchar: bytes | bytearray = b" ", /) -> bytearray: ... def lower(self) -> bytearray: ... - def lstrip(self, __bytes: ReadableBuffer | None = None) -> bytearray: ... - def partition(self, __sep: ReadableBuffer) -> tuple[bytearray, bytearray, bytearray]: ... - def pop(self, __index: int = -1) -> int: ... - def remove(self, __value: int) -> None: ... + def lstrip(self, bytes: ReadableBuffer | None = None, /) -> bytearray: ... + def partition(self, sep: ReadableBuffer, /) -> tuple[bytearray, bytearray, bytearray]: ... + def pop(self, index: int = -1, /) -> int: ... + def remove(self, value: int, /) -> None: ... if sys.version_info >= (3, 9): - def removeprefix(self, __prefix: ReadableBuffer) -> bytearray: ... - def removesuffix(self, __suffix: ReadableBuffer) -> bytearray: ... + def removeprefix(self, prefix: ReadableBuffer, /) -> bytearray: ... + def removesuffix(self, suffix: ReadableBuffer, /) -> bytearray: ... - def replace(self, __old: ReadableBuffer, __new: ReadableBuffer, __count: SupportsIndex = -1) -> bytearray: ... + def replace(self, old: ReadableBuffer, new: ReadableBuffer, count: SupportsIndex = -1, /) -> bytearray: ... def rfind( - self, __sub: ReadableBuffer | SupportsIndex, __start: SupportsIndex | None = ..., __end: SupportsIndex | None = ... + self, sub: ReadableBuffer | SupportsIndex, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., / ) -> int: ... def rindex( - self, __sub: ReadableBuffer | SupportsIndex, __start: SupportsIndex | None = ..., __end: SupportsIndex | None = ... + self, sub: ReadableBuffer | SupportsIndex, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., / ) -> int: ... - def rjust(self, __width: SupportsIndex, __fillchar: bytes | bytearray = b" ") -> bytearray: ... - def rpartition(self, __sep: ReadableBuffer) -> tuple[bytearray, bytearray, bytearray]: ... + def rjust(self, width: SupportsIndex, fillchar: bytes | bytearray = b" ", /) -> bytearray: ... + def rpartition(self, sep: ReadableBuffer, /) -> tuple[bytearray, bytearray, bytearray]: ... def rsplit(self, sep: ReadableBuffer | None = None, maxsplit: SupportsIndex = -1) -> list[bytearray]: ... - def rstrip(self, __bytes: ReadableBuffer | None = None) -> bytearray: ... + def rstrip(self, bytes: ReadableBuffer | None = None, /) -> bytearray: ... def split(self, sep: ReadableBuffer | None = None, maxsplit: SupportsIndex = -1) -> list[bytearray]: ... def splitlines(self, keepends: bool = False) -> list[bytearray]: ... def startswith( self, - __prefix: ReadableBuffer | tuple[ReadableBuffer, ...], - __start: SupportsIndex | None = ..., - __end: SupportsIndex | None = ..., + prefix: ReadableBuffer | tuple[ReadableBuffer, ...], + start: SupportsIndex | None = ..., + end: SupportsIndex | None = ..., + /, ) -> bool: ... - def strip(self, __bytes: ReadableBuffer | None = None) -> bytearray: ... + def strip(self, bytes: ReadableBuffer | None = None, /) -> bytearray: ... def swapcase(self) -> bytearray: ... def title(self) -> bytearray: ... - def translate(self, __table: ReadableBuffer | None, delete: bytes = b"") -> bytearray: ... + def translate(self, table: ReadableBuffer | None, /, delete: bytes = b"") -> bytearray: ... def upper(self) -> bytearray: ... - def zfill(self, __width: SupportsIndex) -> bytearray: ... + def zfill(self, width: SupportsIndex, /) -> bytearray: ... @classmethod - def fromhex(cls, __string: str) -> Self: ... + def fromhex(cls, string: str, /) -> Self: ... @staticmethod - def maketrans(__frm: ReadableBuffer, __to: ReadableBuffer) -> bytes: ... + def maketrans(frm: ReadableBuffer, to: ReadableBuffer, /) -> bytes: ... def __len__(self) -> int: ... def __iter__(self) -> Iterator[int]: ... __hash__: ClassVar[None] # type: ignore[assignment] @overload - def __getitem__(self, __i: SupportsIndex) -> int: ... + def __getitem__(self, key: SupportsIndex, /) -> int: ... @overload - def __getitem__(self, __s: slice) -> bytearray: ... + def __getitem__(self, key: slice, /) -> bytearray: ... @overload - def __setitem__(self, __i: SupportsIndex, __x: SupportsIndex) -> None: ... + def __setitem__(self, key: SupportsIndex, value: SupportsIndex, /) -> None: ... @overload - def __setitem__(self, __s: slice, __x: Iterable[SupportsIndex] | bytes) -> None: ... - def __delitem__(self, __i: SupportsIndex | slice) -> None: ... - def __add__(self, __s: ReadableBuffer) -> bytearray: ... + def __setitem__(self, key: slice, value: Iterable[SupportsIndex] | bytes, /) -> None: ... + def __delitem__(self, key: SupportsIndex | slice, /) -> None: ... + def __add__(self, value: ReadableBuffer, /) -> bytearray: ... # The superclass wants us to accept Iterable[int], but that fails at runtime. - def __iadd__(self, __s: ReadableBuffer) -> Self: ... # type: ignore[override] - def __mul__(self, __n: SupportsIndex) -> bytearray: ... - def __rmul__(self, __n: SupportsIndex) -> bytearray: ... - def __imul__(self, __n: SupportsIndex) -> Self: ... - def __mod__(self, __value: Any) -> bytes: ... + def __iadd__(self, value: ReadableBuffer, /) -> Self: ... # type: ignore[override] + def __mul__(self, value: SupportsIndex, /) -> bytearray: ... + def __rmul__(self, value: SupportsIndex, /) -> bytearray: ... + def __imul__(self, value: SupportsIndex, /) -> Self: ... + def __mod__(self, value: Any, /) -> bytes: ... # Incompatible with Sequence.__contains__ - def __contains__(self, __o: SupportsIndex | ReadableBuffer) -> bool: ... # type: ignore[override] - def __eq__(self, __x: object) -> bool: ... - def __ne__(self, __x: object) -> bool: ... - def __lt__(self, __x: ReadableBuffer) -> bool: ... - def __le__(self, __x: ReadableBuffer) -> bool: ... - def __gt__(self, __x: ReadableBuffer) -> bool: ... - def __ge__(self, __x: ReadableBuffer) -> bool: ... + def __contains__(self, key: SupportsIndex | ReadableBuffer, /) -> bool: ... # type: ignore[override] + def __eq__(self, value: object, /) -> bool: ... + def __ne__(self, value: object, /) -> bool: ... + def __lt__(self, value: ReadableBuffer, /) -> bool: ... + def __le__(self, value: ReadableBuffer, /) -> bool: ... + def __gt__(self, value: ReadableBuffer, /) -> bool: ... + def __ge__(self, value: ReadableBuffer, /) -> bool: ... def __alloc__(self) -> int: ... + def __buffer__(self, flags: int, /) -> memoryview: ... + def __release_buffer__(self, buffer: memoryview, /) -> None: ... @final class memoryview(Sequence[int]): @@ -740,70 +751,69 @@ class memoryview(Sequence[int]): def contiguous(self) -> bool: ... @property def nbytes(self) -> int: ... - def __init__(self, obj: ReadableBuffer) -> None: ... + def __new__(cls, obj: ReadableBuffer) -> Self: ... def __enter__(self) -> Self: ... def __exit__( - self, __exc_type: type[BaseException] | None, __exc_val: BaseException | None, __exc_tb: TracebackType | None + self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, / ) -> None: ... def cast(self, format: str, shape: list[int] | tuple[int, ...] = ...) -> memoryview: ... @overload - def __getitem__(self, __i: SupportsIndex) -> int: ... + def __getitem__(self, key: SupportsIndex | tuple[SupportsIndex, ...], /) -> int: ... @overload - def __getitem__(self, __s: slice) -> memoryview: ... - def __contains__(self, __x: object) -> bool: ... + def __getitem__(self, key: slice, /) -> memoryview: ... + def __contains__(self, x: object, /) -> bool: ... def __iter__(self) -> Iterator[int]: ... def __len__(self) -> int: ... + def __eq__(self, value: object, /) -> bool: ... + def __hash__(self) -> int: ... @overload - def __setitem__(self, __s: slice, __o: ReadableBuffer) -> None: ... + def __setitem__(self, key: slice, value: ReadableBuffer, /) -> None: ... @overload - def __setitem__(self, __i: SupportsIndex, __o: SupportsIndex) -> None: ... + def __setitem__(self, key: SupportsIndex | tuple[SupportsIndex, ...], value: SupportsIndex, /) -> None: ... if sys.version_info >= (3, 10): def tobytes(self, order: Literal["C", "F", "A"] | None = "C") -> bytes: ... - elif sys.version_info >= (3, 8): - def tobytes(self, order: Literal["C", "F", "A"] | None = None) -> bytes: ... else: - def tobytes(self) -> bytes: ... + def tobytes(self, order: Literal["C", "F", "A"] | None = None) -> bytes: ... def tolist(self) -> list[int]: ... - if sys.version_info >= (3, 8): - def toreadonly(self) -> memoryview: ... - + def toreadonly(self) -> memoryview: ... def release(self) -> None: ... - if sys.version_info >= (3, 8): - def hex(self, sep: str | bytes = ..., bytes_per_sep: SupportsIndex = ...) -> str: ... - else: - def hex(self) -> str: ... + def hex(self, sep: str | bytes = ..., bytes_per_sep: SupportsIndex = ...) -> str: ... + def __buffer__(self, flags: int, /) -> memoryview: ... + def __release_buffer__(self, buffer: memoryview, /) -> None: ... @final class bool(int): - def __new__(cls, __o: object = ...) -> Self: ... + def __new__(cls, o: object = ..., /) -> Self: ... # The following overloads could be represented more elegantly with a TypeVar("_B", bool, int), # however mypy has a bug regarding TypeVar constraints (https://github.com/python/mypy/issues/11880). @overload - def __and__(self, __x: bool) -> bool: ... + def __and__(self, value: bool, /) -> bool: ... @overload - def __and__(self, __x: int) -> int: ... + def __and__(self, value: int, /) -> int: ... @overload - def __or__(self, __x: bool) -> bool: ... + def __or__(self, value: bool, /) -> bool: ... @overload - def __or__(self, __x: int) -> int: ... + def __or__(self, value: int, /) -> int: ... @overload - def __xor__(self, __x: bool) -> bool: ... + def __xor__(self, value: bool, /) -> bool: ... @overload - def __xor__(self, __x: int) -> int: ... + def __xor__(self, value: int, /) -> int: ... @overload - def __rand__(self, __x: bool) -> bool: ... + def __rand__(self, value: bool, /) -> bool: ... @overload - def __rand__(self, __x: int) -> int: ... + def __rand__(self, value: int, /) -> int: ... @overload - def __ror__(self, __x: bool) -> bool: ... + def __ror__(self, value: bool, /) -> bool: ... @overload - def __ror__(self, __x: int) -> int: ... + def __ror__(self, value: int, /) -> int: ... @overload - def __rxor__(self, __x: bool) -> bool: ... + def __rxor__(self, value: bool, /) -> bool: ... @overload - def __rxor__(self, __x: int) -> int: ... + def __rxor__(self, value: int, /) -> int: ... def __getnewargs__(self) -> tuple[int]: ... + @deprecated("Will throw an error in Python 3.14. Use `not` for logical negation of bools instead.") + def __invert__(self) -> int: ... @final class slice: @@ -814,35 +824,38 @@ class slice: @property def stop(self) -> Any: ... @overload - def __init__(self, __stop: Any) -> None: ... + def __new__(cls, stop: Any, /) -> Self: ... @overload - def __init__(self, __start: Any, __stop: Any, __step: Any = ...) -> None: ... + def __new__(cls, start: Any, stop: Any, step: Any = ..., /) -> Self: ... + def __eq__(self, value: object, /) -> bool: ... __hash__: ClassVar[None] # type: ignore[assignment] - def indices(self, __len: SupportsIndex) -> tuple[int, int, int]: ... + def indices(self, len: SupportsIndex, /) -> tuple[int, int, int]: ... -class tuple(Sequence[_T_co], Generic[_T_co]): - def __new__(cls, __iterable: Iterable[_T_co] = ...) -> Self: ... +class tuple(Sequence[_T_co]): + def __new__(cls, iterable: Iterable[_T_co] = ..., /) -> Self: ... def __len__(self) -> int: ... - def __contains__(self, __x: object) -> bool: ... + def __contains__(self, key: object, /) -> bool: ... @overload - def __getitem__(self, __x: SupportsIndex) -> _T_co: ... + def __getitem__(self, key: SupportsIndex, /) -> _T_co: ... @overload - def __getitem__(self, __x: slice) -> tuple[_T_co, ...]: ... + def __getitem__(self, key: slice, /) -> tuple[_T_co, ...]: ... def __iter__(self) -> Iterator[_T_co]: ... - def __lt__(self, __x: tuple[_T_co, ...]) -> bool: ... - def __le__(self, __x: tuple[_T_co, ...]) -> bool: ... - def __gt__(self, __x: tuple[_T_co, ...]) -> bool: ... - def __ge__(self, __x: tuple[_T_co, ...]) -> bool: ... - @overload - def __add__(self, __x: tuple[_T_co, ...]) -> tuple[_T_co, ...]: ... - @overload - def __add__(self, __x: tuple[_T, ...]) -> tuple[_T_co | _T, ...]: ... - def __mul__(self, __n: SupportsIndex) -> tuple[_T_co, ...]: ... - def __rmul__(self, __n: SupportsIndex) -> tuple[_T_co, ...]: ... - def count(self, __value: Any) -> int: ... - def index(self, __value: Any, __start: SupportsIndex = 0, __stop: SupportsIndex = sys.maxsize) -> int: ... + def __lt__(self, value: tuple[_T_co, ...], /) -> bool: ... + def __le__(self, value: tuple[_T_co, ...], /) -> bool: ... + def __gt__(self, value: tuple[_T_co, ...], /) -> bool: ... + def __ge__(self, value: tuple[_T_co, ...], /) -> bool: ... + def __eq__(self, value: object, /) -> bool: ... + def __hash__(self) -> int: ... + @overload + def __add__(self, value: tuple[_T_co, ...], /) -> tuple[_T_co, ...]: ... + @overload + def __add__(self, value: tuple[_T, ...], /) -> tuple[_T_co | _T, ...]: ... + def __mul__(self, value: SupportsIndex, /) -> tuple[_T_co, ...]: ... + def __rmul__(self, value: SupportsIndex, /) -> tuple[_T_co, ...]: ... + def count(self, value: Any, /) -> int: ... + def index(self, value: Any, start: SupportsIndex = 0, stop: SupportsIndex = sys.maxsize, /) -> int: ... if sys.version_info >= (3, 9): - def __class_getitem__(cls, __item: Any) -> GenericAlias: ... + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... # Doesn't exist at runtime, but deleting this breaks mypy. See #2999 @final @@ -850,7 +863,7 @@ class tuple(Sequence[_T_co], Generic[_T_co]): class function: # Make sure this class definition stays roughly in line with `types.FunctionType` @property - def __closure__(self) -> tuple[_Cell, ...] | None: ... + def __closure__(self) -> tuple[CellType, ...] | None: ... __code__: CodeType __defaults__: tuple[Any, ...] | None __dict__: dict[str, Any] @@ -863,26 +876,28 @@ class function: if sys.version_info >= (3, 10): @property def __builtins__(self) -> dict[str, Any]: ... + if sys.version_info >= (3, 12): + __type_params__: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] __module__: str # mypy uses `builtins.function.__get__` to represent methods, properties, and getset_descriptors so we type the return as Any. - def __get__(self, obj: object, type: type | None = ...) -> Any: ... + def __get__(self, instance: object, owner: type | None = None, /) -> Any: ... -class list(MutableSequence[_T], Generic[_T]): +class list(MutableSequence[_T]): @overload def __init__(self) -> None: ... @overload - def __init__(self, __iterable: Iterable[_T]) -> None: ... + def __init__(self, iterable: Iterable[_T], /) -> None: ... def copy(self) -> list[_T]: ... - def append(self, __object: _T) -> None: ... - def extend(self, __iterable: Iterable[_T]) -> None: ... - def pop(self, __index: SupportsIndex = -1) -> _T: ... + def append(self, object: _T, /) -> None: ... + def extend(self, iterable: Iterable[_T], /) -> None: ... + def pop(self, index: SupportsIndex = -1, /) -> _T: ... # Signature of `list.index` should be kept in line with `collections.UserList.index()` # and multiprocessing.managers.ListProxy.index() - def index(self, __value: _T, __start: SupportsIndex = 0, __stop: SupportsIndex = sys.maxsize) -> int: ... - def count(self, __value: _T) -> int: ... - def insert(self, __index: SupportsIndex, __object: _T) -> None: ... - def remove(self, __value: _T) -> None: ... + def index(self, value: _T, start: SupportsIndex = 0, stop: SupportsIndex = sys.maxsize, /) -> int: ... + def count(self, value: _T, /) -> int: ... + def insert(self, index: SupportsIndex, object: _T, /) -> None: ... + def remove(self, value: _T, /) -> None: ... # Signature of `list.sort` should be kept inline with `collections.UserList.sort()` # and multiprocessing.managers.ListProxy.sort() # @@ -896,51 +911,64 @@ class list(MutableSequence[_T], Generic[_T]): def __iter__(self) -> Iterator[_T]: ... __hash__: ClassVar[None] # type: ignore[assignment] @overload - def __getitem__(self, __i: SupportsIndex) -> _T: ... + def __getitem__(self, i: SupportsIndex, /) -> _T: ... @overload - def __getitem__(self, __s: slice) -> list[_T]: ... + def __getitem__(self, s: slice, /) -> list[_T]: ... @overload - def __setitem__(self, __i: SupportsIndex, __o: _T) -> None: ... + def __setitem__(self, key: SupportsIndex, value: _T, /) -> None: ... @overload - def __setitem__(self, __s: slice, __o: Iterable[_T]) -> None: ... - def __delitem__(self, __i: SupportsIndex | slice) -> None: ... + def __setitem__(self, key: slice, value: Iterable[_T], /) -> None: ... + def __delitem__(self, key: SupportsIndex | slice, /) -> None: ... # Overloading looks unnecessary, but is needed to work around complex mypy problems @overload - def __add__(self, __x: list[_T]) -> list[_T]: ... + def __add__(self, value: list[_T], /) -> list[_T]: ... @overload - def __add__(self, __x: list[_S]) -> list[_S | _T]: ... - def __iadd__(self, __x: Iterable[_T]) -> Self: ... # type: ignore[misc] - def __mul__(self, __n: SupportsIndex) -> list[_T]: ... - def __rmul__(self, __n: SupportsIndex) -> list[_T]: ... - def __imul__(self, __n: SupportsIndex) -> Self: ... - def __contains__(self, __o: object) -> bool: ... + def __add__(self, value: list[_S], /) -> list[_S | _T]: ... + def __iadd__(self, value: Iterable[_T], /) -> Self: ... # type: ignore[misc] + def __mul__(self, value: SupportsIndex, /) -> list[_T]: ... + def __rmul__(self, value: SupportsIndex, /) -> list[_T]: ... + def __imul__(self, value: SupportsIndex, /) -> Self: ... + def __contains__(self, key: object, /) -> bool: ... def __reversed__(self) -> Iterator[_T]: ... - def __gt__(self, __x: list[_T]) -> bool: ... - def __ge__(self, __x: list[_T]) -> bool: ... - def __lt__(self, __x: list[_T]) -> bool: ... - def __le__(self, __x: list[_T]) -> bool: ... + def __gt__(self, value: list[_T], /) -> bool: ... + def __ge__(self, value: list[_T], /) -> bool: ... + def __lt__(self, value: list[_T], /) -> bool: ... + def __le__(self, value: list[_T], /) -> bool: ... + def __eq__(self, value: object, /) -> bool: ... if sys.version_info >= (3, 9): - def __class_getitem__(cls, __item: Any) -> GenericAlias: ... + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... -class dict(MutableMapping[_KT, _VT], Generic[_KT, _VT]): +class dict(MutableMapping[_KT, _VT]): # __init__ should be kept roughly in line with `collections.UserDict.__init__`, which has similar semantics # Also multiprocessing.managers.SyncManager.dict() @overload def __init__(self) -> None: ... @overload - def __init__(self: dict[str, _VT], **kwargs: _VT) -> None: ... + def __init__(self: dict[str, _VT], **kwargs: _VT) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] #11780 @overload - def __init__(self, __map: SupportsKeysAndGetItem[_KT, _VT]) -> None: ... + def __init__(self, map: SupportsKeysAndGetItem[_KT, _VT], /) -> None: ... @overload - def __init__(self: dict[str, _VT], __map: SupportsKeysAndGetItem[str, _VT], **kwargs: _VT) -> None: ... + def __init__( + self: dict[str, _VT], # pyright: ignore[reportInvalidTypeVarUse] #11780 + map: SupportsKeysAndGetItem[str, _VT], + /, + **kwargs: _VT, + ) -> None: ... @overload - def __init__(self, __iterable: Iterable[tuple[_KT, _VT]]) -> None: ... + def __init__(self, iterable: Iterable[tuple[_KT, _VT]], /) -> None: ... @overload - def __init__(self: dict[str, _VT], __iterable: Iterable[tuple[str, _VT]], **kwargs: _VT) -> None: ... - # Next overload is for dict(string.split(sep) for string in iterable) + def __init__( + self: dict[str, _VT], # pyright: ignore[reportInvalidTypeVarUse] #11780 + iterable: Iterable[tuple[str, _VT]], + /, + **kwargs: _VT, + ) -> None: ... + # Next two overloads are for dict(string.split(sep) for string in iterable) # Cannot be Iterable[Sequence[_T]] or otherwise dict(["foo", "bar", "baz"]) is not an error @overload - def __init__(self: dict[str, str], __iterable: Iterable[list[str]]) -> None: ... + def __init__(self: dict[str, str], iterable: Iterable[list[str]], /) -> None: ... + @overload + def __init__(self: dict[bytes, bytes], iterable: Iterable[list[bytes]], /) -> None: ... def __new__(cls, *args: Any, **kwargs: Any) -> Self: ... def copy(self) -> dict[_KT, _VT]: ... def keys(self) -> dict_keys[_KT, _VT]: ... @@ -951,109 +979,122 @@ class dict(MutableMapping[_KT, _VT], Generic[_KT, _VT]): # See #3800 & https://github.com/python/typing/issues/548#issuecomment-683336963. @classmethod @overload - def fromkeys(cls, __iterable: Iterable[_T], __value: None = None) -> dict[_T, Any | None]: ... + def fromkeys(cls, iterable: Iterable[_T], value: None = None, /) -> dict[_T, Any | None]: ... @classmethod @overload - def fromkeys(cls, __iterable: Iterable[_T], __value: _S) -> dict[_T, _S]: ... + def fromkeys(cls, iterable: Iterable[_T], value: _S, /) -> dict[_T, _S]: ... # Positional-only in dict, but not in MutableMapping + @overload # type: ignore[override] + def get(self, key: _KT, /) -> _VT | None: ... @overload - def get(self, __key: _KT) -> _VT | None: ... + def get(self, key: _KT, default: _VT, /) -> _VT: ... @overload - def get(self, __key: _KT, __default: _VT | _T) -> _VT | _T: ... + def get(self, key: _KT, default: _T, /) -> _VT | _T: ... @overload - def pop(self, __key: _KT) -> _VT: ... + def pop(self, key: _KT, /) -> _VT: ... @overload - def pop(self, __key: _KT, __default: _VT | _T) -> _VT | _T: ... + def pop(self, key: _KT, default: _VT, /) -> _VT: ... + @overload + def pop(self, key: _KT, default: _T, /) -> _VT | _T: ... def __len__(self) -> int: ... - def __getitem__(self, __key: _KT) -> _VT: ... - def __setitem__(self, __key: _KT, __value: _VT) -> None: ... - def __delitem__(self, __key: _KT) -> None: ... + def __getitem__(self, key: _KT, /) -> _VT: ... + def __setitem__(self, key: _KT, value: _VT, /) -> None: ... + def __delitem__(self, key: _KT, /) -> None: ... def __iter__(self) -> Iterator[_KT]: ... - if sys.version_info >= (3, 8): - def __reversed__(self) -> Iterator[_KT]: ... + def __eq__(self, value: object, /) -> bool: ... + def __reversed__(self) -> Iterator[_KT]: ... __hash__: ClassVar[None] # type: ignore[assignment] if sys.version_info >= (3, 9): - def __class_getitem__(cls, __item: Any) -> GenericAlias: ... - def __or__(self, __value: Mapping[_T1, _T2]) -> dict[_KT | _T1, _VT | _T2]: ... - def __ror__(self, __value: Mapping[_T1, _T2]) -> dict[_KT | _T1, _VT | _T2]: ... + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... + @overload + def __or__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... + @overload + def __or__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... + @overload + def __ror__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... + @overload + def __ror__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... # dict.__ior__ should be kept roughly in line with MutableMapping.update() @overload # type: ignore[misc] - def __ior__(self, __value: SupportsKeysAndGetItem[_KT, _VT]) -> Self: ... + def __ior__(self, value: SupportsKeysAndGetItem[_KT, _VT], /) -> Self: ... @overload - def __ior__(self, __value: Iterable[tuple[_KT, _VT]]) -> Self: ... + def __ior__(self, value: Iterable[tuple[_KT, _VT]], /) -> Self: ... -class set(MutableSet[_T], Generic[_T]): +class set(MutableSet[_T]): @overload def __init__(self) -> None: ... @overload - def __init__(self, __iterable: Iterable[_T]) -> None: ... - def add(self, __element: _T) -> None: ... + def __init__(self, iterable: Iterable[_T], /) -> None: ... + def add(self, element: _T, /) -> None: ... def copy(self) -> set[_T]: ... def difference(self, *s: Iterable[Any]) -> set[_T]: ... def difference_update(self, *s: Iterable[Any]) -> None: ... - def discard(self, __element: _T) -> None: ... + def discard(self, element: _T, /) -> None: ... def intersection(self, *s: Iterable[Any]) -> set[_T]: ... def intersection_update(self, *s: Iterable[Any]) -> None: ... - def isdisjoint(self, __s: Iterable[Any]) -> bool: ... - def issubset(self, __s: Iterable[Any]) -> bool: ... - def issuperset(self, __s: Iterable[Any]) -> bool: ... - def remove(self, __element: _T) -> None: ... - def symmetric_difference(self, __s: Iterable[_T]) -> set[_T]: ... - def symmetric_difference_update(self, __s: Iterable[_T]) -> None: ... + def isdisjoint(self, s: Iterable[Any], /) -> bool: ... + def issubset(self, s: Iterable[Any], /) -> bool: ... + def issuperset(self, s: Iterable[Any], /) -> bool: ... + def remove(self, element: _T, /) -> None: ... + def symmetric_difference(self, s: Iterable[_T], /) -> set[_T]: ... + def symmetric_difference_update(self, s: Iterable[_T], /) -> None: ... def union(self, *s: Iterable[_S]) -> set[_T | _S]: ... def update(self, *s: Iterable[_T]) -> None: ... def __len__(self) -> int: ... - def __contains__(self, __o: object) -> bool: ... + def __contains__(self, o: object, /) -> bool: ... def __iter__(self) -> Iterator[_T]: ... - def __and__(self, __s: AbstractSet[object]) -> set[_T]: ... - def __iand__(self, __s: AbstractSet[object]) -> Self: ... - def __or__(self, __s: AbstractSet[_S]) -> set[_T | _S]: ... - def __ior__(self, __s: AbstractSet[_T]) -> Self: ... # type: ignore[override,misc] - def __sub__(self, __s: AbstractSet[_T | None]) -> set[_T]: ... - def __isub__(self, __s: AbstractSet[object]) -> Self: ... - def __xor__(self, __s: AbstractSet[_S]) -> set[_T | _S]: ... - def __ixor__(self, __s: AbstractSet[_T]) -> Self: ... # type: ignore[override,misc] - def __le__(self, __s: AbstractSet[object]) -> bool: ... - def __lt__(self, __s: AbstractSet[object]) -> bool: ... - def __ge__(self, __s: AbstractSet[object]) -> bool: ... - def __gt__(self, __s: AbstractSet[object]) -> bool: ... + def __and__(self, value: AbstractSet[object], /) -> set[_T]: ... + def __iand__(self, value: AbstractSet[object], /) -> Self: ... + def __or__(self, value: AbstractSet[_S], /) -> set[_T | _S]: ... + def __ior__(self, value: AbstractSet[_T], /) -> Self: ... # type: ignore[override,misc] + def __sub__(self, value: AbstractSet[_T | None], /) -> set[_T]: ... + def __isub__(self, value: AbstractSet[object], /) -> Self: ... + def __xor__(self, value: AbstractSet[_S], /) -> set[_T | _S]: ... + def __ixor__(self, value: AbstractSet[_T], /) -> Self: ... # type: ignore[override,misc] + def __le__(self, value: AbstractSet[object], /) -> bool: ... + def __lt__(self, value: AbstractSet[object], /) -> bool: ... + def __ge__(self, value: AbstractSet[object], /) -> bool: ... + def __gt__(self, value: AbstractSet[object], /) -> bool: ... + def __eq__(self, value: object, /) -> bool: ... __hash__: ClassVar[None] # type: ignore[assignment] if sys.version_info >= (3, 9): - def __class_getitem__(cls, __item: Any) -> GenericAlias: ... + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... -class frozenset(AbstractSet[_T_co], Generic[_T_co]): +class frozenset(AbstractSet[_T_co]): @overload def __new__(cls) -> Self: ... @overload - def __new__(cls, __iterable: Iterable[_T_co]) -> Self: ... + def __new__(cls, iterable: Iterable[_T_co], /) -> Self: ... def copy(self) -> frozenset[_T_co]: ... def difference(self, *s: Iterable[object]) -> frozenset[_T_co]: ... def intersection(self, *s: Iterable[object]) -> frozenset[_T_co]: ... - def isdisjoint(self, __s: Iterable[_T_co]) -> bool: ... - def issubset(self, __s: Iterable[object]) -> bool: ... - def issuperset(self, __s: Iterable[object]) -> bool: ... - def symmetric_difference(self, __s: Iterable[_T_co]) -> frozenset[_T_co]: ... + def isdisjoint(self, s: Iterable[_T_co], /) -> bool: ... + def issubset(self, s: Iterable[object], /) -> bool: ... + def issuperset(self, s: Iterable[object], /) -> bool: ... + def symmetric_difference(self, s: Iterable[_T_co], /) -> frozenset[_T_co]: ... def union(self, *s: Iterable[_S]) -> frozenset[_T_co | _S]: ... def __len__(self) -> int: ... - def __contains__(self, __o: object) -> bool: ... + def __contains__(self, o: object, /) -> bool: ... def __iter__(self) -> Iterator[_T_co]: ... - def __and__(self, __s: AbstractSet[_T_co]) -> frozenset[_T_co]: ... - def __or__(self, __s: AbstractSet[_S]) -> frozenset[_T_co | _S]: ... - def __sub__(self, __s: AbstractSet[_T_co]) -> frozenset[_T_co]: ... - def __xor__(self, __s: AbstractSet[_S]) -> frozenset[_T_co | _S]: ... - def __le__(self, __s: AbstractSet[object]) -> bool: ... - def __lt__(self, __s: AbstractSet[object]) -> bool: ... - def __ge__(self, __s: AbstractSet[object]) -> bool: ... - def __gt__(self, __s: AbstractSet[object]) -> bool: ... + def __and__(self, value: AbstractSet[_T_co], /) -> frozenset[_T_co]: ... + def __or__(self, value: AbstractSet[_S], /) -> frozenset[_T_co | _S]: ... + def __sub__(self, value: AbstractSet[_T_co], /) -> frozenset[_T_co]: ... + def __xor__(self, value: AbstractSet[_S], /) -> frozenset[_T_co | _S]: ... + def __le__(self, value: AbstractSet[object], /) -> bool: ... + def __lt__(self, value: AbstractSet[object], /) -> bool: ... + def __ge__(self, value: AbstractSet[object], /) -> bool: ... + def __gt__(self, value: AbstractSet[object], /) -> bool: ... + def __eq__(self, value: object, /) -> bool: ... + def __hash__(self) -> int: ... if sys.version_info >= (3, 9): - def __class_getitem__(cls, __item: Any) -> GenericAlias: ... + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... -class enumerate(Iterator[tuple[int, _T]], Generic[_T]): - def __init__(self, iterable: Iterable[_T], start: int = ...) -> None: ... +class enumerate(Iterator[tuple[int, _T]]): + def __new__(cls, iterable: Iterable[_T], start: int = ...) -> Self: ... def __iter__(self) -> Self: ... def __next__(self) -> tuple[int, _T]: ... if sys.version_info >= (3, 9): - def __class_getitem__(cls, __item: Any) -> GenericAlias: ... + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @final class range(Sequence[int]): @@ -1064,18 +1105,20 @@ class range(Sequence[int]): @property def step(self) -> int: ... @overload - def __init__(self, __stop: SupportsIndex) -> None: ... + def __new__(cls, stop: SupportsIndex, /) -> Self: ... @overload - def __init__(self, __start: SupportsIndex, __stop: SupportsIndex, __step: SupportsIndex = ...) -> None: ... - def count(self, __value: int) -> int: ... - def index(self, __value: int) -> int: ... # type: ignore[override] + def __new__(cls, start: SupportsIndex, stop: SupportsIndex, step: SupportsIndex = ..., /) -> Self: ... + def count(self, value: int, /) -> int: ... + def index(self, value: int, /) -> int: ... # type: ignore[override] def __len__(self) -> int: ... - def __contains__(self, __o: object) -> bool: ... + def __eq__(self, value: object, /) -> bool: ... + def __hash__(self) -> int: ... + def __contains__(self, key: object, /) -> bool: ... def __iter__(self) -> Iterator[int]: ... @overload - def __getitem__(self, __i: SupportsIndex) -> int: ... + def __getitem__(self, key: SupportsIndex, /) -> int: ... @overload - def __getitem__(self, __s: slice) -> range: ... + def __getitem__(self, key: slice, /) -> range: ... def __reversed__(self) -> Iterator[int]: ... class property: @@ -1090,29 +1133,29 @@ class property: fdel: Callable[[Any], None] | None = ..., doc: str | None = ..., ) -> None: ... - def getter(self, __fget: Callable[[Any], Any]) -> property: ... - def setter(self, __fset: Callable[[Any, Any], None]) -> property: ... - def deleter(self, __fdel: Callable[[Any], None]) -> property: ... - def __get__(self, __obj: Any, __type: type | None = ...) -> Any: ... - def __set__(self, __obj: Any, __value: Any) -> None: ... - def __delete__(self, __obj: Any) -> None: ... + def getter(self, fget: Callable[[Any], Any], /) -> property: ... + def setter(self, fset: Callable[[Any, Any], None], /) -> property: ... + def deleter(self, fdel: Callable[[Any], None], /) -> property: ... + def __get__(self, instance: Any, owner: type | None = None, /) -> Any: ... + def __set__(self, instance: Any, value: Any, /) -> None: ... + def __delete__(self, instance: Any, /) -> None: ... @final -class _NotImplementedType(Any): # type: ignore[misc] +class _NotImplementedType(Any): # A little weird, but typing the __call__ as NotImplemented makes the error message # for NotImplemented() much better - __call__: NotImplemented # type: ignore[valid-type] # pyright: ignore[reportGeneralTypeIssues] + __call__: NotImplemented # type: ignore[valid-type] # pyright: ignore[reportInvalidTypeForm] NotImplemented: _NotImplementedType -def abs(__x: SupportsAbs[_T]) -> _T: ... -def all(__iterable: Iterable[object]) -> bool: ... -def any(__iterable: Iterable[object]) -> bool: ... -def ascii(__obj: object) -> str: ... -def bin(__number: int | SupportsIndex) -> str: ... +def abs(x: SupportsAbs[_T], /) -> _T: ... +def all(iterable: Iterable[object], /) -> bool: ... +def any(iterable: Iterable[object], /) -> bool: ... +def ascii(obj: object, /) -> str: ... +def bin(number: int | SupportsIndex, /) -> str: ... def breakpoint(*args: Any, **kws: Any) -> None: ... -def callable(__obj: object) -> TypeGuard[Callable[..., object]]: ... -def chr(__i: int) -> str: ... +def callable(obj: object, /) -> TypeIs[Callable[..., object]]: ... +def chr(i: int, /) -> str: ... # We define this here instead of using os.PathLike to avoid import cycle issues. # See https://github.com/python/typeshed/pull/991#issuecomment-288160993 @@ -1120,7 +1163,7 @@ class _PathLike(Protocol[AnyStr_co]): def __fspath__(self) -> AnyStr_co: ... if sys.version_info >= (3, 10): - def aiter(__async_iterable: SupportsAiter[_SupportsAnextT]) -> _SupportsAnextT: ... + def aiter(async_iterable: SupportsAiter[_SupportsAnextT], /) -> _SupportsAnextT: ... class _SupportsSynchronousAnext(Protocol[_AwaitableT_co]): def __anext__(self) -> _AwaitableT_co: ... @@ -1129,266 +1172,232 @@ if sys.version_info >= (3, 10): # `anext` is not, in fact, an async function. When default is not provided # `anext` is just a passthrough for `obj.__anext__` # See discussion in #7491 and pure-Python implementation of `anext` at https://github.com/python/cpython/blob/ea786a882b9ed4261eafabad6011bc7ef3b5bf94/Lib/test/test_asyncgen.py#L52-L80 - def anext(__i: _SupportsSynchronousAnext[_AwaitableT]) -> _AwaitableT: ... + def anext(i: _SupportsSynchronousAnext[_AwaitableT], /) -> _AwaitableT: ... @overload - async def anext(__i: SupportsAnext[_T], default: _VT) -> _T | _VT: ... + async def anext(i: SupportsAnext[_T], default: _VT, /) -> _T | _VT: ... # compile() returns a CodeType, unless the flags argument includes PyCF_ONLY_AST (=1024), # in which case it returns ast.AST. We have overloads for flag 0 (the default) and for # explicitly passing PyCF_ONLY_AST. We fall back to Any for other values of flags. -if sys.version_info >= (3, 8): - @overload - def compile( - source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, - filename: str | ReadableBuffer | _PathLike[Any], - mode: str, - flags: Literal[0], - dont_inherit: bool = False, - optimize: int = -1, - *, - _feature_version: int = -1, - ) -> CodeType: ... - @overload - def compile( - source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, - filename: str | ReadableBuffer | _PathLike[Any], - mode: str, - *, - dont_inherit: bool = False, - optimize: int = -1, - _feature_version: int = -1, - ) -> CodeType: ... - @overload - def compile( - source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, - filename: str | ReadableBuffer | _PathLike[Any], - mode: str, - flags: Literal[1024], - dont_inherit: bool = False, - optimize: int = -1, - *, - _feature_version: int = -1, - ) -> _ast.AST: ... - @overload - def compile( - source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, - filename: str | ReadableBuffer | _PathLike[Any], - mode: str, - flags: int, - dont_inherit: bool = False, - optimize: int = -1, - *, - _feature_version: int = -1, - ) -> Any: ... - -else: - @overload - def compile( - source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, - filename: str | ReadableBuffer | _PathLike[Any], - mode: str, - flags: Literal[0], - dont_inherit: bool = False, - optimize: int = -1, - ) -> CodeType: ... - @overload - def compile( - source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, - filename: str | ReadableBuffer | _PathLike[Any], - mode: str, - *, - dont_inherit: bool = False, - optimize: int = -1, - ) -> CodeType: ... - @overload - def compile( - source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, - filename: str | ReadableBuffer | _PathLike[Any], - mode: str, - flags: Literal[1024], - dont_inherit: bool = False, - optimize: int = -1, - ) -> _ast.AST: ... - @overload - def compile( - source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, - filename: str | ReadableBuffer | _PathLike[Any], - mode: str, - flags: int, - dont_inherit: bool = False, - optimize: int = -1, - ) -> Any: ... - +@overload +def compile( + source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, + filename: str | ReadableBuffer | _PathLike[Any], + mode: str, + flags: Literal[0], + dont_inherit: bool = False, + optimize: int = -1, + *, + _feature_version: int = -1, +) -> CodeType: ... +@overload +def compile( + source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, + filename: str | ReadableBuffer | _PathLike[Any], + mode: str, + *, + dont_inherit: bool = False, + optimize: int = -1, + _feature_version: int = -1, +) -> CodeType: ... +@overload +def compile( + source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, + filename: str | ReadableBuffer | _PathLike[Any], + mode: str, + flags: Literal[1024], + dont_inherit: bool = False, + optimize: int = -1, + *, + _feature_version: int = -1, +) -> _ast.AST: ... +@overload +def compile( + source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, + filename: str | ReadableBuffer | _PathLike[Any], + mode: str, + flags: int, + dont_inherit: bool = False, + optimize: int = -1, + *, + _feature_version: int = -1, +) -> Any: ... def copyright() -> None: ... def credits() -> None: ... -def delattr(__obj: object, __name: str) -> None: ... -def dir(__o: object = ...) -> list[str]: ... +def delattr(obj: object, name: str, /) -> None: ... +def dir(o: object = ..., /) -> list[str]: ... @overload -def divmod(__x: SupportsDivMod[_T_contra, _T_co], __y: _T_contra) -> _T_co: ... +def divmod(x: SupportsDivMod[_T_contra, _T_co], y: _T_contra, /) -> _T_co: ... @overload -def divmod(__x: _T_contra, __y: SupportsRDivMod[_T_contra, _T_co]) -> _T_co: ... +def divmod(x: _T_contra, y: SupportsRDivMod[_T_contra, _T_co], /) -> _T_co: ... # The `globals` argument to `eval` has to be `dict[str, Any]` rather than `dict[str, object]` due to invariance. # (The `globals` argument has to be a "real dict", rather than any old mapping, unlike the `locals` argument.) def eval( - __source: str | ReadableBuffer | CodeType, - __globals: dict[str, Any] | None = None, - __locals: Mapping[str, object] | None = None, + source: str | ReadableBuffer | CodeType, globals: dict[str, Any] | None = None, locals: Mapping[str, object] | None = None, / ) -> Any: ... # Comment above regarding `eval` applies to `exec` as well if sys.version_info >= (3, 11): def exec( - __source: str | ReadableBuffer | CodeType, - __globals: dict[str, Any] | None = None, - __locals: Mapping[str, object] | None = None, + source: str | ReadableBuffer | CodeType, + globals: dict[str, Any] | None = None, + locals: Mapping[str, object] | None = None, + /, *, - closure: tuple[_Cell, ...] | None = None, + closure: tuple[CellType, ...] | None = None, ) -> None: ... else: def exec( - __source: str | ReadableBuffer | CodeType, - __globals: dict[str, Any] | None = None, - __locals: Mapping[str, object] | None = None, + source: str | ReadableBuffer | CodeType, + globals: dict[str, Any] | None = None, + locals: Mapping[str, object] | None = None, + /, ) -> None: ... def exit(code: sys._ExitCode = None) -> NoReturn: ... -class filter(Iterator[_T], Generic[_T]): +class filter(Iterator[_T]): + @overload + def __new__(cls, function: None, iterable: Iterable[_T | None], /) -> Self: ... @overload - def __init__(self, __function: None, __iterable: Iterable[_T | None]) -> None: ... + def __new__(cls, function: Callable[[_S], TypeGuard[_T]], iterable: Iterable[_S], /) -> Self: ... @overload - def __init__(self, __function: Callable[[_S], TypeGuard[_T]], __iterable: Iterable[_S]) -> None: ... + def __new__(cls, function: Callable[[_S], TypeIs[_T]], iterable: Iterable[_S], /) -> Self: ... @overload - def __init__(self, __function: Callable[[_T], Any], __iterable: Iterable[_T]) -> None: ... + def __new__(cls, function: Callable[[_T], Any], iterable: Iterable[_T], /) -> Self: ... def __iter__(self) -> Self: ... def __next__(self) -> _T: ... -def format(__value: object, __format_spec: str = "") -> str: ... +def format(value: object, format_spec: str = "", /) -> str: ... @overload -def getattr(__o: object, __name: str) -> Any: ... +def getattr(o: object, name: str, /) -> Any: ... # While technically covered by the last overload, spelling out the types for None, bool # and basic containers help mypy out in some tricky situations involving type context # (aka bidirectional inference) @overload -def getattr(__o: object, __name: str, __default: None) -> Any | None: ... +def getattr(o: object, name: str, default: None, /) -> Any | None: ... @overload -def getattr(__o: object, __name: str, __default: bool) -> Any | bool: ... +def getattr(o: object, name: str, default: bool, /) -> Any | bool: ... @overload -def getattr(__o: object, name: str, __default: list[Any]) -> Any | list[Any]: ... +def getattr(o: object, name: str, default: list[Any], /) -> Any | list[Any]: ... @overload -def getattr(__o: object, name: str, __default: dict[Any, Any]) -> Any | dict[Any, Any]: ... +def getattr(o: object, name: str, default: dict[Any, Any], /) -> Any | dict[Any, Any]: ... @overload -def getattr(__o: object, __name: str, __default: _T) -> Any | _T: ... +def getattr(o: object, name: str, default: _T, /) -> Any | _T: ... def globals() -> dict[str, Any]: ... -def hasattr(__obj: object, __name: str) -> bool: ... -def hash(__obj: object) -> int: ... +def hasattr(obj: object, name: str, /) -> bool: ... +def hash(obj: object, /) -> int: ... def help(request: object = ...) -> None: ... -def hex(__number: int | SupportsIndex) -> str: ... -def id(__obj: object) -> int: ... -def input(__prompt: object = "") -> str: ... +def hex(number: int | SupportsIndex, /) -> str: ... +def id(obj: object, /) -> int: ... +def input(prompt: object = "", /) -> str: ... class _GetItemIterable(Protocol[_T_co]): - def __getitem__(self, __i: int) -> _T_co: ... + def __getitem__(self, i: int, /) -> _T_co: ... @overload -def iter(__iterable: SupportsIter[_SupportsNextT]) -> _SupportsNextT: ... +def iter(object: SupportsIter[_SupportsNextT], /) -> _SupportsNextT: ... @overload -def iter(__iterable: _GetItemIterable[_T]) -> Iterator[_T]: ... +def iter(object: _GetItemIterable[_T], /) -> Iterator[_T]: ... @overload -def iter(__function: Callable[[], _T | None], __sentinel: None) -> Iterator[_T]: ... +def iter(object: Callable[[], _T | None], sentinel: None, /) -> Iterator[_T]: ... @overload -def iter(__function: Callable[[], _T], __sentinel: object) -> Iterator[_T]: ... +def iter(object: Callable[[], _T], sentinel: object, /) -> Iterator[_T]: ... +# Keep this alias in sync with unittest.case._ClassInfo if sys.version_info >= (3, 10): _ClassInfo: TypeAlias = type | types.UnionType | tuple[_ClassInfo, ...] else: _ClassInfo: TypeAlias = type | tuple[_ClassInfo, ...] -def isinstance(__obj: object, __class_or_tuple: _ClassInfo) -> bool: ... -def issubclass(__cls: type, __class_or_tuple: _ClassInfo) -> bool: ... -def len(__obj: Sized) -> int: ... +def isinstance(obj: object, class_or_tuple: _ClassInfo, /) -> bool: ... +def issubclass(cls: type, class_or_tuple: _ClassInfo, /) -> bool: ... +def len(obj: Sized, /) -> int: ... def license() -> None: ... def locals() -> dict[str, Any]: ... -class map(Iterator[_S], Generic[_S]): +class map(Iterator[_S]): @overload - def __init__(self, __func: Callable[[_T1], _S], __iter1: Iterable[_T1]) -> None: ... + def __new__(cls, func: Callable[[_T1], _S], iter1: Iterable[_T1], /) -> Self: ... @overload - def __init__(self, __func: Callable[[_T1, _T2], _S], __iter1: Iterable[_T1], __iter2: Iterable[_T2]) -> None: ... + def __new__(cls, func: Callable[[_T1, _T2], _S], iter1: Iterable[_T1], iter2: Iterable[_T2], /) -> Self: ... @overload - def __init__( - self, __func: Callable[[_T1, _T2, _T3], _S], __iter1: Iterable[_T1], __iter2: Iterable[_T2], __iter3: Iterable[_T3] - ) -> None: ... + def __new__( + cls, func: Callable[[_T1, _T2, _T3], _S], iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], / + ) -> Self: ... @overload - def __init__( - self, - __func: Callable[[_T1, _T2, _T3, _T4], _S], - __iter1: Iterable[_T1], - __iter2: Iterable[_T2], - __iter3: Iterable[_T3], - __iter4: Iterable[_T4], - ) -> None: ... + def __new__( + cls, + func: Callable[[_T1, _T2, _T3, _T4], _S], + iter1: Iterable[_T1], + iter2: Iterable[_T2], + iter3: Iterable[_T3], + iter4: Iterable[_T4], + /, + ) -> Self: ... @overload - def __init__( - self, - __func: Callable[[_T1, _T2, _T3, _T4, _T5], _S], - __iter1: Iterable[_T1], - __iter2: Iterable[_T2], - __iter3: Iterable[_T3], - __iter4: Iterable[_T4], - __iter5: Iterable[_T5], - ) -> None: ... + def __new__( + cls, + func: Callable[[_T1, _T2, _T3, _T4, _T5], _S], + iter1: Iterable[_T1], + iter2: Iterable[_T2], + iter3: Iterable[_T3], + iter4: Iterable[_T4], + iter5: Iterable[_T5], + /, + ) -> Self: ... @overload - def __init__( - self, - __func: Callable[..., _S], - __iter1: Iterable[Any], - __iter2: Iterable[Any], - __iter3: Iterable[Any], - __iter4: Iterable[Any], - __iter5: Iterable[Any], - __iter6: Iterable[Any], + def __new__( + cls, + func: Callable[..., _S], + iter1: Iterable[Any], + iter2: Iterable[Any], + iter3: Iterable[Any], + iter4: Iterable[Any], + iter5: Iterable[Any], + iter6: Iterable[Any], + /, *iterables: Iterable[Any], - ) -> None: ... + ) -> Self: ... def __iter__(self) -> Self: ... def __next__(self) -> _S: ... @overload def max( - __arg1: SupportsRichComparisonT, __arg2: SupportsRichComparisonT, *_args: SupportsRichComparisonT, key: None = None + arg1: SupportsRichComparisonT, arg2: SupportsRichComparisonT, /, *_args: SupportsRichComparisonT, key: None = None ) -> SupportsRichComparisonT: ... @overload -def max(__arg1: _T, __arg2: _T, *_args: _T, key: Callable[[_T], SupportsRichComparison]) -> _T: ... +def max(arg1: _T, arg2: _T, /, *_args: _T, key: Callable[[_T], SupportsRichComparison]) -> _T: ... @overload -def max(__iterable: Iterable[SupportsRichComparisonT], *, key: None = None) -> SupportsRichComparisonT: ... +def max(iterable: Iterable[SupportsRichComparisonT], /, *, key: None = None) -> SupportsRichComparisonT: ... @overload -def max(__iterable: Iterable[_T], *, key: Callable[[_T], SupportsRichComparison]) -> _T: ... +def max(iterable: Iterable[_T], /, *, key: Callable[[_T], SupportsRichComparison]) -> _T: ... @overload -def max(__iterable: Iterable[SupportsRichComparisonT], *, key: None = None, default: _T) -> SupportsRichComparisonT | _T: ... +def max(iterable: Iterable[SupportsRichComparisonT], /, *, key: None = None, default: _T) -> SupportsRichComparisonT | _T: ... @overload -def max(__iterable: Iterable[_T1], *, key: Callable[[_T1], SupportsRichComparison], default: _T2) -> _T1 | _T2: ... +def max(iterable: Iterable[_T1], /, *, key: Callable[[_T1], SupportsRichComparison], default: _T2) -> _T1 | _T2: ... @overload def min( - __arg1: SupportsRichComparisonT, __arg2: SupportsRichComparisonT, *_args: SupportsRichComparisonT, key: None = None + arg1: SupportsRichComparisonT, arg2: SupportsRichComparisonT, /, *_args: SupportsRichComparisonT, key: None = None ) -> SupportsRichComparisonT: ... @overload -def min(__arg1: _T, __arg2: _T, *_args: _T, key: Callable[[_T], SupportsRichComparison]) -> _T: ... +def min(arg1: _T, arg2: _T, /, *_args: _T, key: Callable[[_T], SupportsRichComparison]) -> _T: ... @overload -def min(__iterable: Iterable[SupportsRichComparisonT], *, key: None = None) -> SupportsRichComparisonT: ... +def min(iterable: Iterable[SupportsRichComparisonT], /, *, key: None = None) -> SupportsRichComparisonT: ... @overload -def min(__iterable: Iterable[_T], *, key: Callable[[_T], SupportsRichComparison]) -> _T: ... +def min(iterable: Iterable[_T], /, *, key: Callable[[_T], SupportsRichComparison]) -> _T: ... @overload -def min(__iterable: Iterable[SupportsRichComparisonT], *, key: None = None, default: _T) -> SupportsRichComparisonT | _T: ... +def min(iterable: Iterable[SupportsRichComparisonT], /, *, key: None = None, default: _T) -> SupportsRichComparisonT | _T: ... @overload -def min(__iterable: Iterable[_T1], *, key: Callable[[_T1], SupportsRichComparison], default: _T2) -> _T1 | _T2: ... +def min(iterable: Iterable[_T1], /, *, key: Callable[[_T1], SupportsRichComparison], default: _T2) -> _T1 | _T2: ... @overload -def next(__i: SupportsNext[_T]) -> _T: ... +def next(i: SupportsNext[_T], /) -> _T: ... @overload -def next(__i: SupportsNext[_T], __default: _VT) -> _T | _VT: ... -def oct(__number: int | SupportsIndex) -> str: ... +def next(i: SupportsNext[_T], default: _VT, /) -> _T | _VT: ... +def oct(number: int | SupportsIndex, /) -> str: ... _Opener: TypeAlias = Callable[[str, int], int] @@ -1478,10 +1487,9 @@ def open( closefd: bool = True, opener: _Opener | None = None, ) -> IO[Any]: ... -def ord(__c: str | bytes | bytearray) -> int: ... +def ord(c: str | bytes | bytearray, /) -> int: ... -class _SupportsWriteAndFlush(SupportsWrite[_T_contra], Protocol[_T_contra]): - def flush(self) -> None: ... +class _SupportsWriteAndFlush(SupportsWrite[_T_contra], SupportsFlush, Protocol[_T_contra]): ... @overload def print( @@ -1500,101 +1508,69 @@ _E = TypeVar("_E", contravariant=True) _M = TypeVar("_M", contravariant=True) class _SupportsPow2(Protocol[_E, _T_co]): - def __pow__(self, __other: _E) -> _T_co: ... + def __pow__(self, other: _E, /) -> _T_co: ... class _SupportsPow3NoneOnly(Protocol[_E, _T_co]): - def __pow__(self, __other: _E, __modulo: None = None) -> _T_co: ... + def __pow__(self, other: _E, modulo: None = None, /) -> _T_co: ... class _SupportsPow3(Protocol[_E, _M, _T_co]): - def __pow__(self, __other: _E, __modulo: _M) -> _T_co: ... + def __pow__(self, other: _E, modulo: _M, /) -> _T_co: ... _SupportsSomeKindOfPow = ( # noqa: Y026 # TODO: Use TypeAlias once mypy bugs are fixed _SupportsPow2[Any, Any] | _SupportsPow3NoneOnly[Any, Any] | _SupportsPow3[Any, Any, Any] ) -if sys.version_info >= (3, 8): - # TODO: `pow(int, int, Literal[0])` fails at runtime, - # but adding a `NoReturn` overload isn't a good solution for expressing that (see #8566). - @overload - def pow(base: int, exp: int, mod: int) -> int: ... - @overload - def pow(base: int, exp: Literal[0], mod: None = None) -> Literal[1]: ... # type: ignore[misc] - @overload - def pow(base: int, exp: _PositiveInteger, mod: None = None) -> int: ... # type: ignore[misc] - @overload - def pow(base: int, exp: _NegativeInteger, mod: None = None) -> float: ... # type: ignore[misc] - # int base & positive-int exp -> int; int base & negative-int exp -> float - # return type must be Any as `int | float` causes too many false-positive errors - @overload - def pow(base: int, exp: int, mod: None = None) -> Any: ... - @overload - def pow(base: _PositiveInteger, exp: float, mod: None = None) -> float: ... - @overload - def pow(base: _NegativeInteger, exp: float, mod: None = None) -> complex: ... - @overload - def pow(base: float, exp: int, mod: None = None) -> float: ... - # float base & float exp could return float or complex - # return type must be Any (same as complex base, complex exp), - # as `float | complex` causes too many false-positive errors - @overload - def pow(base: float, exp: complex | _SupportsSomeKindOfPow, mod: None = None) -> Any: ... - @overload - def pow(base: complex, exp: complex | _SupportsSomeKindOfPow, mod: None = None) -> complex: ... - @overload - def pow(base: _SupportsPow2[_E, _T_co], exp: _E, mod: None = None) -> _T_co: ... - @overload - def pow(base: _SupportsPow3NoneOnly[_E, _T_co], exp: _E, mod: None = None) -> _T_co: ... - @overload - def pow(base: _SupportsPow3[_E, _M, _T_co], exp: _E, mod: _M) -> _T_co: ... - @overload - def pow(base: _SupportsSomeKindOfPow, exp: float, mod: None = None) -> Any: ... - @overload - def pow(base: _SupportsSomeKindOfPow, exp: complex, mod: None = None) -> complex: ... +# TODO: `pow(int, int, Literal[0])` fails at runtime, +# but adding a `NoReturn` overload isn't a good solution for expressing that (see #8566). +@overload +def pow(base: int, exp: int, mod: int) -> int: ... +@overload +def pow(base: int, exp: Literal[0], mod: None = None) -> Literal[1]: ... +@overload +def pow(base: int, exp: _PositiveInteger, mod: None = None) -> int: ... +@overload +def pow(base: int, exp: _NegativeInteger, mod: None = None) -> float: ... -else: - @overload - def pow(__base: int, __exp: int, __mod: int) -> int: ... - @overload - def pow(__base: int, __exp: Literal[0], __mod: None = None) -> Literal[1]: ... # type: ignore[misc] - @overload - def pow(__base: int, __exp: _PositiveInteger, __mod: None = None) -> int: ... # type: ignore[misc] - @overload - def pow(__base: int, __exp: _NegativeInteger, __mod: None = None) -> float: ... # type: ignore[misc] - @overload - def pow(__base: int, __exp: int, __mod: None = None) -> Any: ... - @overload - def pow(__base: _PositiveInteger, __exp: float, __mod: None = None) -> float: ... - @overload - def pow(__base: _NegativeInteger, __exp: float, __mod: None = None) -> complex: ... - @overload - def pow(__base: float, __exp: int, __mod: None = None) -> float: ... - @overload - def pow(__base: float, __exp: complex | _SupportsSomeKindOfPow, __mod: None = None) -> Any: ... - @overload - def pow(__base: complex, __exp: complex | _SupportsSomeKindOfPow, __mod: None = None) -> complex: ... - @overload - def pow(__base: _SupportsPow2[_E, _T_co], __exp: _E, __mod: None = None) -> _T_co: ... - @overload - def pow(__base: _SupportsPow3NoneOnly[_E, _T_co], __exp: _E, __mod: None = None) -> _T_co: ... - @overload - def pow(__base: _SupportsPow3[_E, _M, _T_co], __exp: _E, __mod: _M) -> _T_co: ... - @overload - def pow(__base: _SupportsSomeKindOfPow, __exp: float, __mod: None = None) -> Any: ... - @overload - def pow(__base: _SupportsSomeKindOfPow, __exp: complex, __mod: None = None) -> complex: ... +# int base & positive-int exp -> int; int base & negative-int exp -> float +# return type must be Any as `int | float` causes too many false-positive errors +@overload +def pow(base: int, exp: int, mod: None = None) -> Any: ... +@overload +def pow(base: _PositiveInteger, exp: float, mod: None = None) -> float: ... +@overload +def pow(base: _NegativeInteger, exp: float, mod: None = None) -> complex: ... +@overload +def pow(base: float, exp: int, mod: None = None) -> float: ... +# float base & float exp could return float or complex +# return type must be Any (same as complex base, complex exp), +# as `float | complex` causes too many false-positive errors +@overload +def pow(base: float, exp: complex | _SupportsSomeKindOfPow, mod: None = None) -> Any: ... +@overload +def pow(base: complex, exp: complex | _SupportsSomeKindOfPow, mod: None = None) -> complex: ... +@overload +def pow(base: _SupportsPow2[_E, _T_co], exp: _E, mod: None = None) -> _T_co: ... +@overload +def pow(base: _SupportsPow3NoneOnly[_E, _T_co], exp: _E, mod: None = None) -> _T_co: ... +@overload +def pow(base: _SupportsPow3[_E, _M, _T_co], exp: _E, mod: _M) -> _T_co: ... +@overload +def pow(base: _SupportsSomeKindOfPow, exp: float, mod: None = None) -> Any: ... +@overload +def pow(base: _SupportsSomeKindOfPow, exp: complex, mod: None = None) -> complex: ... def quit(code: sys._ExitCode = None) -> NoReturn: ... -class reversed(Iterator[_T], Generic[_T]): +class reversed(Iterator[_T]): @overload - def __init__(self, __sequence: Reversible[_T]) -> None: ... + def __new__(cls, sequence: Reversible[_T], /) -> Iterator[_T]: ... # type: ignore[misc] @overload - def __init__(self, __sequence: SupportsLenAndGetItem[_T]) -> None: ... + def __new__(cls, sequence: SupportsLenAndGetItem[_T], /) -> Iterator[_T]: ... # type: ignore[misc] def __iter__(self) -> Self: ... def __next__(self) -> _T: ... def __length_hint__(self) -> int: ... -def repr(__obj: object) -> str: ... +def repr(obj: object, /) -> str: ... # See https://github.com/python/typeshed/pull/9141 # and https://github.com/python/typeshed/pull/9151 @@ -1604,7 +1580,7 @@ class _SupportsRound1(Protocol[_T_co]): def __round__(self) -> _T_co: ... class _SupportsRound2(Protocol[_T_co]): - def __round__(self, __ndigits: int) -> _T_co: ... + def __round__(self, ndigits: int, /) -> _T_co: ... @overload def round(number: _SupportsRound1[_T], ndigits: None = None) -> _T: ... @@ -1613,13 +1589,13 @@ def round(number: _SupportsRound2[_T], ndigits: SupportsIndex) -> _T: ... # See https://github.com/python/typeshed/pull/6292#discussion_r748875189 # for why arg 3 of `setattr` should be annotated with `Any` and not `object` -def setattr(__obj: object, __name: str, __value: Any) -> None: ... +def setattr(obj: object, name: str, value: Any, /) -> None: ... @overload def sorted( - __iterable: Iterable[SupportsRichComparisonT], *, key: None = None, reverse: bool = False + iterable: Iterable[SupportsRichComparisonT], /, *, key: None = None, reverse: bool = False ) -> list[SupportsRichComparisonT]: ... @overload -def sorted(__iterable: Iterable[_T], *, key: Callable[[_T], SupportsRichComparison], reverse: bool = False) -> list[_T]: ... +def sorted(iterable: Iterable[_T], /, *, key: Callable[[_T], SupportsRichComparison], reverse: bool = False) -> list[_T]: ... _AddableT1 = TypeVar("_AddableT1", bound=SupportsAdd[Any, Any]) _AddableT2 = TypeVar("_AddableT2", bound=SupportsAdd[Any, Any]) @@ -1632,105 +1608,89 @@ _SupportsSumNoDefaultT = TypeVar("_SupportsSumNoDefaultT", bound=_SupportsSumWit # However, we can't express that in the stub for `sum()` # without creating many false-positive errors (see #7578). # Instead, we special-case the most common examples of this: bool and literal integers. -if sys.version_info >= (3, 8): - @overload - def sum(__iterable: Iterable[bool], start: int = 0) -> int: ... # type: ignore[misc] - -else: - @overload - def sum(__iterable: Iterable[bool], __start: int = 0) -> int: ... # type: ignore[misc] - @overload -def sum(__iterable: Iterable[_SupportsSumNoDefaultT]) -> _SupportsSumNoDefaultT | Literal[0]: ... - -if sys.version_info >= (3, 8): - @overload - def sum(__iterable: Iterable[_AddableT1], start: _AddableT2) -> _AddableT1 | _AddableT2: ... - -else: - @overload - def sum(__iterable: Iterable[_AddableT1], __start: _AddableT2) -> _AddableT1 | _AddableT2: ... +def sum(iterable: Iterable[bool], /, start: int = 0) -> int: ... # type: ignore[overload-overlap] +@overload +def sum(iterable: Iterable[_SupportsSumNoDefaultT], /) -> _SupportsSumNoDefaultT | Literal[0]: ... +@overload +def sum(iterable: Iterable[_AddableT1], /, start: _AddableT2) -> _AddableT1 | _AddableT2: ... # The argument to `vars()` has to have a `__dict__` attribute, so the second overload can't be annotated with `object` # (A "SupportsDunderDict" protocol doesn't work) # Use a type: ignore to make complaints about overlapping overloads go away @overload -def vars(__object: type) -> types.MappingProxyType[str, Any]: ... # type: ignore[misc] +def vars(object: type, /) -> types.MappingProxyType[str, Any]: ... # type: ignore[overload-overlap] @overload -def vars(__object: Any = ...) -> dict[str, Any]: ... +def vars(object: Any = ..., /) -> dict[str, Any]: ... -class zip(Iterator[_T_co], Generic[_T_co]): +class zip(Iterator[_T_co]): if sys.version_info >= (3, 10): @overload - def __new__(cls, __iter1: Iterable[_T1], *, strict: bool = ...) -> zip[tuple[_T1]]: ... + def __new__(cls, *, strict: bool = ...) -> zip[Any]: ... + @overload + def __new__(cls, iter1: Iterable[_T1], /, *, strict: bool = ...) -> zip[tuple[_T1]]: ... @overload - def __new__(cls, __iter1: Iterable[_T1], __iter2: Iterable[_T2], *, strict: bool = ...) -> zip[tuple[_T1, _T2]]: ... + def __new__(cls, iter1: Iterable[_T1], iter2: Iterable[_T2], /, *, strict: bool = ...) -> zip[tuple[_T1, _T2]]: ... @overload def __new__( - cls, __iter1: Iterable[_T1], __iter2: Iterable[_T2], __iter3: Iterable[_T3], *, strict: bool = ... + cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], /, *, strict: bool = ... ) -> zip[tuple[_T1, _T2, _T3]]: ... @overload def __new__( - cls, - __iter1: Iterable[_T1], - __iter2: Iterable[_T2], - __iter3: Iterable[_T3], - __iter4: Iterable[_T4], - *, - strict: bool = ..., + cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], iter4: Iterable[_T4], /, *, strict: bool = ... ) -> zip[tuple[_T1, _T2, _T3, _T4]]: ... @overload def __new__( cls, - __iter1: Iterable[_T1], - __iter2: Iterable[_T2], - __iter3: Iterable[_T3], - __iter4: Iterable[_T4], - __iter5: Iterable[_T5], + iter1: Iterable[_T1], + iter2: Iterable[_T2], + iter3: Iterable[_T3], + iter4: Iterable[_T4], + iter5: Iterable[_T5], + /, *, strict: bool = ..., ) -> zip[tuple[_T1, _T2, _T3, _T4, _T5]]: ... @overload def __new__( cls, - __iter1: Iterable[Any], - __iter2: Iterable[Any], - __iter3: Iterable[Any], - __iter4: Iterable[Any], - __iter5: Iterable[Any], - __iter6: Iterable[Any], + iter1: Iterable[Any], + iter2: Iterable[Any], + iter3: Iterable[Any], + iter4: Iterable[Any], + iter5: Iterable[Any], + iter6: Iterable[Any], + /, *iterables: Iterable[Any], strict: bool = ..., ) -> zip[tuple[Any, ...]]: ... else: @overload - def __new__(cls, __iter1: Iterable[_T1]) -> zip[tuple[_T1]]: ... + def __new__(cls) -> zip[Any]: ... + @overload + def __new__(cls, iter1: Iterable[_T1], /) -> zip[tuple[_T1]]: ... @overload - def __new__(cls, __iter1: Iterable[_T1], __iter2: Iterable[_T2]) -> zip[tuple[_T1, _T2]]: ... + def __new__(cls, iter1: Iterable[_T1], iter2: Iterable[_T2], /) -> zip[tuple[_T1, _T2]]: ... @overload - def __new__(cls, __iter1: Iterable[_T1], __iter2: Iterable[_T2], __iter3: Iterable[_T3]) -> zip[tuple[_T1, _T2, _T3]]: ... + def __new__(cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], /) -> zip[tuple[_T1, _T2, _T3]]: ... @overload def __new__( - cls, __iter1: Iterable[_T1], __iter2: Iterable[_T2], __iter3: Iterable[_T3], __iter4: Iterable[_T4] + cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], iter4: Iterable[_T4], / ) -> zip[tuple[_T1, _T2, _T3, _T4]]: ... @overload def __new__( - cls, - __iter1: Iterable[_T1], - __iter2: Iterable[_T2], - __iter3: Iterable[_T3], - __iter4: Iterable[_T4], - __iter5: Iterable[_T5], + cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], iter4: Iterable[_T4], iter5: Iterable[_T5], / ) -> zip[tuple[_T1, _T2, _T3, _T4, _T5]]: ... @overload def __new__( cls, - __iter1: Iterable[Any], - __iter2: Iterable[Any], - __iter3: Iterable[Any], - __iter4: Iterable[Any], - __iter5: Iterable[Any], - __iter6: Iterable[Any], + iter1: Iterable[Any], + iter2: Iterable[Any], + iter3: Iterable[Any], + iter4: Iterable[Any], + iter5: Iterable[Any], + iter6: Iterable[Any], + /, *iterables: Iterable[Any], ) -> zip[tuple[Any, ...]]: ... @@ -1743,18 +1703,28 @@ def __import__( name: str, globals: Mapping[str, object] | None = None, locals: Mapping[str, object] | None = None, - fromlist: Sequence[str] = ..., + fromlist: Sequence[str] = (), level: int = 0, ) -> types.ModuleType: ... -def __build_class__(__func: Callable[[], _Cell | Any], __name: str, *bases: Any, metaclass: Any = ..., **kwds: Any) -> Any: ... +def __build_class__(func: Callable[[], CellType | Any], name: str, /, *bases: Any, metaclass: Any = ..., **kwds: Any) -> Any: ... -# Actually the type of Ellipsis is , but since it's -# not exposed anywhere under that name, we make it private here. -@final -@type_check_only -class ellipsis: ... +if sys.version_info >= (3, 10): + from types import EllipsisType + + # Backwards compatibility hack for folks who relied on the ellipsis type + # existing in typeshed in Python 3.9 and earlier. + ellipsis = EllipsisType + + Ellipsis: EllipsisType + +else: + # Actually the type of Ellipsis is , but since it's + # not exposed anywhere under that name, we make it private here. + @final + @type_check_only + class ellipsis: ... -Ellipsis: ellipsis + Ellipsis: ellipsis class BaseException: args: tuple[Any, ...] @@ -1763,12 +1733,12 @@ class BaseException: __suppress_context__: bool __traceback__: TracebackType | None def __init__(self, *args: object) -> None: ... - def __setstate__(self, __state: dict[str, Any] | None) -> None: ... - def with_traceback(self, __tb: TracebackType | None) -> Self: ... + def __setstate__(self, state: dict[str, Any] | None, /) -> None: ... + def with_traceback(self, tb: TracebackType | None, /) -> Self: ... if sys.version_info >= (3, 11): # only present after add_note() is called __notes__: list[str] - def add_note(self, __note: str) -> None: ... + def add_note(self, note: str, /) -> None: ... class GeneratorExit(BaseException): ... class KeyboardInterrupt(BaseException): ... @@ -1812,12 +1782,15 @@ class ImportError(Exception): name: str | None path: str | None msg: str # undocumented + if sys.version_info >= (3, 12): + name_from: str | None # undocumented class LookupError(Exception): ... class MemoryError(Exception): ... class NameError(Exception): if sys.version_info >= (3, 10): + def __init__(self, *args: object, name: str | None = ...) -> None: ... name: str class ReferenceError(Exception): ... @@ -1876,7 +1849,7 @@ class UnicodeDecodeError(UnicodeError): start: int end: int reason: str - def __init__(self, __encoding: str, __object: ReadableBuffer, __start: int, __end: int, __reason: str) -> None: ... + def __init__(self, encoding: str, object: ReadableBuffer, start: int, end: int, reason: str, /) -> None: ... class UnicodeEncodeError(UnicodeError): encoding: str @@ -1884,7 +1857,7 @@ class UnicodeEncodeError(UnicodeError): start: int end: int reason: str - def __init__(self, __encoding: str, __object: str, __start: int, __end: int, __reason: str) -> None: ... + def __init__(self, encoding: str, object: str, start: int, end: int, reason: str, /) -> None: ... class UnicodeTranslateError(UnicodeError): encoding: None @@ -1892,7 +1865,7 @@ class UnicodeTranslateError(UnicodeError): start: int end: int reason: str - def __init__(self, __object: str, __start: int, __end: int, __reason: str) -> None: ... + def __init__(self, object: str, start: int, end: int, reason: str, /) -> None: ... class Warning(Exception): ... class UserWarning(Warning): ... @@ -1917,60 +1890,60 @@ if sys.version_info >= (3, 11): # See `check_exception_group.py` for use-cases and comments. class BaseExceptionGroup(BaseException, Generic[_BaseExceptionT_co]): - def __new__(cls, __message: str, __exceptions: Sequence[_BaseExceptionT_co]) -> Self: ... - def __init__(self, __message: str, __exceptions: Sequence[_BaseExceptionT_co]) -> None: ... + def __new__(cls, message: str, exceptions: Sequence[_BaseExceptionT_co], /) -> Self: ... + def __init__(self, message: str, exceptions: Sequence[_BaseExceptionT_co], /) -> None: ... @property def message(self) -> str: ... @property def exceptions(self) -> tuple[_BaseExceptionT_co | BaseExceptionGroup[_BaseExceptionT_co], ...]: ... @overload def subgroup( - self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...] + self, condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...], / ) -> ExceptionGroup[_ExceptionT] | None: ... @overload def subgroup( - self, __condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...] + self, condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...], / ) -> BaseExceptionGroup[_BaseExceptionT] | None: ... @overload def subgroup( - self, __condition: Callable[[_BaseExceptionT_co | Self], bool] + self, condition: Callable[[_BaseExceptionT_co | Self], bool], / ) -> BaseExceptionGroup[_BaseExceptionT_co] | None: ... @overload def split( - self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...] + self, condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...], / ) -> tuple[ExceptionGroup[_ExceptionT] | None, BaseExceptionGroup[_BaseExceptionT_co] | None]: ... @overload def split( - self, __condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...] + self, condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...], / ) -> tuple[BaseExceptionGroup[_BaseExceptionT] | None, BaseExceptionGroup[_BaseExceptionT_co] | None]: ... @overload def split( - self, __condition: Callable[[_BaseExceptionT_co | Self], bool] + self, condition: Callable[[_BaseExceptionT_co | Self], bool], / ) -> tuple[BaseExceptionGroup[_BaseExceptionT_co] | None, BaseExceptionGroup[_BaseExceptionT_co] | None]: ... # In reality it is `NonEmptySequence`: @overload - def derive(self, __excs: Sequence[_ExceptionT]) -> ExceptionGroup[_ExceptionT]: ... + def derive(self, excs: Sequence[_ExceptionT], /) -> ExceptionGroup[_ExceptionT]: ... @overload - def derive(self, __excs: Sequence[_BaseExceptionT]) -> BaseExceptionGroup[_BaseExceptionT]: ... - def __class_getitem__(cls, __item: Any) -> GenericAlias: ... + def derive(self, excs: Sequence[_BaseExceptionT], /) -> BaseExceptionGroup[_BaseExceptionT]: ... + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... class ExceptionGroup(BaseExceptionGroup[_ExceptionT_co], Exception): - def __new__(cls, __message: str, __exceptions: Sequence[_ExceptionT_co]) -> Self: ... - def __init__(self, __message: str, __exceptions: Sequence[_ExceptionT_co]) -> None: ... + def __new__(cls, message: str, exceptions: Sequence[_ExceptionT_co], /) -> Self: ... + def __init__(self, message: str, exceptions: Sequence[_ExceptionT_co], /) -> None: ... @property def exceptions(self) -> tuple[_ExceptionT_co | ExceptionGroup[_ExceptionT_co], ...]: ... # We accept a narrower type, but that's OK. @overload # type: ignore[override] def subgroup( - self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...] + self, condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...], / ) -> ExceptionGroup[_ExceptionT] | None: ... @overload - def subgroup(self, __condition: Callable[[_ExceptionT_co | Self], bool]) -> ExceptionGroup[_ExceptionT_co] | None: ... + def subgroup(self, condition: Callable[[_ExceptionT_co | Self], bool], /) -> ExceptionGroup[_ExceptionT_co] | None: ... @overload # type: ignore[override] def split( - self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...] + self, condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...], / ) -> tuple[ExceptionGroup[_ExceptionT] | None, ExceptionGroup[_ExceptionT_co] | None]: ... @overload def split( - self, __condition: Callable[[_ExceptionT_co | Self], bool] + self, condition: Callable[[_ExceptionT_co | Self], bool], / ) -> tuple[ExceptionGroup[_ExceptionT_co] | None, ExceptionGroup[_ExceptionT_co] | None]: ... diff --git a/mypy/typeshed/stdlib/bz2.pyi b/mypy/typeshed/stdlib/bz2.pyi index 9ad80ee6f731..a7837e1b9ff8 100644 --- a/mypy/typeshed/stdlib/bz2.pyi +++ b/mypy/typeshed/stdlib/bz2.pyi @@ -3,8 +3,8 @@ import sys from _compression import BaseStream from _typeshed import ReadableBuffer, StrOrBytesPath, WriteableBuffer from collections.abc import Iterable -from typing import IO, Any, Protocol, TextIO, overload -from typing_extensions import Literal, Self, SupportsIndex, TypeAlias, final +from typing import IO, Any, Literal, Protocol, SupportsIndex, TextIO, final, overload +from typing_extensions import Self, TypeAlias __all__ = ["BZ2File", "BZ2Compressor", "BZ2Decompressor", "open", "compress", "decompress"] @@ -14,7 +14,7 @@ __all__ = ["BZ2File", "BZ2Compressor", "BZ2Decompressor", "open", "compress", "d class _ReadableFileobj(_compression._Reader, Protocol): ... class _WritableFileobj(Protocol): - def write(self, __b: bytes) -> object: ... + def write(self, b: bytes, /) -> object: ... # The following attributes and methods are optional: # def fileno(self) -> int: ... # def close(self) -> object: ... @@ -132,7 +132,7 @@ class BZ2File(BaseStream, IO[bytes]): @final class BZ2Compressor: def __init__(self, compresslevel: int = ...) -> None: ... - def compress(self, __data: ReadableBuffer) -> bytes: ... + def compress(self, data: ReadableBuffer, /) -> bytes: ... def flush(self) -> bytes: ... @final diff --git a/mypy/typeshed/stdlib/cProfile.pyi b/mypy/typeshed/stdlib/cProfile.pyi index 8945b21427ab..0cf6e34ec99e 100644 --- a/mypy/typeshed/stdlib/cProfile.pyi +++ b/mypy/typeshed/stdlib/cProfile.pyi @@ -1,4 +1,4 @@ -import sys +import _lsprof from _typeshed import StrOrBytesPath, Unused from collections.abc import Callable from types import CodeType @@ -16,22 +16,16 @@ _T = TypeVar("_T") _P = ParamSpec("_P") _Label: TypeAlias = tuple[str, int, str] -class Profile: +class Profile(_lsprof.Profiler): stats: dict[_Label, tuple[int, int, int, int, dict[_Label, tuple[int, int, int, int]]]] # undocumented - def __init__( - self, timer: Callable[[], float] = ..., timeunit: float = ..., subcalls: bool = ..., builtins: bool = ... - ) -> None: ... - def enable(self) -> None: ... - def disable(self) -> None: ... def print_stats(self, sort: str | int = -1) -> None: ... def dump_stats(self, file: StrOrBytesPath) -> None: ... def create_stats(self) -> None: ... def snapshot_stats(self) -> None: ... def run(self, cmd: str) -> Self: ... def runctx(self, cmd: str, globals: dict[str, Any], locals: dict[str, Any]) -> Self: ... - def runcall(self, __func: Callable[_P, _T], *args: _P.args, **kw: _P.kwargs) -> _T: ... - if sys.version_info >= (3, 8): - def __enter__(self) -> Self: ... - def __exit__(self, *exc_info: Unused) -> None: ... + def runcall(self, func: Callable[_P, _T], /, *args: _P.args, **kw: _P.kwargs) -> _T: ... + def __enter__(self) -> Self: ... + def __exit__(self, *exc_info: Unused) -> None: ... def label(code: str | CodeType) -> _Label: ... # undocumented diff --git a/mypy/typeshed/stdlib/calendar.pyi b/mypy/typeshed/stdlib/calendar.pyi index 255a12d3348a..5cc49e102fdf 100644 --- a/mypy/typeshed/stdlib/calendar.pyi +++ b/mypy/typeshed/stdlib/calendar.pyi @@ -1,10 +1,11 @@ import datetime +import enum import sys from _typeshed import Unused from collections.abc import Iterable, Sequence from time import struct_time -from typing import ClassVar -from typing_extensions import Literal, TypeAlias +from typing import ClassVar, Literal +from typing_extensions import TypeAlias __all__ = [ "IllegalMonthError", @@ -35,6 +36,23 @@ __all__ = [ if sys.version_info >= (3, 10): __all__ += ["FRIDAY", "MONDAY", "SATURDAY", "SUNDAY", "THURSDAY", "TUESDAY", "WEDNESDAY"] +if sys.version_info >= (3, 12): + __all__ += [ + "Day", + "Month", + "JANUARY", + "FEBRUARY", + "MARCH", + "APRIL", + "MAY", + "JUNE", + "JULY", + "AUGUST", + "SEPTEMBER", + "OCTOBER", + "NOVEMBER", + "DECEMBER", + ] _LocaleType: TypeAlias = tuple[str | None, str | None] @@ -106,7 +124,7 @@ class HTMLCalendar(Calendar): def formatyear(self, theyear: int, width: int = 3) -> str: ... def formatyearpage( self, theyear: int, width: int = 3, css: str | None = "calendar.css", encoding: str | None = None - ) -> str: ... + ) -> bytes: ... class different_locale: def __init__(self, locale: _LocaleType) -> None: ... @@ -134,12 +152,57 @@ day_abbr: Sequence[str] month_name: Sequence[str] month_abbr: Sequence[str] -MONDAY: Literal[0] -TUESDAY: Literal[1] -WEDNESDAY: Literal[2] -THURSDAY: Literal[3] -FRIDAY: Literal[4] -SATURDAY: Literal[5] -SUNDAY: Literal[6] +if sys.version_info >= (3, 12): + class Month(enum.IntEnum): + JANUARY: Literal[1] + FEBRUARY: Literal[2] + MARCH: Literal[3] + APRIL: Literal[4] + MAY: Literal[5] + JUNE: Literal[6] + JULY: Literal[7] + AUGUST: Literal[8] + SEPTEMBER: Literal[9] + OCTOBER: Literal[10] + NOVEMBER: Literal[11] + DECEMBER: Literal[12] + + JANUARY = Month.JANUARY + FEBRUARY = Month.FEBRUARY + MARCH = Month.MARCH + APRIL = Month.APRIL + MAY = Month.MAY + JUNE = Month.JUNE + JULY = Month.JULY + AUGUST = Month.AUGUST + SEPTEMBER = Month.SEPTEMBER + OCTOBER = Month.OCTOBER + NOVEMBER = Month.NOVEMBER + DECEMBER = Month.DECEMBER + + class Day(enum.IntEnum): + MONDAY: Literal[0] + TUESDAY: Literal[1] + WEDNESDAY: Literal[2] + THURSDAY: Literal[3] + FRIDAY: Literal[4] + SATURDAY: Literal[5] + SUNDAY: Literal[6] + + MONDAY = Day.MONDAY + TUESDAY = Day.TUESDAY + WEDNESDAY = Day.WEDNESDAY + THURSDAY = Day.THURSDAY + FRIDAY = Day.FRIDAY + SATURDAY = Day.SATURDAY + SUNDAY = Day.SUNDAY +else: + MONDAY: Literal[0] + TUESDAY: Literal[1] + WEDNESDAY: Literal[2] + THURSDAY: Literal[3] + FRIDAY: Literal[4] + SATURDAY: Literal[5] + SUNDAY: Literal[6] EPOCH: Literal[1970] diff --git a/mypy/typeshed/stdlib/cgi.pyi b/mypy/typeshed/stdlib/cgi.pyi index a2acfa92d463..3a2e2a91b241 100644 --- a/mypy/typeshed/stdlib/cgi.pyi +++ b/mypy/typeshed/stdlib/cgi.pyi @@ -1,5 +1,4 @@ -import sys -from _typeshed import SupportsGetItem, SupportsItemAccess, Unused +from _typeshed import SupportsContainsAndGetItem, SupportsGetItem, SupportsItemAccess, Unused from builtins import list as _list, type as _type from collections.abc import Iterable, Iterator, Mapping from email.message import Message @@ -22,9 +21,6 @@ __all__ = [ "print_environ_usage", ] -if sys.version_info < (3, 8): - __all__ += ["parse_qs", "parse_qsl", "escape"] - def parse( fp: IO[Any] | None = None, environ: SupportsItemAccess[str, str] = ..., @@ -32,17 +28,12 @@ def parse( strict_parsing: bool = ..., separator: str = "&", ) -> dict[str, list[str]]: ... - -if sys.version_info < (3, 8): - def parse_qs(qs: str, keep_blank_values: bool = ..., strict_parsing: bool = ...) -> dict[str, list[str]]: ... - def parse_qsl(qs: str, keep_blank_values: bool = ..., strict_parsing: bool = ...) -> list[tuple[str, str]]: ... - def parse_multipart( fp: IO[Any], pdict: SupportsGetItem[str, bytes], encoding: str = "utf-8", errors: str = "replace", separator: str = "&" ) -> dict[str, list[Any]]: ... class _Environ(Protocol): - def __getitem__(self, __k: str) -> str: ... + def __getitem__(self, k: str, /) -> str: ... def keys(self) -> Iterable[str]: ... def parse_header(line: str) -> tuple[str, dict[str, str]]: ... @@ -52,9 +43,6 @@ def print_form(form: dict[str, Any]) -> None: ... def print_directory() -> None: ... def print_environ_usage() -> None: ... -if sys.version_info < (3, 8): - def escape(s: str, quote: bool | None = None) -> str: ... - class MiniFieldStorage: # The first five "Any" attributes here are always None, but mypy doesn't support that filename: Any @@ -97,7 +85,7 @@ class FieldStorage: fp: IO[Any] | None = None, headers: Mapping[str, str] | Message | None = None, outerboundary: bytes = b"", - environ: SupportsGetItem[str, str] = ..., + environ: SupportsContainsAndGetItem[str, str] = ..., keep_blank_values: int = 0, strict_parsing: int = 0, limit: int | None = None, @@ -117,7 +105,8 @@ class FieldStorage: def __contains__(self, key: str) -> bool: ... def __len__(self) -> int: ... def __bool__(self) -> bool: ... - # In Python 3 it returns bytes or str IO depending on an internal flag + def __del__(self) -> None: ... + # Returns bytes or str IO depending on an internal flag def make_file(self) -> IO[Any]: ... def print_exception( diff --git a/mypy/typeshed/stdlib/cgitb.pyi b/mypy/typeshed/stdlib/cgitb.pyi index 4c315bf6ca39..565725801159 100644 --- a/mypy/typeshed/stdlib/cgitb.pyi +++ b/mypy/typeshed/stdlib/cgitb.pyi @@ -1,8 +1,7 @@ from _typeshed import OptExcInfo, StrOrBytesPath from collections.abc import Callable from types import FrameType, TracebackType -from typing import IO, Any -from typing_extensions import Final +from typing import IO, Any, Final __UNDEF__: Final[object] # undocumented sentinel diff --git a/mypy/typeshed/stdlib/cmath.pyi b/mypy/typeshed/stdlib/cmath.pyi index 0a85600e99b7..fab9d10230f8 100644 --- a/mypy/typeshed/stdlib/cmath.pyi +++ b/mypy/typeshed/stdlib/cmath.pyi @@ -1,10 +1,6 @@ -import sys -from typing import SupportsComplex, SupportsFloat +from typing import SupportsComplex, SupportsFloat, SupportsIndex from typing_extensions import TypeAlias -if sys.version_info >= (3, 8): - from typing import SupportsIndex - e: float pi: float inf: float @@ -13,31 +9,28 @@ nan: float nanj: complex tau: float -if sys.version_info >= (3, 8): - _C: TypeAlias = SupportsFloat | SupportsComplex | SupportsIndex | complex -else: - _C: TypeAlias = SupportsFloat | SupportsComplex | complex +_C: TypeAlias = SupportsFloat | SupportsComplex | SupportsIndex | complex -def acos(__z: _C) -> complex: ... -def acosh(__z: _C) -> complex: ... -def asin(__z: _C) -> complex: ... -def asinh(__z: _C) -> complex: ... -def atan(__z: _C) -> complex: ... -def atanh(__z: _C) -> complex: ... -def cos(__z: _C) -> complex: ... -def cosh(__z: _C) -> complex: ... -def exp(__z: _C) -> complex: ... +def acos(z: _C, /) -> complex: ... +def acosh(z: _C, /) -> complex: ... +def asin(z: _C, /) -> complex: ... +def asinh(z: _C, /) -> complex: ... +def atan(z: _C, /) -> complex: ... +def atanh(z: _C, /) -> complex: ... +def cos(z: _C, /) -> complex: ... +def cosh(z: _C, /) -> complex: ... +def exp(z: _C, /) -> complex: ... def isclose(a: _C, b: _C, *, rel_tol: SupportsFloat = 1e-09, abs_tol: SupportsFloat = 0.0) -> bool: ... -def isinf(__z: _C) -> bool: ... -def isnan(__z: _C) -> bool: ... -def log(__x: _C, __y_obj: _C = ...) -> complex: ... -def log10(__z: _C) -> complex: ... -def phase(__z: _C) -> float: ... -def polar(__z: _C) -> tuple[float, float]: ... -def rect(__r: float, __phi: float) -> complex: ... -def sin(__z: _C) -> complex: ... -def sinh(__z: _C) -> complex: ... -def sqrt(__z: _C) -> complex: ... -def tan(__z: _C) -> complex: ... -def tanh(__z: _C) -> complex: ... -def isfinite(__z: _C) -> bool: ... +def isinf(z: _C, /) -> bool: ... +def isnan(z: _C, /) -> bool: ... +def log(x: _C, base: _C = ..., /) -> complex: ... +def log10(z: _C, /) -> complex: ... +def phase(z: _C, /) -> float: ... +def polar(z: _C, /) -> tuple[float, float]: ... +def rect(r: float, phi: float, /) -> complex: ... +def sin(z: _C, /) -> complex: ... +def sinh(z: _C, /) -> complex: ... +def sqrt(z: _C, /) -> complex: ... +def tan(z: _C, /) -> complex: ... +def tanh(z: _C, /) -> complex: ... +def isfinite(z: _C, /) -> bool: ... diff --git a/mypy/typeshed/stdlib/cmd.pyi b/mypy/typeshed/stdlib/cmd.pyi index b658a873410b..9499847fb153 100644 --- a/mypy/typeshed/stdlib/cmd.pyi +++ b/mypy/typeshed/stdlib/cmd.pyi @@ -1,6 +1,5 @@ from collections.abc import Callable -from typing import IO, Any -from typing_extensions import Literal +from typing import IO, Any, Literal __all__ = ["Cmd"] diff --git a/mypy/typeshed/stdlib/codecs.pyi b/mypy/typeshed/stdlib/codecs.pyi index 5a22853b6aee..6e53b780c473 100644 --- a/mypy/typeshed/stdlib/codecs.pyi +++ b/mypy/typeshed/stdlib/codecs.pyi @@ -1,11 +1,10 @@ -import sys import types from _codecs import * from _typeshed import ReadableBuffer from abc import abstractmethod from collections.abc import Callable, Generator, Iterable -from typing import Any, BinaryIO, Protocol, TextIO -from typing_extensions import Literal, Self +from typing import Any, BinaryIO, Literal, Protocol, TextIO +from typing_extensions import Self __all__ = [ "register", @@ -60,13 +59,13 @@ BOM64_BE: Literal[b"\x00\x00\xfe\xff"] BOM64_LE: Literal[b"\xff\xfe\x00\x00"] class _WritableStream(Protocol): - def write(self, __data: bytes) -> object: ... - def seek(self, __offset: int, __whence: int) -> object: ... + def write(self, data: bytes, /) -> object: ... + def seek(self, offset: int, whence: int, /) -> object: ... def close(self) -> object: ... class _ReadableStream(Protocol): - def read(self, __size: int = ...) -> bytes: ... - def seek(self, __offset: int, __whence: int) -> object: ... + def read(self, size: int = ..., /) -> bytes: ... + def seek(self, offset: int, whence: int, /) -> object: ... def close(self) -> object: ... class _Stream(_WritableStream, _ReadableStream, Protocol): ... @@ -78,16 +77,16 @@ class _Stream(_WritableStream, _ReadableStream, Protocol): ... # They were much more common in Python 2 than in Python 3. class _Encoder(Protocol): - def __call__(self, input: str, errors: str = ...) -> tuple[bytes, int]: ... # signature of Codec().encode + def __call__(self, input: str, errors: str = ..., /) -> tuple[bytes, int]: ... # signature of Codec().encode class _Decoder(Protocol): - def __call__(self, input: bytes, errors: str = ...) -> tuple[str, int]: ... # signature of Codec().decode + def __call__(self, input: bytes, errors: str = ..., /) -> tuple[str, int]: ... # signature of Codec().decode class _StreamReader(Protocol): - def __call__(self, stream: _ReadableStream, errors: str = ...) -> StreamReader: ... + def __call__(self, stream: _ReadableStream, errors: str = ..., /) -> StreamReader: ... class _StreamWriter(Protocol): - def __call__(self, stream: _WritableStream, errors: str = ...) -> StreamWriter: ... + def __call__(self, stream: _WritableStream, errors: str = ..., /) -> StreamWriter: ... class _IncrementalEncoder(Protocol): def __call__(self, errors: str = ...) -> IncrementalEncoder: ... @@ -96,6 +95,7 @@ class _IncrementalDecoder(Protocol): def __call__(self, errors: str = ...) -> IncrementalDecoder: ... class CodecInfo(tuple[_Encoder, _Decoder, _StreamReader, _StreamWriter]): + _is_text_encoding: bool @property def encode(self) -> _Encoder: ... @property @@ -128,17 +128,9 @@ def getincrementalencoder(encoding: str) -> _IncrementalEncoder: ... def getincrementaldecoder(encoding: str) -> _IncrementalDecoder: ... def getreader(encoding: str) -> _StreamReader: ... def getwriter(encoding: str) -> _StreamWriter: ... - -if sys.version_info >= (3, 8): - def open( - filename: str, mode: str = "r", encoding: str | None = None, errors: str = "strict", buffering: int = -1 - ) -> StreamReaderWriter: ... - -else: - def open( - filename: str, mode: str = "r", encoding: str | None = None, errors: str = "strict", buffering: int = 1 - ) -> StreamReaderWriter: ... - +def open( + filename: str, mode: str = "r", encoding: str | None = None, errors: str = "strict", buffering: int = -1 +) -> StreamReaderWriter: ... def EncodedFile(file: _Stream, data_encoding: str, file_encoding: str | None = None, errors: str = "strict") -> StreamRecoder: ... def iterencode(iterator: Iterable[str], encoding: str, errors: str = "strict") -> Generator[bytes, None, None]: ... def iterdecode(iterator: Iterable[bytes], encoding: str, errors: str = "strict") -> Generator[str, None, None]: ... @@ -212,7 +204,7 @@ class StreamWriter(Codec): def reset(self) -> None: ... def __enter__(self) -> Self: ... def __exit__(self, type: type[BaseException] | None, value: BaseException | None, tb: types.TracebackType | None) -> None: ... - def __getattr__(self, name: str, getattr: Callable[[str], Any] = ...) -> Any: ... + def __getattr__(self, name: str, getattr: Callable[[Any, str], Any] = ...) -> Any: ... class StreamReader(Codec): stream: _ReadableStream @@ -226,7 +218,7 @@ class StreamReader(Codec): def __exit__(self, type: type[BaseException] | None, value: BaseException | None, tb: types.TracebackType | None) -> None: ... def __iter__(self) -> Self: ... def __next__(self) -> str: ... - def __getattr__(self, name: str, getattr: Callable[[str], Any] = ...) -> Any: ... + def __getattr__(self, name: str, getattr: Callable[[Any, str], Any] = ...) -> Any: ... # Doesn't actually inherit from TextIO, but wraps a BinaryIO to provide text reading and writing # and delegates attributes to the underlying binary stream with __getattr__. @@ -272,8 +264,9 @@ class StreamRecoder(BinaryIO): def readlines(self, sizehint: int | None = None) -> list[bytes]: ... def __next__(self) -> bytes: ... def __iter__(self) -> Self: ... + # Base class accepts more types than just bytes def write(self, data: bytes) -> None: ... # type: ignore[override] - def writelines(self, list: Iterable[bytes]) -> None: ... + def writelines(self, list: Iterable[bytes]) -> None: ... # type: ignore[override] def reset(self) -> None: ... def __getattr__(self, name: str) -> Any: ... def __enter__(self) -> Self: ... diff --git a/mypy/typeshed/stdlib/collections/__init__.pyi b/mypy/typeshed/stdlib/collections/__init__.pyi index 893a289d3cb1..71e3c564dd57 100644 --- a/mypy/typeshed/stdlib/collections/__init__.pyi +++ b/mypy/typeshed/stdlib/collections/__init__.pyi @@ -1,8 +1,8 @@ import sys from _collections_abc import dict_items, dict_keys, dict_values -from _typeshed import SupportsKeysAndGetItem, SupportsRichComparison, SupportsRichComparisonT -from typing import Any, Generic, NoReturn, TypeVar, overload -from typing_extensions import Self, SupportsIndex, final +from _typeshed import SupportsItems, SupportsKeysAndGetItem, SupportsRichComparison, SupportsRichComparisonT +from typing import Any, Generic, NoReturn, SupportsIndex, TypeVar, final, overload +from typing_extensions import Self if sys.version_info >= (3, 9): from types import GenericAlias @@ -45,23 +45,37 @@ def namedtuple( defaults: Iterable[Any] | None = None, ) -> type[tuple[Any, ...]]: ... -class UserDict(MutableMapping[_KT, _VT], Generic[_KT, _VT]): +class UserDict(MutableMapping[_KT, _VT]): data: dict[_KT, _VT] # __init__ should be kept roughly in line with `dict.__init__`, which has the same semantics @overload - def __init__(self, __dict: None = None) -> None: ... + def __init__(self, dict: None = None, /) -> None: ... @overload - def __init__(self: UserDict[str, _VT], __dict: None = None, **kwargs: _VT) -> None: ... + def __init__( + self: UserDict[str, _VT], dict: None = None, /, **kwargs: _VT # pyright: ignore[reportInvalidTypeVarUse] #11780 + ) -> None: ... @overload - def __init__(self, __dict: SupportsKeysAndGetItem[_KT, _VT]) -> None: ... + def __init__(self, dict: SupportsKeysAndGetItem[_KT, _VT], /) -> None: ... @overload - def __init__(self: UserDict[str, _VT], __dict: SupportsKeysAndGetItem[str, _VT], **kwargs: _VT) -> None: ... + def __init__( + self: UserDict[str, _VT], # pyright: ignore[reportInvalidTypeVarUse] #11780 + dict: SupportsKeysAndGetItem[str, _VT], + /, + **kwargs: _VT, + ) -> None: ... @overload - def __init__(self, __iterable: Iterable[tuple[_KT, _VT]]) -> None: ... + def __init__(self, iterable: Iterable[tuple[_KT, _VT]], /) -> None: ... @overload - def __init__(self: UserDict[str, _VT], __iterable: Iterable[tuple[str, _VT]], **kwargs: _VT) -> None: ... + def __init__( + self: UserDict[str, _VT], # pyright: ignore[reportInvalidTypeVarUse] #11780 + iterable: Iterable[tuple[str, _VT]], + /, + **kwargs: _VT, + ) -> None: ... + @overload + def __init__(self: UserDict[str, str], iterable: Iterable[list[str]], /) -> None: ... @overload - def __init__(self: UserDict[str, str], __iterable: Iterable[list[str]]) -> None: ... + def __init__(self: UserDict[bytes, bytes], iterable: Iterable[list[bytes]], /) -> None: ... def __len__(self) -> int: ... def __getitem__(self, key: _KT) -> _VT: ... def __setitem__(self, key: _KT, item: _VT) -> None: ... @@ -81,13 +95,24 @@ class UserDict(MutableMapping[_KT, _VT], Generic[_KT, _VT]): @overload def fromkeys(cls, iterable: Iterable[_T], value: _S) -> UserDict[_T, _S]: ... if sys.version_info >= (3, 9): + @overload + def __or__(self, other: UserDict[_KT, _VT] | dict[_KT, _VT]) -> Self: ... + @overload def __or__(self, other: UserDict[_T1, _T2] | dict[_T1, _T2]) -> UserDict[_KT | _T1, _VT | _T2]: ... - def __ror__(self, other: UserDict[_T1, _T2] | dict[_T1, _T2]) -> UserDict[_KT | _T1, _VT | _T2]: ... # type: ignore[misc] + @overload + def __ror__(self, other: UserDict[_KT, _VT] | dict[_KT, _VT]) -> Self: ... + @overload + def __ror__(self, other: UserDict[_T1, _T2] | dict[_T1, _T2]) -> UserDict[_KT | _T1, _VT | _T2]: ... # UserDict.__ior__ should be kept roughly in line with MutableMapping.update() @overload # type: ignore[misc] def __ior__(self, other: SupportsKeysAndGetItem[_KT, _VT]) -> Self: ... @overload def __ior__(self, other: Iterable[tuple[_KT, _VT]]) -> Self: ... + if sys.version_info >= (3, 12): + @overload + def get(self, key: _KT, default: None = None) -> _VT | None: ... + @overload + def get(self, key: _KT, default: _T) -> _VT | _T: ... class UserList(MutableSequence[_T]): data: list[_T] @@ -124,8 +149,10 @@ class UserList(MutableSequence[_T]): def copy(self) -> Self: ... def __copy__(self) -> Self: ... def count(self, item: _T) -> int: ... - # All arguments are passed to `list.index` at runtime, so the signature should be kept in line with `list.index`. - def index(self, item: _T, __start: SupportsIndex = 0, __stop: SupportsIndex = sys.maxsize) -> int: ... + # The runtime signature is "item, *args", and the arguments are then passed + # to `list.index`. In order to give more precise types, we pretend that the + # `item` argument is positional-only. + def index(self, item: _T, start: SupportsIndex = 0, stop: SupportsIndex = sys.maxsize, /) -> int: ... # All arguments are passed to `list.sort` at runtime, so the signature should be kept in line with `list.sort`. @overload def sort(self: UserList[SupportsRichComparisonT], *, key: None = None, reverse: bool = False) -> None: ... @@ -145,6 +172,7 @@ class UserString(Sequence[UserString]): def __gt__(self, string: str | UserString) -> bool: ... def __ge__(self, string: str | UserString) -> bool: ... def __eq__(self, string: object) -> bool: ... + def __hash__(self) -> int: ... def __contains__(self, char: object) -> bool: ... def __len__(self) -> int: ... def __getitem__(self, index: SupportsIndex | slice) -> Self: ... @@ -155,20 +183,12 @@ class UserString(Sequence[UserString]): def __mul__(self, n: int) -> Self: ... def __rmul__(self, n: int) -> Self: ... def __mod__(self, args: Any) -> Self: ... - if sys.version_info >= (3, 8): - def __rmod__(self, template: object) -> Self: ... - else: - def __rmod__(self, format: Any) -> Self: ... - + def __rmod__(self, template: object) -> Self: ... def capitalize(self) -> Self: ... def casefold(self) -> Self: ... def center(self, width: int, *args: Any) -> Self: ... def count(self, sub: str | UserString, start: int = 0, end: int = sys.maxsize) -> int: ... - if sys.version_info >= (3, 8): - def encode(self: UserString, encoding: str | None = "utf-8", errors: str | None = "strict") -> bytes: ... - else: - def encode(self, encoding: str | None = None, errors: str | None = None) -> Self: ... - + def encode(self: UserString, encoding: str | None = "utf-8", errors: str | None = "strict") -> bytes: ... def endswith(self, suffix: str | tuple[str, ...], start: int | None = 0, end: int | None = sys.maxsize) -> bool: ... def expandtabs(self, tabsize: int = 8) -> Self: ... def find(self, sub: str | UserString, start: int = 0, end: int = sys.maxsize) -> int: ... @@ -194,8 +214,8 @@ class UserString(Sequence[UserString]): maketrans = str.maketrans def partition(self, sep: str) -> tuple[str, str, str]: ... if sys.version_info >= (3, 9): - def removeprefix(self, __prefix: str | UserString) -> Self: ... - def removesuffix(self, __suffix: str | UserString) -> Self: ... + def removeprefix(self, prefix: str | UserString, /) -> Self: ... + def removesuffix(self, suffix: str | UserString, /) -> Self: ... def replace(self, old: str | UserString, new: str | UserString, maxsplit: int = -1) -> Self: ... def rfind(self, sub: str | UserString, start: int = 0, end: int = sys.maxsize) -> int: ... @@ -214,64 +234,65 @@ class UserString(Sequence[UserString]): def upper(self) -> Self: ... def zfill(self, width: int) -> Self: ... -class deque(MutableSequence[_T], Generic[_T]): +class deque(MutableSequence[_T]): @property def maxlen(self) -> int | None: ... @overload def __init__(self, *, maxlen: int | None = None) -> None: ... @overload def __init__(self, iterable: Iterable[_T], maxlen: int | None = None) -> None: ... - def append(self, __x: _T) -> None: ... - def appendleft(self, __x: _T) -> None: ... + def append(self, x: _T, /) -> None: ... + def appendleft(self, x: _T, /) -> None: ... def copy(self) -> Self: ... - def count(self, __x: _T) -> int: ... - def extend(self, __iterable: Iterable[_T]) -> None: ... - def extendleft(self, __iterable: Iterable[_T]) -> None: ... - def insert(self, __i: int, __x: _T) -> None: ... - def index(self, __x: _T, __start: int = 0, __stop: int = ...) -> int: ... + def count(self, x: _T, /) -> int: ... + def extend(self, iterable: Iterable[_T], /) -> None: ... + def extendleft(self, iterable: Iterable[_T], /) -> None: ... + def insert(self, i: int, x: _T, /) -> None: ... + def index(self, x: _T, start: int = 0, stop: int = ..., /) -> int: ... def pop(self) -> _T: ... # type: ignore[override] def popleft(self) -> _T: ... - def remove(self, __value: _T) -> None: ... - def rotate(self, __n: int = 1) -> None: ... + def remove(self, value: _T, /) -> None: ... + def rotate(self, n: int = 1, /) -> None: ... def __copy__(self) -> Self: ... def __len__(self) -> int: ... # These methods of deque don't take slices, unlike MutableSequence, hence the type: ignores - def __getitem__(self, __index: SupportsIndex) -> _T: ... # type: ignore[override] - def __setitem__(self, __i: SupportsIndex, __x: _T) -> None: ... # type: ignore[override] - def __delitem__(self, __i: SupportsIndex) -> None: ... # type: ignore[override] - def __contains__(self, __o: object) -> bool: ... + def __getitem__(self, key: SupportsIndex, /) -> _T: ... # type: ignore[override] + def __setitem__(self, key: SupportsIndex, value: _T, /) -> None: ... # type: ignore[override] + def __delitem__(self, key: SupportsIndex, /) -> None: ... # type: ignore[override] + def __contains__(self, key: object, /) -> bool: ... def __reduce__(self) -> tuple[type[Self], tuple[()], None, Iterator[_T]]: ... - def __iadd__(self, __iterable: Iterable[_T]) -> Self: ... - def __add__(self, __other: Self) -> Self: ... - def __mul__(self, __other: int) -> Self: ... - def __imul__(self, __other: int) -> Self: ... - def __lt__(self, __other: deque[_T]) -> bool: ... - def __le__(self, __other: deque[_T]) -> bool: ... - def __gt__(self, __other: deque[_T]) -> bool: ... - def __ge__(self, __other: deque[_T]) -> bool: ... + def __iadd__(self, value: Iterable[_T], /) -> Self: ... + def __add__(self, value: Self, /) -> Self: ... + def __mul__(self, value: int, /) -> Self: ... + def __imul__(self, value: int, /) -> Self: ... + def __lt__(self, value: deque[_T], /) -> bool: ... + def __le__(self, value: deque[_T], /) -> bool: ... + def __gt__(self, value: deque[_T], /) -> bool: ... + def __ge__(self, value: deque[_T], /) -> bool: ... + def __eq__(self, value: object, /) -> bool: ... if sys.version_info >= (3, 9): - def __class_getitem__(cls, __item: Any) -> GenericAlias: ... + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... class Counter(dict[_T, int], Generic[_T]): @overload - def __init__(self, __iterable: None = None) -> None: ... + def __init__(self, iterable: None = None, /) -> None: ... @overload - def __init__(self: Counter[str], __iterable: None = None, **kwargs: int) -> None: ... + def __init__(self: Counter[str], iterable: None = None, /, **kwargs: int) -> None: ... @overload - def __init__(self, __mapping: SupportsKeysAndGetItem[_T, int]) -> None: ... + def __init__(self, mapping: SupportsKeysAndGetItem[_T, int], /) -> None: ... @overload - def __init__(self, __iterable: Iterable[_T]) -> None: ... + def __init__(self, iterable: Iterable[_T], /) -> None: ... def copy(self) -> Self: ... def elements(self) -> Iterator[_T]: ... def most_common(self, n: int | None = None) -> list[tuple[_T, int]]: ... @classmethod def fromkeys(cls, iterable: Any, v: int | None = None) -> NoReturn: ... # type: ignore[override] @overload - def subtract(self, __iterable: None = None) -> None: ... + def subtract(self, iterable: None = None, /) -> None: ... @overload - def subtract(self, __mapping: Mapping[_T, int]) -> None: ... + def subtract(self, mapping: Mapping[_T, int], /) -> None: ... @overload - def subtract(self, __iterable: Iterable[_T]) -> None: ... + def subtract(self, iterable: Iterable[_T], /) -> None: ... # Unlike dict.update(), use Mapping instead of SupportsKeysAndGetItem for the first overload # (source code does an `isinstance(other, Mapping)` check) # @@ -279,11 +300,11 @@ class Counter(dict[_T, int], Generic[_T]): # (if it were `Iterable[_T] | Iterable[tuple[_T, int]]`, # the tuples would be added as keys, breaking type safety) @overload # type: ignore[override] - def update(self, __m: Mapping[_T, int], **kwargs: int) -> None: ... + def update(self, m: Mapping[_T, int], /, **kwargs: int) -> None: ... @overload - def update(self, __iterable: Iterable[_T], **kwargs: int) -> None: ... + def update(self, iterable: Iterable[_T], /, **kwargs: int) -> None: ... @overload - def update(self, __iterable: None = None, **kwargs: int) -> None: ... + def update(self, iterable: None = None, /, **kwargs: int) -> None: ... def __missing__(self, key: _T) -> int: ... def __delitem__(self, elem: object) -> None: ... if sys.version_info >= (3, 10): @@ -297,10 +318,10 @@ class Counter(dict[_T, int], Generic[_T]): def __pos__(self) -> Counter[_T]: ... def __neg__(self) -> Counter[_T]: ... # several type: ignores because __iadd__ is supposedly incompatible with __add__, etc. - def __iadd__(self, other: Counter[_T]) -> Self: ... # type: ignore[misc] - def __isub__(self, other: Counter[_T]) -> Self: ... - def __iand__(self, other: Counter[_T]) -> Self: ... - def __ior__(self, other: Counter[_T]) -> Self: ... # type: ignore[override,misc] + def __iadd__(self, other: SupportsItems[_T, int]) -> Self: ... # type: ignore[misc] + def __isub__(self, other: SupportsItems[_T, int]) -> Self: ... + def __iand__(self, other: SupportsItems[_T, int]) -> Self: ... + def __ior__(self, other: SupportsItems[_T, int]) -> Self: ... # type: ignore[override,misc] if sys.version_info >= (3, 10): def total(self) -> int: ... def __le__(self, other: Counter[Any]) -> bool: ... @@ -357,40 +378,73 @@ class OrderedDict(dict[_KT, _VT], Reversible[_KT], Generic[_KT, _VT]): def setdefault(self: OrderedDict[_KT, _T | None], key: _KT, default: None = None) -> _T | None: ... @overload def setdefault(self, key: _KT, default: _VT) -> _VT: ... + # Same as dict.pop, but accepts keyword arguments + @overload + def pop(self, key: _KT) -> _VT: ... + @overload + def pop(self, key: _KT, default: _VT) -> _VT: ... + @overload + def pop(self, key: _KT, default: _T) -> _VT | _T: ... + def __eq__(self, value: object, /) -> bool: ... + if sys.version_info >= (3, 9): + @overload + def __or__(self, value: dict[_KT, _VT], /) -> Self: ... + @overload + def __or__(self, value: dict[_T1, _T2], /) -> OrderedDict[_KT | _T1, _VT | _T2]: ... + @overload + def __ror__(self, value: dict[_KT, _VT], /) -> Self: ... + @overload + def __ror__(self, value: dict[_T1, _T2], /) -> OrderedDict[_KT | _T1, _VT | _T2]: ... # type: ignore[misc] -class defaultdict(dict[_KT, _VT], Generic[_KT, _VT]): +class defaultdict(dict[_KT, _VT]): default_factory: Callable[[], _VT] | None @overload def __init__(self) -> None: ... @overload - def __init__(self: defaultdict[str, _VT], **kwargs: _VT) -> None: ... + def __init__(self: defaultdict[str, _VT], **kwargs: _VT) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] #11780 @overload - def __init__(self, __default_factory: Callable[[], _VT] | None) -> None: ... + def __init__(self, default_factory: Callable[[], _VT] | None, /) -> None: ... @overload - def __init__(self: defaultdict[str, _VT], __default_factory: Callable[[], _VT] | None, **kwargs: _VT) -> None: ... + def __init__( + self: defaultdict[str, _VT], # pyright: ignore[reportInvalidTypeVarUse] #11780 + default_factory: Callable[[], _VT] | None, + /, + **kwargs: _VT, + ) -> None: ... @overload - def __init__(self, __default_factory: Callable[[], _VT] | None, __map: SupportsKeysAndGetItem[_KT, _VT]) -> None: ... + def __init__(self, default_factory: Callable[[], _VT] | None, map: SupportsKeysAndGetItem[_KT, _VT], /) -> None: ... @overload def __init__( - self: defaultdict[str, _VT], - __default_factory: Callable[[], _VT] | None, - __map: SupportsKeysAndGetItem[str, _VT], + self: defaultdict[str, _VT], # pyright: ignore[reportInvalidTypeVarUse] #11780 + default_factory: Callable[[], _VT] | None, + map: SupportsKeysAndGetItem[str, _VT], + /, **kwargs: _VT, ) -> None: ... @overload - def __init__(self, __default_factory: Callable[[], _VT] | None, __iterable: Iterable[tuple[_KT, _VT]]) -> None: ... + def __init__(self, default_factory: Callable[[], _VT] | None, iterable: Iterable[tuple[_KT, _VT]], /) -> None: ... @overload def __init__( - self: defaultdict[str, _VT], - __default_factory: Callable[[], _VT] | None, - __iterable: Iterable[tuple[str, _VT]], + self: defaultdict[str, _VT], # pyright: ignore[reportInvalidTypeVarUse] #11780 + default_factory: Callable[[], _VT] | None, + iterable: Iterable[tuple[str, _VT]], + /, **kwargs: _VT, ) -> None: ... - def __missing__(self, __key: _KT) -> _VT: ... + def __missing__(self, key: _KT, /) -> _VT: ... def __copy__(self) -> Self: ... def copy(self) -> Self: ... + if sys.version_info >= (3, 9): + @overload + def __or__(self, value: dict[_KT, _VT], /) -> Self: ... + @overload + def __or__(self, value: dict[_T1, _T2], /) -> defaultdict[_KT | _T1, _VT | _T2]: ... + @overload + def __ror__(self, value: dict[_KT, _VT], /) -> Self: ... + @overload + def __ror__(self, value: dict[_T1, _T2], /) -> defaultdict[_KT | _T1, _VT | _T2]: ... # type: ignore[misc] -class ChainMap(MutableMapping[_KT, _VT], Generic[_KT, _VT]): +class ChainMap(MutableMapping[_KT, _VT]): maps: list[MutableMapping[_KT, _VT]] def __init__(self, *maps: MutableMapping[_KT, _VT]) -> None: ... def new_child(self, m: MutableMapping[_KT, _VT] | None = None) -> Self: ... @@ -402,6 +456,10 @@ class ChainMap(MutableMapping[_KT, _VT], Generic[_KT, _VT]): def __iter__(self) -> Iterator[_KT]: ... def __len__(self) -> int: ... def __contains__(self, key: object) -> bool: ... + @overload + def get(self, key: _KT, default: None = None) -> _VT | None: ... + @overload + def get(self, key: _KT, default: _T) -> _VT | _T: ... def __missing__(self, key: _KT) -> _VT: ... # undocumented def __bool__(self) -> bool: ... # Keep ChainMap.setdefault in line with MutableMapping.setdefault, modulo positional-only differences. @@ -412,18 +470,30 @@ class ChainMap(MutableMapping[_KT, _VT], Generic[_KT, _VT]): @overload def pop(self, key: _KT) -> _VT: ... @overload - def pop(self, key: _KT, default: _VT | _T) -> _VT | _T: ... + def pop(self, key: _KT, default: _VT) -> _VT: ... + @overload + def pop(self, key: _KT, default: _T) -> _VT | _T: ... def copy(self) -> Self: ... __copy__ = copy # All arguments to `fromkeys` are passed to `dict.fromkeys` at runtime, so the signature should be kept in line with `dict.fromkeys`. @classmethod @overload - def fromkeys(cls, iterable: Iterable[_T], __value: None = None) -> ChainMap[_T, Any | None]: ... + def fromkeys(cls, iterable: Iterable[_T]) -> ChainMap[_T, Any | None]: ... + @classmethod + @overload + # Special-case None: the user probably wants to add non-None values later. + def fromkeys(cls, iterable: Iterable[_T], value: None, /) -> ChainMap[_T, Any | None]: ... @classmethod @overload - def fromkeys(cls, __iterable: Iterable[_T], __value: _S) -> ChainMap[_T, _S]: ... + def fromkeys(cls, iterable: Iterable[_T], value: _S, /) -> ChainMap[_T, _S]: ... if sys.version_info >= (3, 9): + @overload + def __or__(self, other: Mapping[_KT, _VT]) -> Self: ... + @overload def __or__(self, other: Mapping[_T1, _T2]) -> ChainMap[_KT | _T1, _VT | _T2]: ... + @overload + def __ror__(self, other: Mapping[_KT, _VT]) -> Self: ... + @overload def __ror__(self, other: Mapping[_T1, _T2]) -> ChainMap[_KT | _T1, _VT | _T2]: ... # ChainMap.__ior__ should be kept roughly in line with MutableMapping.update() @overload # type: ignore[misc] diff --git a/mypy/typeshed/stdlib/compileall.pyi b/mypy/typeshed/stdlib/compileall.pyi index 7520c2f5b676..9fb3608f2979 100644 --- a/mypy/typeshed/stdlib/compileall.pyi +++ b/mypy/typeshed/stdlib/compileall.pyi @@ -6,7 +6,7 @@ from typing import Any, Protocol __all__ = ["compile_dir", "compile_file", "compile_path"] class _SupportsSearch(Protocol): - def search(self, string: str) -> Any: ... + def search(self, string: str, /) -> Any: ... if sys.version_info >= (3, 10): def compile_dir( diff --git a/mypy/typeshed/stdlib/concurrent/futures/__init__.pyi b/mypy/typeshed/stdlib/concurrent/futures/__init__.pyi index ff2e72bbf4fb..07314ce9d402 100644 --- a/mypy/typeshed/stdlib/concurrent/futures/__init__.pyi +++ b/mypy/typeshed/stdlib/concurrent/futures/__init__.pyi @@ -1,5 +1,3 @@ -import sys - from ._base import ( ALL_COMPLETED as ALL_COMPLETED, FIRST_COMPLETED as FIRST_COMPLETED, @@ -8,6 +6,7 @@ from ._base import ( CancelledError as CancelledError, Executor as Executor, Future as Future, + InvalidStateError as InvalidStateError, TimeoutError as TimeoutError, as_completed as as_completed, wait as wait, @@ -15,9 +14,6 @@ from ._base import ( from .process import ProcessPoolExecutor as ProcessPoolExecutor from .thread import ThreadPoolExecutor as ThreadPoolExecutor -if sys.version_info >= (3, 8): - from ._base import InvalidStateError as InvalidStateError - __all__ = ( "FIRST_COMPLETED", "FIRST_EXCEPTION", diff --git a/mypy/typeshed/stdlib/concurrent/futures/_base.pyi b/mypy/typeshed/stdlib/concurrent/futures/_base.pyi index e792cf1a83c0..7dfdda224013 100644 --- a/mypy/typeshed/stdlib/concurrent/futures/_base.pyi +++ b/mypy/typeshed/stdlib/concurrent/futures/_base.pyi @@ -1,11 +1,11 @@ import sys import threading from _typeshed import Unused -from collections.abc import Callable, Iterable, Iterator, Sequence +from collections.abc import Callable, Collection, Iterable, Iterator from logging import Logger from types import TracebackType -from typing import Any, Generic, TypeVar, overload -from typing_extensions import Literal, ParamSpec, Self, SupportsIndex +from typing import Any, Generic, Literal, NamedTuple, Protocol, TypeVar +from typing_extensions import ParamSpec, Self if sys.version_info >= (3, 9): from types import GenericAlias @@ -24,17 +24,25 @@ LOGGER: Logger class Error(Exception): ... class CancelledError(Error): ... -class TimeoutError(Error): ... -if sys.version_info >= (3, 8): - class InvalidStateError(Error): ... +if sys.version_info >= (3, 11): + from builtins import TimeoutError as TimeoutError +else: + class TimeoutError(Error): ... +class InvalidStateError(Error): ... class BrokenExecutor(RuntimeError): ... _T = TypeVar("_T") +_T_co = TypeVar("_T_co", covariant=True) _P = ParamSpec("_P") class Future(Generic[_T]): + _condition: threading.Condition + _state: str + _result: _T | None + _exception: BaseException | None + _waiters: list[_Waiter] def cancel(self) -> bool: ... def cancelled(self) -> bool: ... def running(self) -> bool: ... @@ -50,7 +58,7 @@ class Future(Generic[_T]): class Executor: if sys.version_info >= (3, 9): - def submit(self, __fn: Callable[_P, _T], *args: _P.args, **kwargs: _P.kwargs) -> Future[_T]: ... + def submit(self, fn: Callable[_P, _T], /, *args: _P.args, **kwargs: _P.kwargs) -> Future[_T]: ... else: def submit(self, fn: Callable[_P, _T], *args: _P.args, **kwargs: _P.kwargs) -> Future[_T]: ... @@ -67,26 +75,31 @@ class Executor: self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None ) -> bool | None: ... -def as_completed(fs: Iterable[Future[_T]], timeout: float | None = None) -> Iterator[Future[_T]]: ... - -# Ideally this would be a namedtuple, but mypy doesn't support generic tuple types. See #1976 -class DoneAndNotDoneFutures(Sequence[set[Future[_T]]]): - if sys.version_info >= (3, 10): - __match_args__ = ("done", "not_done") - @property - def done(self) -> set[Future[_T]]: ... - @property - def not_done(self) -> set[Future[_T]]: ... - def __new__(_cls, done: set[Future[_T]], not_done: set[Future[_T]]) -> DoneAndNotDoneFutures[_T]: ... - def __len__(self) -> int: ... - @overload - def __getitem__(self, __i: SupportsIndex) -> set[Future[_T]]: ... - @overload - def __getitem__(self, __s: slice) -> DoneAndNotDoneFutures[_T]: ... - -def wait( - fs: Iterable[Future[_T]], timeout: float | None = None, return_when: str = "ALL_COMPLETED" -) -> DoneAndNotDoneFutures[_T]: ... +class _AsCompletedFuture(Protocol[_T_co]): + # as_completed only mutates non-generic aspects of passed Futures and does not do any nominal + # checks. Therefore, we can use a Protocol here to allow as_completed to act covariantly. + # See the tests for concurrent.futures + _condition: threading.Condition + _state: str + _waiters: list[_Waiter] + # Not used by as_completed, but needed to propagate the generic type + def result(self, timeout: float | None = None) -> _T_co: ... + +def as_completed(fs: Iterable[_AsCompletedFuture[_T]], timeout: float | None = None) -> Iterator[Future[_T]]: ... + +class DoneAndNotDoneFutures(NamedTuple, Generic[_T]): + done: set[Future[_T]] + not_done: set[Future[_T]] + +if sys.version_info >= (3, 9): + def wait( + fs: Iterable[Future[_T]], timeout: float | None = None, return_when: str = "ALL_COMPLETED" + ) -> DoneAndNotDoneFutures[_T]: ... + +else: + def wait( + fs: Collection[Future[_T]], timeout: float | None = None, return_when: str = "ALL_COMPLETED" + ) -> DoneAndNotDoneFutures[_T]: ... class _Waiter: event: threading.Event diff --git a/mypy/typeshed/stdlib/concurrent/futures/process.pyi b/mypy/typeshed/stdlib/concurrent/futures/process.pyi index 85af2e7f84c7..d3706a9c15a6 100644 --- a/mypy/typeshed/stdlib/concurrent/futures/process.pyi +++ b/mypy/typeshed/stdlib/concurrent/futures/process.pyi @@ -5,12 +5,14 @@ from multiprocessing.context import BaseContext, Process from multiprocessing.queues import Queue, SimpleQueue from threading import Lock, Semaphore, Thread from types import TracebackType -from typing import Any, Generic, TypeVar +from typing import Any, Generic, TypeVar, overload +from typing_extensions import TypeVarTuple, Unpack from weakref import ref from ._base import BrokenExecutor, Executor, Future _T = TypeVar("_T") +_Ts = TypeVarTuple("_Ts") _threads_wakeups: MutableMapping[Any, Any] _global_shutdown: bool @@ -109,8 +111,8 @@ if sys.version_info >= (3, 11): def _process_worker( call_queue: Queue[_CallItem], result_queue: SimpleQueue[_ResultItem], - initializer: Callable[..., object] | None, - initargs: tuple[Any, ...], + initializer: Callable[[Unpack[_Ts]], object] | None, + initargs: tuple[Unpack[_Ts]], max_tasks: int | None = None, ) -> None: ... @@ -118,8 +120,8 @@ else: def _process_worker( call_queue: Queue[_CallItem], result_queue: SimpleQueue[_ResultItem], - initializer: Callable[..., object] | None, - initargs: tuple[Any, ...], + initializer: Callable[[Unpack[_Ts]], object] | None, + initargs: tuple[Unpack[_Ts]], ) -> None: ... if sys.version_info >= (3, 9): @@ -153,9 +155,9 @@ def _chain_from_iterable_of_lists(iterable: Iterable[MutableSequence[Any]]) -> A class BrokenProcessPool(BrokenExecutor): ... class ProcessPoolExecutor(Executor): - _mp_context: BaseContext | None = ... - _initializer: Callable[..., None] | None = ... - _initargs: tuple[Any, ...] = ... + _mp_context: BaseContext | None + _initializer: Callable[..., None] | None + _initargs: tuple[Any, ...] _executor_manager_thread: _ThreadWakeup _processes: MutableMapping[int, Process] _shutdown_thread: bool @@ -169,22 +171,61 @@ class ProcessPoolExecutor(Executor): _result_queue: SimpleQueue[Any] _work_ids: Queue[Any] if sys.version_info >= (3, 11): + @overload def __init__( self, max_workers: int | None = None, mp_context: BaseContext | None = None, - initializer: Callable[..., object] | None = None, - initargs: tuple[Any, ...] = ..., + initializer: Callable[[], object] | None = None, + initargs: tuple[()] = (), + *, + max_tasks_per_child: int | None = None, + ) -> None: ... + @overload + def __init__( + self, + max_workers: int | None = None, + mp_context: BaseContext | None = None, + *, + initializer: Callable[[Unpack[_Ts]], object], + initargs: tuple[Unpack[_Ts]], + max_tasks_per_child: int | None = None, + ) -> None: ... + @overload + def __init__( + self, + max_workers: int | None, + mp_context: BaseContext | None, + initializer: Callable[[Unpack[_Ts]], object], + initargs: tuple[Unpack[_Ts]], *, max_tasks_per_child: int | None = None, ) -> None: ... else: + @overload + def __init__( + self, + max_workers: int | None = None, + mp_context: BaseContext | None = None, + initializer: Callable[[], object] | None = None, + initargs: tuple[()] = (), + ) -> None: ... + @overload def __init__( self, max_workers: int | None = None, mp_context: BaseContext | None = None, - initializer: Callable[..., object] | None = None, - initargs: tuple[Any, ...] = ..., + *, + initializer: Callable[[Unpack[_Ts]], object], + initargs: tuple[Unpack[_Ts]], + ) -> None: ... + @overload + def __init__( + self, + max_workers: int | None, + mp_context: BaseContext | None, + initializer: Callable[[Unpack[_Ts]], object], + initargs: tuple[Unpack[_Ts]], ) -> None: ... if sys.version_info >= (3, 9): def _start_executor_manager_thread(self) -> None: ... diff --git a/mypy/typeshed/stdlib/concurrent/futures/thread.pyi b/mypy/typeshed/stdlib/concurrent/futures/thread.pyi index e43dd3dfa33a..f38cf2c57963 100644 --- a/mypy/typeshed/stdlib/concurrent/futures/thread.pyi +++ b/mypy/typeshed/stdlib/concurrent/futures/thread.pyi @@ -2,11 +2,14 @@ import queue import sys from collections.abc import Callable, Iterable, Mapping, Set as AbstractSet from threading import Lock, Semaphore, Thread -from typing import Any, Generic, TypeVar +from typing import Any, Generic, TypeVar, overload +from typing_extensions import TypeVarTuple, Unpack from weakref import ref from ._base import BrokenExecutor, Executor, Future +_Ts = TypeVarTuple("_Ts") + _threads_queues: Mapping[Any, Any] _shutdown: bool _global_shutdown_lock: Lock @@ -31,8 +34,8 @@ class _WorkItem(Generic[_S]): def _worker( executor_reference: ref[Any], work_queue: queue.SimpleQueue[Any], - initializer: Callable[..., object], - initargs: tuple[Any, ...], + initializer: Callable[[Unpack[_Ts]], object], + initargs: tuple[Unpack[_Ts]], ) -> None: ... class BrokenThreadPool(BrokenExecutor): ... @@ -44,16 +47,34 @@ class ThreadPoolExecutor(Executor): _broken: bool _shutdown: bool _shutdown_lock: Lock - _thread_name_prefix: str | None = ... - _initializer: Callable[..., None] | None = ... - _initargs: tuple[Any, ...] = ... + _thread_name_prefix: str | None + _initializer: Callable[..., None] | None + _initargs: tuple[Any, ...] _work_queue: queue.SimpleQueue[_WorkItem[Any]] + @overload def __init__( self, max_workers: int | None = None, thread_name_prefix: str = "", - initializer: Callable[..., object] | None = None, - initargs: tuple[Any, ...] = ..., + initializer: Callable[[], object] | None = None, + initargs: tuple[()] = (), + ) -> None: ... + @overload + def __init__( + self, + max_workers: int | None = None, + thread_name_prefix: str = "", + *, + initializer: Callable[[Unpack[_Ts]], object], + initargs: tuple[Unpack[_Ts]], + ) -> None: ... + @overload + def __init__( + self, + max_workers: int | None, + thread_name_prefix: str, + initializer: Callable[[Unpack[_Ts]], object], + initargs: tuple[Unpack[_Ts]], ) -> None: ... def _adjust_thread_count(self) -> None: ... def _initializer_failed(self) -> None: ... diff --git a/mypy/typeshed/stdlib/configparser.pyi b/mypy/typeshed/stdlib/configparser.pyi index 2c5b68385767..07b57b17d56d 100644 --- a/mypy/typeshed/stdlib/configparser.pyi +++ b/mypy/typeshed/stdlib/configparser.pyi @@ -2,32 +2,56 @@ import sys from _typeshed import StrOrBytesPath, SupportsWrite from collections.abc import Callable, ItemsView, Iterable, Iterator, Mapping, MutableMapping, Sequence from re import Pattern -from typing import Any, ClassVar, TypeVar, overload -from typing_extensions import Literal, TypeAlias +from typing import Any, ClassVar, Literal, TypeVar, overload +from typing_extensions import TypeAlias -__all__ = [ - "NoSectionError", - "DuplicateOptionError", - "DuplicateSectionError", - "NoOptionError", - "InterpolationError", - "InterpolationDepthError", - "InterpolationMissingOptionError", - "InterpolationSyntaxError", - "ParsingError", - "MissingSectionHeaderError", - "ConfigParser", - "SafeConfigParser", - "RawConfigParser", - "Interpolation", - "BasicInterpolation", - "ExtendedInterpolation", - "LegacyInterpolation", - "SectionProxy", - "ConverterMapping", - "DEFAULTSECT", - "MAX_INTERPOLATION_DEPTH", -] +if sys.version_info >= (3, 12): + __all__ = ( + "NoSectionError", + "DuplicateOptionError", + "DuplicateSectionError", + "NoOptionError", + "InterpolationError", + "InterpolationDepthError", + "InterpolationMissingOptionError", + "InterpolationSyntaxError", + "ParsingError", + "MissingSectionHeaderError", + "ConfigParser", + "RawConfigParser", + "Interpolation", + "BasicInterpolation", + "ExtendedInterpolation", + "LegacyInterpolation", + "SectionProxy", + "ConverterMapping", + "DEFAULTSECT", + "MAX_INTERPOLATION_DEPTH", + ) +else: + __all__ = [ + "NoSectionError", + "DuplicateOptionError", + "DuplicateSectionError", + "NoOptionError", + "InterpolationError", + "InterpolationDepthError", + "InterpolationMissingOptionError", + "InterpolationSyntaxError", + "ParsingError", + "MissingSectionHeaderError", + "ConfigParser", + "SafeConfigParser", + "RawConfigParser", + "Interpolation", + "BasicInterpolation", + "ExtendedInterpolation", + "LegacyInterpolation", + "SectionProxy", + "ConverterMapping", + "DEFAULTSECT", + "MAX_INTERPOLATION_DEPTH", + ] _Section: TypeAlias = Mapping[str, str] _Parser: TypeAlias = MutableMapping[str, _Section] @@ -69,8 +93,8 @@ class RawConfigParser(_Parser): dict_type: type[Mapping[str, str]] = ..., *, allow_no_value: Literal[True], - delimiters: Sequence[str] = ..., - comment_prefixes: Sequence[str] = ..., + delimiters: Sequence[str] = ("=", ":"), + comment_prefixes: Sequence[str] = ("#", ";"), inline_comment_prefixes: Sequence[str] | None = None, strict: bool = True, empty_lines_in_values: bool = True, @@ -85,8 +109,8 @@ class RawConfigParser(_Parser): dict_type: type[Mapping[str, str]], allow_no_value: Literal[True], *, - delimiters: Sequence[str] = ..., - comment_prefixes: Sequence[str] = ..., + delimiters: Sequence[str] = ("=", ":"), + comment_prefixes: Sequence[str] = ("#", ";"), inline_comment_prefixes: Sequence[str] | None = None, strict: bool = True, empty_lines_in_values: bool = True, @@ -101,8 +125,8 @@ class RawConfigParser(_Parser): dict_type: type[Mapping[str, str]] = ..., allow_no_value: bool = False, *, - delimiters: Sequence[str] = ..., - comment_prefixes: Sequence[str] = ..., + delimiters: Sequence[str] = ("=", ":"), + comment_prefixes: Sequence[str] = ("#", ";"), inline_comment_prefixes: Sequence[str] | None = None, strict: bool = True, empty_lines_in_values: bool = True, @@ -126,7 +150,8 @@ class RawConfigParser(_Parser): def read_file(self, f: Iterable[str], source: str | None = None) -> None: ... def read_string(self, string: str, source: str = "") -> None: ... def read_dict(self, dictionary: Mapping[str, Mapping[str, Any]], source: str = "") -> None: ... - def readfp(self, fp: Iterable[str], filename: str | None = None) -> None: ... + if sys.version_info < (3, 12): + def readfp(self, fp: Iterable[str], filename: str | None = None) -> None: ... # These get* methods are partially applied (with the same names) in # SectionProxy; the stubs should be kept updated together @overload @@ -275,7 +300,11 @@ class InterpolationSyntaxError(InterpolationError): ... class ParsingError(Error): source: str errors: list[tuple[int, str]] - def __init__(self, source: str | None = None, filename: str | None = None) -> None: ... + if sys.version_info >= (3, 12): + def __init__(self, source: str) -> None: ... + else: + def __init__(self, source: str | None = None, filename: str | None = None) -> None: ... + def append(self, lineno: int, line: str) -> None: ... class MissingSectionHeaderError(ParsingError): diff --git a/mypy/typeshed/stdlib/contextlib.pyi b/mypy/typeshed/stdlib/contextlib.pyi index feb43aabb039..29ac7cde561a 100644 --- a/mypy/typeshed/stdlib/contextlib.pyi +++ b/mypy/typeshed/stdlib/contextlib.pyi @@ -31,32 +31,33 @@ if sys.version_info >= (3, 11): _T = TypeVar("_T") _T_co = TypeVar("_T_co", covariant=True) _T_io = TypeVar("_T_io", bound=IO[str] | None) +_ExitT_co = TypeVar("_ExitT_co", covariant=True, bound=bool | None, default=bool | None) _F = TypeVar("_F", bound=Callable[..., Any]) _P = ParamSpec("_P") _ExitFunc: TypeAlias = Callable[[type[BaseException] | None, BaseException | None, TracebackType | None], bool | None] -_CM_EF = TypeVar("_CM_EF", bound=AbstractContextManager[Any] | _ExitFunc) +_CM_EF = TypeVar("_CM_EF", bound=AbstractContextManager[Any, Any] | _ExitFunc) @runtime_checkable -class AbstractContextManager(Protocol[_T_co]): +class AbstractContextManager(Protocol[_T_co, _ExitT_co]): def __enter__(self) -> _T_co: ... @abstractmethod def __exit__( - self, __exc_type: type[BaseException] | None, __exc_value: BaseException | None, __traceback: TracebackType | None - ) -> bool | None: ... + self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, / + ) -> _ExitT_co: ... @runtime_checkable -class AbstractAsyncContextManager(Protocol[_T_co]): +class AbstractAsyncContextManager(Protocol[_T_co, _ExitT_co]): async def __aenter__(self) -> _T_co: ... @abstractmethod async def __aexit__( - self, __exc_type: type[BaseException] | None, __exc_value: BaseException | None, __traceback: TracebackType | None - ) -> bool | None: ... + self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, / + ) -> _ExitT_co: ... class ContextDecorator: def __call__(self, func: _F) -> _F: ... -class _GeneratorContextManager(AbstractContextManager[_T_co], ContextDecorator, Generic[_T_co]): +class _GeneratorContextManager(AbstractContextManager[_T_co, bool | None], ContextDecorator): # __init__ and all instance attributes are actually inherited from _GeneratorContextManagerBase # _GeneratorContextManagerBase is more trouble than it's worth to include in the stub; see #6676 def __init__(self, func: Callable[..., Iterator[_T_co]], args: tuple[Any, ...], kwds: dict[str, Any]) -> None: ... @@ -64,9 +65,14 @@ class _GeneratorContextManager(AbstractContextManager[_T_co], ContextDecorator, func: Callable[..., Generator[_T_co, Any, Any]] args: tuple[Any, ...] kwds: dict[str, Any] - def __exit__( - self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None - ) -> bool | None: ... + if sys.version_info >= (3, 9): + def __exit__( + self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None + ) -> bool | None: ... + else: + def __exit__( + self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None + ) -> bool | None: ... def contextmanager(func: Callable[_P, Iterator[_T_co]]) -> Callable[_P, _GeneratorContextManager[_T_co]]: ... @@ -76,7 +82,7 @@ if sys.version_info >= (3, 10): class AsyncContextDecorator: def __call__(self, func: _AF) -> _AF: ... - class _AsyncGeneratorContextManager(AbstractAsyncContextManager[_T_co], AsyncContextDecorator, Generic[_T_co]): + class _AsyncGeneratorContextManager(AbstractAsyncContextManager[_T_co, bool | None], AsyncContextDecorator): # __init__ and these attributes are actually defined in the base class _GeneratorContextManagerBase, # which is more trouble than it's worth to include in the stub (see #6676) def __init__(self, func: Callable[..., AsyncIterator[_T_co]], args: tuple[Any, ...], kwds: dict[str, Any]) -> None: ... @@ -89,7 +95,7 @@ if sys.version_info >= (3, 10): ) -> bool | None: ... else: - class _AsyncGeneratorContextManager(AbstractAsyncContextManager[_T_co], Generic[_T_co]): + class _AsyncGeneratorContextManager(AbstractAsyncContextManager[_T_co, bool | None]): def __init__(self, func: Callable[..., AsyncIterator[_T_co]], args: tuple[Any, ...], kwds: dict[str, Any]) -> None: ... gen: AsyncGenerator[_T_co, Any] func: Callable[..., AsyncGenerator[_T_co, Any]] @@ -106,26 +112,27 @@ class _SupportsClose(Protocol): _SupportsCloseT = TypeVar("_SupportsCloseT", bound=_SupportsClose) -class closing(AbstractContextManager[_SupportsCloseT]): +class closing(AbstractContextManager[_SupportsCloseT, None]): def __init__(self, thing: _SupportsCloseT) -> None: ... def __exit__(self, *exc_info: Unused) -> None: ... if sys.version_info >= (3, 10): class _SupportsAclose(Protocol): def aclose(self) -> Awaitable[object]: ... + _SupportsAcloseT = TypeVar("_SupportsAcloseT", bound=_SupportsAclose) - class aclosing(AbstractAsyncContextManager[_SupportsAcloseT]): + class aclosing(AbstractAsyncContextManager[_SupportsAcloseT, None]): def __init__(self, thing: _SupportsAcloseT) -> None: ... async def __aexit__(self, *exc_info: Unused) -> None: ... -class suppress(AbstractContextManager[None]): +class suppress(AbstractContextManager[None, bool]): def __init__(self, *exceptions: type[BaseException]) -> None: ... def __exit__( self, exctype: type[BaseException] | None, excinst: BaseException | None, exctb: TracebackType | None ) -> bool: ... -class _RedirectStream(AbstractContextManager[_T_io]): +class _RedirectStream(AbstractContextManager[_T_io, None]): def __init__(self, new_target: _T_io) -> None: ... def __exit__( self, exctype: type[BaseException] | None, excinst: BaseException | None, exctb: TracebackType | None @@ -136,66 +143,66 @@ class redirect_stderr(_RedirectStream[_T_io]): ... # In reality this is a subclass of `AbstractContextManager`; # see #7961 for why we don't do that in the stub -class ExitStack(metaclass=abc.ABCMeta): - def enter_context(self, cm: AbstractContextManager[_T]) -> _T: ... +class ExitStack(Generic[_ExitT_co], metaclass=abc.ABCMeta): + def enter_context(self, cm: AbstractContextManager[_T, _ExitT_co]) -> _T: ... def push(self, exit: _CM_EF) -> _CM_EF: ... - def callback(self, __callback: Callable[_P, _T], *args: _P.args, **kwds: _P.kwargs) -> Callable[_P, _T]: ... + def callback(self, callback: Callable[_P, _T], /, *args: _P.args, **kwds: _P.kwargs) -> Callable[_P, _T]: ... def pop_all(self) -> Self: ... def close(self) -> None: ... def __enter__(self) -> Self: ... def __exit__( - self, __exc_type: type[BaseException] | None, __exc_value: BaseException | None, __traceback: TracebackType | None - ) -> bool: ... + self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, / + ) -> _ExitT_co: ... _ExitCoroFunc: TypeAlias = Callable[ [type[BaseException] | None, BaseException | None, TracebackType | None], Awaitable[bool | None] ] -_ACM_EF = TypeVar("_ACM_EF", bound=AbstractAsyncContextManager[Any] | _ExitCoroFunc) +_ACM_EF = TypeVar("_ACM_EF", bound=AbstractAsyncContextManager[Any, Any] | _ExitCoroFunc) # In reality this is a subclass of `AbstractAsyncContextManager`; # see #7961 for why we don't do that in the stub -class AsyncExitStack(metaclass=abc.ABCMeta): - def enter_context(self, cm: AbstractContextManager[_T]) -> _T: ... - async def enter_async_context(self, cm: AbstractAsyncContextManager[_T]) -> _T: ... +class AsyncExitStack(Generic[_ExitT_co], metaclass=abc.ABCMeta): + def enter_context(self, cm: AbstractContextManager[_T, _ExitT_co]) -> _T: ... + async def enter_async_context(self, cm: AbstractAsyncContextManager[_T, _ExitT_co]) -> _T: ... def push(self, exit: _CM_EF) -> _CM_EF: ... def push_async_exit(self, exit: _ACM_EF) -> _ACM_EF: ... - def callback(self, __callback: Callable[_P, _T], *args: _P.args, **kwds: _P.kwargs) -> Callable[_P, _T]: ... + def callback(self, callback: Callable[_P, _T], /, *args: _P.args, **kwds: _P.kwargs) -> Callable[_P, _T]: ... def push_async_callback( - self, __callback: Callable[_P, Awaitable[_T]], *args: _P.args, **kwds: _P.kwargs + self, callback: Callable[_P, Awaitable[_T]], /, *args: _P.args, **kwds: _P.kwargs ) -> Callable[_P, Awaitable[_T]]: ... def pop_all(self) -> Self: ... async def aclose(self) -> None: ... async def __aenter__(self) -> Self: ... async def __aexit__( - self, __exc_type: type[BaseException] | None, __exc_value: BaseException | None, __traceback: TracebackType | None + self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, / ) -> bool: ... if sys.version_info >= (3, 10): - class nullcontext(AbstractContextManager[_T], AbstractAsyncContextManager[_T]): + class nullcontext(AbstractContextManager[_T, None], AbstractAsyncContextManager[_T, None]): enter_result: _T @overload def __init__(self: nullcontext[None], enter_result: None = None) -> None: ... @overload - def __init__(self: nullcontext[_T], enter_result: _T) -> None: ... + def __init__(self: nullcontext[_T], enter_result: _T) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] #11780 def __enter__(self) -> _T: ... def __exit__(self, *exctype: Unused) -> None: ... async def __aenter__(self) -> _T: ... async def __aexit__(self, *exctype: Unused) -> None: ... else: - class nullcontext(AbstractContextManager[_T]): + class nullcontext(AbstractContextManager[_T, None]): enter_result: _T @overload def __init__(self: nullcontext[None], enter_result: None = None) -> None: ... @overload - def __init__(self: nullcontext[_T], enter_result: _T) -> None: ... + def __init__(self: nullcontext[_T], enter_result: _T) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] #11780 def __enter__(self) -> _T: ... def __exit__(self, *exctype: Unused) -> None: ... if sys.version_info >= (3, 11): _T_fd_or_any_path = TypeVar("_T_fd_or_any_path", bound=FileDescriptorOrPath) - class chdir(AbstractContextManager[None], Generic[_T_fd_or_any_path]): + class chdir(AbstractContextManager[None, None], Generic[_T_fd_or_any_path]): path: _T_fd_or_any_path def __init__(self, path: _T_fd_or_any_path) -> None: ... def __enter__(self) -> None: ... diff --git a/mypy/typeshed/stdlib/contextvars.pyi b/mypy/typeshed/stdlib/contextvars.pyi index 266d96bce6ff..ceb9085fa187 100644 --- a/mypy/typeshed/stdlib/contextvars.pyi +++ b/mypy/typeshed/stdlib/contextvars.pyi @@ -1,7 +1,7 @@ import sys from collections.abc import Callable, Iterator, Mapping -from typing import Any, ClassVar, Generic, TypeVar, overload -from typing_extensions import ParamSpec, final +from typing import Any, ClassVar, Generic, TypeVar, final, overload +from typing_extensions import ParamSpec if sys.version_info >= (3, 9): from types import GenericAlias @@ -18,14 +18,17 @@ class ContextVar(Generic[_T]): def __init__(self, name: str) -> None: ... @overload def __init__(self, name: str, *, default: _T) -> None: ... + def __hash__(self) -> int: ... @property def name(self) -> str: ... @overload def get(self) -> _T: ... @overload - def get(self, default: _D | _T) -> _D | _T: ... - def set(self, __value: _T) -> Token[_T]: ... - def reset(self, __token: Token[_T]) -> None: ... + def get(self, default: _T, /) -> _T: ... + @overload + def get(self, default: _D, /) -> _D | _T: ... + def set(self, value: _T, /) -> Token[_T]: ... + def reset(self, token: Token[_T], /) -> None: ... if sys.version_info >= (3, 9): def __class_getitem__(cls, item: Any) -> GenericAlias: ... @@ -47,11 +50,14 @@ def copy_context() -> Context: ... class Context(Mapping[ContextVar[Any], Any]): def __init__(self) -> None: ... @overload - def get(self, __key: ContextVar[_T]) -> _T | None: ... + def get(self, key: ContextVar[_T], default: None = None, /) -> _T | None: ... + @overload + def get(self, key: ContextVar[_T], default: _T, /) -> _T: ... @overload - def get(self, __key: ContextVar[_T], __default: _D) -> _T | _D: ... + def get(self, key: ContextVar[_T], default: _D, /) -> _T | _D: ... def run(self, callable: Callable[_P, _T], *args: _P.args, **kwargs: _P.kwargs) -> _T: ... def copy(self) -> Context: ... - def __getitem__(self, __key: ContextVar[_T]) -> _T: ... + def __getitem__(self, key: ContextVar[_T], /) -> _T: ... def __iter__(self) -> Iterator[ContextVar[Any]]: ... def __len__(self) -> int: ... + def __eq__(self, value: object, /) -> bool: ... diff --git a/mypy/typeshed/stdlib/copy.pyi b/mypy/typeshed/stdlib/copy.pyi index f68965d3dc91..8a2dcc508e5d 100644 --- a/mypy/typeshed/stdlib/copy.pyi +++ b/mypy/typeshed/stdlib/copy.pyi @@ -8,7 +8,7 @@ _T = TypeVar("_T") PyStringMap: Any # Note: memo and _nil are internal kwargs. -def deepcopy(x: _T, memo: dict[int, Any] | None = None, _nil: Any = ...) -> _T: ... +def deepcopy(x: _T, memo: dict[int, Any] | None = None, _nil: Any = []) -> _T: ... def copy(x: _T) -> _T: ... class Error(Exception): ... diff --git a/mypy/typeshed/stdlib/csv.pyi b/mypy/typeshed/stdlib/csv.pyi index 234b189fb3db..56f8bf029b12 100644 --- a/mypy/typeshed/stdlib/csv.pyi +++ b/mypy/typeshed/stdlib/csv.pyi @@ -21,15 +21,14 @@ from _csv import ( unregister_dialect as unregister_dialect, writer as writer, ) + +if sys.version_info >= (3, 12): + from _csv import QUOTE_NOTNULL as QUOTE_NOTNULL, QUOTE_STRINGS as QUOTE_STRINGS + from _typeshed import SupportsWrite from collections.abc import Collection, Iterable, Iterator, Mapping, Sequence -from typing import Any, Generic, TypeVar, overload -from typing_extensions import Literal, Self - -if sys.version_info >= (3, 8): - from builtins import dict as _DictReadMapping -else: - from collections import OrderedDict as _DictReadMapping +from typing import Any, Generic, Literal, TypeVar, overload +from typing_extensions import Self if sys.version_info >= (3, 12): from types import GenericAlias @@ -57,6 +56,8 @@ __all__ = [ "DictWriter", "unix_dialect", ] +if sys.version_info >= (3, 12): + __all__ += ["QUOTE_STRINGS", "QUOTE_NOTNULL"] _T = TypeVar("_T") @@ -64,10 +65,10 @@ class excel(Dialect): ... class excel_tab(excel): ... class unix_dialect(Dialect): ... -class DictReader(Generic[_T], Iterator[_DictReadMapping[_T | Any, str | Any]]): +class DictReader(Iterator[dict[_T | Any, str | Any]], Generic[_T]): fieldnames: Sequence[_T] | None - restkey: str | None - restval: str | None + restkey: _T | None + restval: str | Any | None reader: _reader dialect: _DialectLike line_num: int @@ -76,18 +77,18 @@ class DictReader(Generic[_T], Iterator[_DictReadMapping[_T | Any, str | Any]]): self, f: Iterable[str], fieldnames: Sequence[_T], - restkey: str | None = None, - restval: str | None = None, + restkey: _T | None = None, + restval: str | Any | None = None, dialect: _DialectLike = "excel", *, - delimiter: str = ..., - quotechar: str | None = ..., - escapechar: str | None = ..., - doublequote: bool = ..., - skipinitialspace: bool = ..., - lineterminator: str = ..., - quoting: _QuotingType = ..., - strict: bool = ..., + delimiter: str = ",", + quotechar: str | None = '"', + escapechar: str | None = None, + doublequote: bool = True, + skipinitialspace: bool = False, + lineterminator: str = "\r\n", + quoting: _QuotingType = 0, + strict: bool = False, ) -> None: ... @overload def __init__( @@ -98,17 +99,17 @@ class DictReader(Generic[_T], Iterator[_DictReadMapping[_T | Any, str | Any]]): restval: str | None = None, dialect: _DialectLike = "excel", *, - delimiter: str = ..., - quotechar: str | None = ..., - escapechar: str | None = ..., - doublequote: bool = ..., - skipinitialspace: bool = ..., - lineterminator: str = ..., - quoting: _QuotingType = ..., - strict: bool = ..., + delimiter: str = ",", + quotechar: str | None = '"', + escapechar: str | None = None, + doublequote: bool = True, + skipinitialspace: bool = False, + lineterminator: str = "\r\n", + quoting: _QuotingType = 0, + strict: bool = False, ) -> None: ... def __iter__(self) -> Self: ... - def __next__(self) -> _DictReadMapping[_T | Any, str | Any]: ... + def __next__(self) -> dict[_T | Any, str | Any]: ... if sys.version_info >= (3, 12): def __class_getitem__(cls, item: Any) -> GenericAlias: ... @@ -125,20 +126,16 @@ class DictWriter(Generic[_T]): extrasaction: Literal["raise", "ignore"] = "raise", dialect: _DialectLike = "excel", *, - delimiter: str = ..., - quotechar: str | None = ..., - escapechar: str | None = ..., - doublequote: bool = ..., - skipinitialspace: bool = ..., - lineterminator: str = ..., - quoting: _QuotingType = ..., - strict: bool = ..., + delimiter: str = ",", + quotechar: str | None = '"', + escapechar: str | None = None, + doublequote: bool = True, + skipinitialspace: bool = False, + lineterminator: str = "\r\n", + quoting: _QuotingType = 0, + strict: bool = False, ) -> None: ... - if sys.version_info >= (3, 8): - def writeheader(self) -> Any: ... - else: - def writeheader(self) -> None: ... - + def writeheader(self) -> Any: ... def writerow(self, rowdict: Mapping[_T, Any]) -> Any: ... def writerows(self, rowdicts: Iterable[Mapping[_T, Any]]) -> None: ... if sys.version_info >= (3, 12): diff --git a/mypy/typeshed/stdlib/ctypes/__init__.pyi b/mypy/typeshed/stdlib/ctypes/__init__.pyi index 497e2f7db70b..2fe551fa9dc2 100644 --- a/mypy/typeshed/stdlib/ctypes/__init__.pyi +++ b/mypy/typeshed/stdlib/ctypes/__init__.pyi @@ -1,46 +1,66 @@ import sys -from _ctypes import RTLD_GLOBAL as RTLD_GLOBAL, RTLD_LOCAL as RTLD_LOCAL -from _typeshed import ReadableBuffer, WriteableBuffer -from abc import abstractmethod -from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence -from typing import Any, ClassVar, Generic, TypeVar, overload -from typing_extensions import Self, TypeAlias +from _ctypes import ( + POINTER as POINTER, + RTLD_GLOBAL as RTLD_GLOBAL, + RTLD_LOCAL as RTLD_LOCAL, + Array as Array, + CFuncPtr as _CFuncPtr, + Structure as Structure, + Union as Union, + _CanCastTo as _CanCastTo, + _CArgObject as _CArgObject, + _CData as _CData, + _CDataMeta as _CDataMeta, + _CField as _CField, + _Pointer as _Pointer, + _PointerLike as _PointerLike, + _SimpleCData as _SimpleCData, + _StructUnionBase as _StructUnionBase, + _StructUnionMeta as _StructUnionMeta, + addressof as addressof, + alignment as alignment, + byref as byref, + get_errno as get_errno, + pointer as pointer, + resize as resize, + set_errno as set_errno, + sizeof as sizeof, +) +from ctypes._endian import BigEndianStructure as BigEndianStructure, LittleEndianStructure as LittleEndianStructure +from typing import Any, ClassVar, Generic, TypeVar +from typing_extensions import TypeAlias + +if sys.platform == "win32": + from _ctypes import FormatError as FormatError, get_last_error as get_last_error, set_last_error as set_last_error + +if sys.version_info >= (3, 11): + from ctypes._endian import BigEndianUnion as BigEndianUnion, LittleEndianUnion as LittleEndianUnion if sys.version_info >= (3, 9): from types import GenericAlias _T = TypeVar("_T") _DLLT = TypeVar("_DLLT", bound=CDLL) -_CT = TypeVar("_CT", bound=_CData) DEFAULT_MODE: int +class ArgumentError(Exception): ... + class CDLL: _func_flags_: ClassVar[int] _func_restype_: ClassVar[_CData] _name: str _handle: int _FuncPtr: type[_FuncPointer] - if sys.version_info >= (3, 8): - def __init__( - self, - name: str | None, - mode: int = ..., - handle: int | None = None, - use_errno: bool = False, - use_last_error: bool = False, - winmode: int | None = None, - ) -> None: ... - else: - def __init__( - self, - name: str | None, - mode: int = ..., - handle: int | None = None, - use_errno: bool = False, - use_last_error: bool = False, - ) -> None: ... - + def __init__( + self, + name: str | None, + mode: int = ..., + handle: int | None = None, + use_errno: bool = False, + use_last_error: bool = False, + winmode: int | None = None, + ) -> None: ... def __getattr__(self, name: str) -> _NamedFuncPointer: ... def __getitem__(self, name_or_ordinal: str) -> _NamedFuncPointer: ... @@ -65,53 +85,11 @@ if sys.platform == "win32": pydll: LibraryLoader[PyDLL] pythonapi: PyDLL -class _CDataMeta(type): - # By default mypy complains about the following two methods, because strictly speaking cls - # might not be a Type[_CT]. However this can never actually happen, because the only class that - # uses _CDataMeta as its metaclass is _CData. So it's safe to ignore the errors here. - def __mul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] - def __rmul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] - -class _CData(metaclass=_CDataMeta): - _b_base_: int - _b_needsfree_: bool - _objects: Mapping[Any, int] | None - @classmethod - def from_buffer(cls, source: WriteableBuffer, offset: int = ...) -> Self: ... - @classmethod - def from_buffer_copy(cls, source: ReadableBuffer, offset: int = ...) -> Self: ... - @classmethod - def from_address(cls, address: int) -> Self: ... - @classmethod - def from_param(cls, obj: Any) -> Self | _CArgObject: ... - @classmethod - def in_dll(cls, library: CDLL, name: str) -> Self: ... - -class _CanCastTo(_CData): ... -class _PointerLike(_CanCastTo): ... - -_ECT: TypeAlias = Callable[[type[_CData] | None, _FuncPointer, tuple[_CData, ...]], _CData] -_PF: TypeAlias = tuple[int] | tuple[int, str] | tuple[int, str, Any] - -class _FuncPointer(_PointerLike, _CData): - restype: type[_CData] | Callable[[int], Any] | None - argtypes: Sequence[type[_CData]] - errcheck: _ECT - @overload - def __init__(self, address: int) -> None: ... - @overload - def __init__(self, callable: Callable[..., Any]) -> None: ... - @overload - def __init__(self, func_spec: tuple[str | int, CDLL], paramflags: tuple[_PF, ...] = ...) -> None: ... - @overload - def __init__(self, vtlb_index: int, name: str, paramflags: tuple[_PF, ...] = ..., iid: _Pointer[c_int] = ...) -> None: ... - def __call__(self, *args: Any, **kwargs: Any) -> Any: ... +class _FuncPointer(_CFuncPtr): ... class _NamedFuncPointer(_FuncPointer): __name__: str -class ArgumentError(Exception): ... - def CFUNCTYPE( restype: type[_CData] | None, *argtypes: type[_CData], use_errno: bool = ..., use_last_error: bool = ... ) -> type[_FuncPointer]: ... @@ -123,8 +101,6 @@ if sys.platform == "win32": def PYFUNCTYPE(restype: type[_CData] | None, *argtypes: type[_CData]) -> type[_FuncPointer]: ... -class _CArgObject: ... - # Any type that can be implicitly converted to c_void_p when passed as a C function argument. # (bytes is not included here, see below.) _CVoidPLike: TypeAlias = _PointerLike | Array[Any] | _CArgObject | int @@ -134,10 +110,6 @@ _CVoidPLike: TypeAlias = _PointerLike | Array[Any] | _CArgObject | int # when memmove(buf, b'foo', 4) was intended. _CVoidConstPLike: TypeAlias = _CVoidPLike | bytes -def addressof(obj: _CData) -> int: ... -def alignment(obj_or_type: _CData | type[_CData]) -> int: ... -def byref(obj: _CData, offset: int = ...) -> _CArgObject: ... - _CastT = TypeVar("_CastT", bound=_CanCastTo) def cast(obj: _CData | _CArgObject | int, typ: type[_CastT]) -> _CastT: ... @@ -150,39 +122,10 @@ def create_unicode_buffer(init: int | str, size: int | None = None) -> Array[c_w if sys.platform == "win32": def DllCanUnloadNow() -> int: ... def DllGetClassObject(rclsid: Any, riid: Any, ppv: Any) -> int: ... # TODO not documented - def FormatError(code: int = ...) -> str: ... def GetLastError() -> int: ... -def get_errno() -> int: ... - -if sys.platform == "win32": - def get_last_error() -> int: ... - def memmove(dst: _CVoidPLike, src: _CVoidConstPLike, count: int) -> int: ... def memset(dst: _CVoidPLike, c: int, count: int) -> int: ... -def POINTER(type: type[_CT]) -> type[_Pointer[_CT]]: ... - -class _Pointer(Generic[_CT], _PointerLike, _CData): - _type_: type[_CT] - contents: _CT - @overload - def __init__(self) -> None: ... - @overload - def __init__(self, arg: _CT) -> None: ... - @overload - def __getitem__(self, __i: int) -> Any: ... - @overload - def __getitem__(self, __s: slice) -> list[Any]: ... - def __setitem__(self, __i: int, __o: Any) -> None: ... - -def pointer(__arg: _CT) -> _Pointer[_CT]: ... -def resize(obj: _CData, size: int) -> None: ... -def set_errno(value: int) -> int: ... - -if sys.platform == "win32": - def set_last_error(value: int) -> int: ... - -def sizeof(obj_or_type: _CData | type[_CData]) -> int: ... def string_at(address: _CVoidConstPLike, size: int = -1) -> bytes: ... if sys.platform == "win32": @@ -190,12 +133,6 @@ if sys.platform == "win32": def wstring_at(address: _CVoidConstPLike, size: int = -1) -> str: ... -class _SimpleCData(Generic[_T], _CData): - value: _T - # The TypeVar can be unsolved here, - # but we can't use overloads without creating many, many mypy false-positive errors - def __init__(self, value: _T = ...) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] - class c_byte(_SimpleCData[int]): ... class c_char(_SimpleCData[bytes]): @@ -205,30 +142,36 @@ class c_char_p(_PointerLike, _SimpleCData[bytes | None]): def __init__(self, value: int | bytes | None = ...) -> None: ... class c_double(_SimpleCData[float]): ... -class c_longdouble(_SimpleCData[float]): ... +class c_longdouble(_SimpleCData[float]): ... # can be an alias for c_double class c_float(_SimpleCData[float]): ... -class c_int(_SimpleCData[int]): ... -class c_int8(_SimpleCData[int]): ... -class c_int16(_SimpleCData[int]): ... -class c_int32(_SimpleCData[int]): ... -class c_int64(_SimpleCData[int]): ... +class c_int(_SimpleCData[int]): ... # can be an alias for c_long class c_long(_SimpleCData[int]): ... -class c_longlong(_SimpleCData[int]): ... +class c_longlong(_SimpleCData[int]): ... # can be an alias for c_long class c_short(_SimpleCData[int]): ... -class c_size_t(_SimpleCData[int]): ... -class c_ssize_t(_SimpleCData[int]): ... +class c_size_t(_SimpleCData[int]): ... # alias for c_uint, c_ulong, or c_ulonglong +class c_ssize_t(_SimpleCData[int]): ... # alias for c_int, c_long, or c_longlong class c_ubyte(_SimpleCData[int]): ... -class c_uint(_SimpleCData[int]): ... -class c_uint8(_SimpleCData[int]): ... -class c_uint16(_SimpleCData[int]): ... -class c_uint32(_SimpleCData[int]): ... -class c_uint64(_SimpleCData[int]): ... +class c_uint(_SimpleCData[int]): ... # can be an alias for c_ulong class c_ulong(_SimpleCData[int]): ... -class c_ulonglong(_SimpleCData[int]): ... +class c_ulonglong(_SimpleCData[int]): ... # can be an alias for c_ulong class c_ushort(_SimpleCData[int]): ... class c_void_p(_PointerLike, _SimpleCData[int | None]): ... class c_wchar(_SimpleCData[str]): ... +c_int8 = c_byte + +# these are actually dynamic aliases for c_short, c_int, c_long, or c_longlong +class c_int16(_SimpleCData[int]): ... +class c_int32(_SimpleCData[int]): ... +class c_int64(_SimpleCData[int]): ... + +c_uint8 = c_ubyte + +# these are actually dynamic aliases for c_ushort, c_uint, c_ulong, or c_ulonglong +class c_uint16(_SimpleCData[int]): ... +class c_uint32(_SimpleCData[int]): ... +class c_uint64(_SimpleCData[int]): ... + class c_wchar_p(_PointerLike, _SimpleCData[str | None]): def __init__(self, value: int | str | None = ...) -> None: ... @@ -238,65 +181,7 @@ class c_bool(_SimpleCData[bool]): if sys.platform == "win32": class HRESULT(_SimpleCData[int]): ... # TODO undocumented -class py_object(_CanCastTo, _SimpleCData[_T]): ... +if sys.version_info >= (3, 12): + c_time_t: type[c_int32 | c_int64] # alias for one or the other at runtime -class _CField: - offset: int - size: int - -class _StructUnionMeta(_CDataMeta): - _fields_: Sequence[tuple[str, type[_CData]] | tuple[str, type[_CData], int]] - _pack_: int - _anonymous_: Sequence[str] - def __getattr__(self, name: str) -> _CField: ... - -class _StructUnionBase(_CData, metaclass=_StructUnionMeta): - def __init__(self, *args: Any, **kw: Any) -> None: ... - def __getattr__(self, name: str) -> Any: ... - def __setattr__(self, name: str, value: Any) -> None: ... - -class Union(_StructUnionBase): ... -class Structure(_StructUnionBase): ... -class BigEndianStructure(Structure): ... -class LittleEndianStructure(Structure): ... - -class Array(Generic[_CT], _CData): - @property - @abstractmethod - def _length_(self) -> int: ... - @_length_.setter - def _length_(self, value: int) -> None: ... - @property - @abstractmethod - def _type_(self) -> type[_CT]: ... - @_type_.setter - def _type_(self, value: type[_CT]) -> None: ... - raw: bytes # Note: only available if _CT == c_char - value: Any # Note: bytes if _CT == c_char, str if _CT == c_wchar, unavailable otherwise - # TODO These methods cannot be annotated correctly at the moment. - # All of these "Any"s stand for the array's element type, but it's not possible to use _CT - # here, because of a special feature of ctypes. - # By default, when accessing an element of an Array[_CT], the returned object has type _CT. - # However, when _CT is a "simple type" like c_int, ctypes automatically "unboxes" the object - # and converts it to the corresponding Python primitive. For example, when accessing an element - # of an Array[c_int], a Python int object is returned, not a c_int. - # This behavior does *not* apply to subclasses of "simple types". - # If MyInt is a subclass of c_int, then accessing an element of an Array[MyInt] returns - # a MyInt, not an int. - # This special behavior is not easy to model in a stub, so for now all places where - # the array element type would belong are annotated with Any instead. - def __init__(self, *args: Any) -> None: ... - @overload - def __getitem__(self, __i: int) -> Any: ... - @overload - def __getitem__(self, __s: slice) -> list[Any]: ... - @overload - def __setitem__(self, __i: int, __o: Any) -> None: ... - @overload - def __setitem__(self, __s: slice, __o: Iterable[Any]) -> None: ... - def __iter__(self) -> Iterator[Any]: ... - # Can't inherit from Sized because the metaclass conflict between - # Sized and _CData prevents using _CDataMeta. - def __len__(self) -> int: ... - if sys.version_info >= (3, 9): - def __class_getitem__(cls, item: Any) -> GenericAlias: ... +class py_object(_CanCastTo, _SimpleCData[_T]): ... diff --git a/mypy/typeshed/stdlib/ctypes/_endian.pyi b/mypy/typeshed/stdlib/ctypes/_endian.pyi new file mode 100644 index 000000000000..add6365e615f --- /dev/null +++ b/mypy/typeshed/stdlib/ctypes/_endian.pyi @@ -0,0 +1,19 @@ +import sys +from _ctypes import RTLD_GLOBAL as RTLD_GLOBAL, RTLD_LOCAL as RTLD_LOCAL, Structure, Union +from ctypes import DEFAULT_MODE as DEFAULT_MODE, cdll as cdll, pydll as pydll, pythonapi as pythonapi + +if sys.version_info >= (3, 12): + from _ctypes import SIZEOF_TIME_T as SIZEOF_TIME_T + +if sys.platform == "win32": + from ctypes import oledll as oledll, windll as windll + +# At runtime, the native endianness is an alias for Structure, +# while the other is a subclass with a metaclass added in. +class BigEndianStructure(Structure): ... +class LittleEndianStructure(Structure): ... + +# Same thing for these: one is an alias of Union at runtime +if sys.version_info >= (3, 11): + class BigEndianUnion(Union): ... + class LittleEndianUnion(Union): ... diff --git a/mypy/typeshed/stdlib/ctypes/wintypes.pyi b/mypy/typeshed/stdlib/ctypes/wintypes.pyi index 3bd27934750a..8847860f2002 100644 --- a/mypy/typeshed/stdlib/ctypes/wintypes.pyi +++ b/mypy/typeshed/stdlib/ctypes/wintypes.pyi @@ -1,6 +1,7 @@ from ctypes import ( Array, Structure, + _CField, _Pointer, _SimpleCData, c_byte, @@ -20,6 +21,7 @@ from ctypes import ( c_wchar, c_wchar_p, ) +from typing import TypeVar from typing_extensions import TypeAlias BYTE = c_byte @@ -101,39 +103,42 @@ HWND = HANDLE SC_HANDLE = HANDLE SERVICE_STATUS_HANDLE = HANDLE +_CIntLikeT = TypeVar("_CIntLikeT", bound=_SimpleCData[int]) +_CIntLikeField: TypeAlias = _CField[_CIntLikeT, int, _CIntLikeT | int] + class RECT(Structure): - left: LONG - top: LONG - right: LONG - bottom: LONG + left: _CIntLikeField[LONG] + top: _CIntLikeField[LONG] + right: _CIntLikeField[LONG] + bottom: _CIntLikeField[LONG] RECTL = RECT _RECTL = RECT tagRECT = RECT class _SMALL_RECT(Structure): - Left: SHORT - Top: SHORT - Right: SHORT - Bottom: SHORT + Left: _CIntLikeField[SHORT] + Top: _CIntLikeField[SHORT] + Right: _CIntLikeField[SHORT] + Bottom: _CIntLikeField[SHORT] SMALL_RECT = _SMALL_RECT class _COORD(Structure): - X: SHORT - Y: SHORT + X: _CIntLikeField[SHORT] + Y: _CIntLikeField[SHORT] class POINT(Structure): - x: LONG - y: LONG + x: _CIntLikeField[LONG] + y: _CIntLikeField[LONG] POINTL = POINT _POINTL = POINT tagPOINT = POINT class SIZE(Structure): - cx: LONG - cy: LONG + cx: _CIntLikeField[LONG] + cy: _CIntLikeField[LONG] SIZEL = SIZE tagSIZE = SIZE @@ -141,95 +146,153 @@ tagSIZE = SIZE def RGB(red: int, green: int, blue: int) -> int: ... class FILETIME(Structure): - dwLowDateTime: DWORD - dwHighDateTime: DWORD + dwLowDateTime: _CIntLikeField[DWORD] + dwHighDateTime: _CIntLikeField[DWORD] _FILETIME = FILETIME class MSG(Structure): - hWnd: HWND - message: UINT - wParam: WPARAM - lParam: LPARAM - time: DWORD - pt: POINT + hWnd: _CField[HWND, int | None, HWND | int | None] + message: _CIntLikeField[UINT] + wParam: _CIntLikeField[WPARAM] + lParam: _CIntLikeField[LPARAM] + time: _CIntLikeField[DWORD] + pt: _CField[POINT, POINT, POINT] tagMSG = MSG MAX_PATH: int class WIN32_FIND_DATAA(Structure): - dwFileAttributes: DWORD - ftCreationTime: FILETIME - ftLastAccessTime: FILETIME - ftLastWriteTime: FILETIME - nFileSizeHigh: DWORD - nFileSizeLow: DWORD - dwReserved0: DWORD - dwReserved1: DWORD - cFileName: Array[CHAR] - cAlternateFileName: Array[CHAR] + dwFileAttributes: _CIntLikeField[DWORD] + ftCreationTime: _CField[FILETIME, FILETIME, FILETIME] + ftLastAccessTime: _CField[FILETIME, FILETIME, FILETIME] + ftLastWriteTime: _CField[FILETIME, FILETIME, FILETIME] + nFileSizeHigh: _CIntLikeField[DWORD] + nFileSizeLow: _CIntLikeField[DWORD] + dwReserved0: _CIntLikeField[DWORD] + dwReserved1: _CIntLikeField[DWORD] + cFileName: _CField[Array[CHAR], bytes, bytes] + cAlternateFileName: _CField[Array[CHAR], bytes, bytes] class WIN32_FIND_DATAW(Structure): - dwFileAttributes: DWORD - ftCreationTime: FILETIME - ftLastAccessTime: FILETIME - ftLastWriteTime: FILETIME - nFileSizeHigh: DWORD - nFileSizeLow: DWORD - dwReserved0: DWORD - dwReserved1: DWORD - cFileName: Array[WCHAR] - cAlternateFileName: Array[WCHAR] - -# These pointer type definitions use _Pointer[...] instead of POINTER(...), to allow them -# to be used in type annotations. -PBOOL: TypeAlias = _Pointer[BOOL] -LPBOOL: TypeAlias = _Pointer[BOOL] -PBOOLEAN: TypeAlias = _Pointer[BOOLEAN] -PBYTE: TypeAlias = _Pointer[BYTE] -LPBYTE: TypeAlias = _Pointer[BYTE] -PCHAR: TypeAlias = _Pointer[CHAR] -LPCOLORREF: TypeAlias = _Pointer[COLORREF] -PDWORD: TypeAlias = _Pointer[DWORD] -LPDWORD: TypeAlias = _Pointer[DWORD] -PFILETIME: TypeAlias = _Pointer[FILETIME] -LPFILETIME: TypeAlias = _Pointer[FILETIME] -PFLOAT: TypeAlias = _Pointer[FLOAT] -PHANDLE: TypeAlias = _Pointer[HANDLE] -LPHANDLE: TypeAlias = _Pointer[HANDLE] -PHKEY: TypeAlias = _Pointer[HKEY] -LPHKL: TypeAlias = _Pointer[HKL] -PINT: TypeAlias = _Pointer[INT] -LPINT: TypeAlias = _Pointer[INT] -PLARGE_INTEGER: TypeAlias = _Pointer[LARGE_INTEGER] -PLCID: TypeAlias = _Pointer[LCID] -PLONG: TypeAlias = _Pointer[LONG] -LPLONG: TypeAlias = _Pointer[LONG] -PMSG: TypeAlias = _Pointer[MSG] -LPMSG: TypeAlias = _Pointer[MSG] -PPOINT: TypeAlias = _Pointer[POINT] -LPPOINT: TypeAlias = _Pointer[POINT] -PPOINTL: TypeAlias = _Pointer[POINTL] -PRECT: TypeAlias = _Pointer[RECT] -LPRECT: TypeAlias = _Pointer[RECT] -PRECTL: TypeAlias = _Pointer[RECTL] -LPRECTL: TypeAlias = _Pointer[RECTL] -LPSC_HANDLE: TypeAlias = _Pointer[SC_HANDLE] -PSHORT: TypeAlias = _Pointer[SHORT] -PSIZE: TypeAlias = _Pointer[SIZE] -LPSIZE: TypeAlias = _Pointer[SIZE] -PSIZEL: TypeAlias = _Pointer[SIZEL] -LPSIZEL: TypeAlias = _Pointer[SIZEL] -PSMALL_RECT: TypeAlias = _Pointer[SMALL_RECT] -PUINT: TypeAlias = _Pointer[UINT] -LPUINT: TypeAlias = _Pointer[UINT] -PULARGE_INTEGER: TypeAlias = _Pointer[ULARGE_INTEGER] -PULONG: TypeAlias = _Pointer[ULONG] -PUSHORT: TypeAlias = _Pointer[USHORT] -PWCHAR: TypeAlias = _Pointer[WCHAR] -PWIN32_FIND_DATAA: TypeAlias = _Pointer[WIN32_FIND_DATAA] -LPWIN32_FIND_DATAA: TypeAlias = _Pointer[WIN32_FIND_DATAA] -PWIN32_FIND_DATAW: TypeAlias = _Pointer[WIN32_FIND_DATAW] -LPWIN32_FIND_DATAW: TypeAlias = _Pointer[WIN32_FIND_DATAW] -PWORD: TypeAlias = _Pointer[WORD] -LPWORD: TypeAlias = _Pointer[WORD] + dwFileAttributes: _CIntLikeField[DWORD] + ftCreationTime: _CField[FILETIME, FILETIME, FILETIME] + ftLastAccessTime: _CField[FILETIME, FILETIME, FILETIME] + ftLastWriteTime: _CField[FILETIME, FILETIME, FILETIME] + nFileSizeHigh: _CIntLikeField[DWORD] + nFileSizeLow: _CIntLikeField[DWORD] + dwReserved0: _CIntLikeField[DWORD] + dwReserved1: _CIntLikeField[DWORD] + cFileName: _CField[Array[WCHAR], str, str] + cAlternateFileName: _CField[Array[WCHAR], str, str] + +# These are all defined with the POINTER() function, which keeps a cache and will +# return a previously created class if it can. The self-reported __name__ +# of these classes is f"LP_{typ.__name__}", where typ is the original class +# passed in to the POINTER() function. + +# LP_c_short +class PSHORT(_Pointer[SHORT]): ... + +# LP_c_ushort +class PUSHORT(_Pointer[USHORT]): ... + +PWORD = PUSHORT +LPWORD = PUSHORT + +# LP_c_long +class PLONG(_Pointer[LONG]): ... + +LPLONG = PLONG +PBOOL = PLONG +LPBOOL = PLONG + +# LP_c_ulong +class PULONG(_Pointer[ULONG]): ... + +PDWORD = PULONG +LPDWORD = PDWORD +LPCOLORREF = PDWORD +PLCID = PDWORD + +# LP_c_int (or LP_c_long if int and long have the same size) +class PINT(_Pointer[INT]): ... + +LPINT = PINT + +# LP_c_uint (or LP_c_ulong if int and long have the same size) +class PUINT(_Pointer[UINT]): ... + +LPUINT = PUINT + +# LP_c_float +class PFLOAT(_Pointer[FLOAT]): ... + +# LP_c_longlong (or LP_c_long if long and long long have the same size) +class PLARGE_INTEGER(_Pointer[LARGE_INTEGER]): ... + +# LP_c_ulonglong (or LP_c_ulong if long and long long have the same size) +class PULARGE_INTEGER(_Pointer[ULARGE_INTEGER]): ... + +# LP_c_byte types +class PBYTE(_Pointer[BYTE]): ... + +LPBYTE = PBYTE +PBOOLEAN = PBYTE + +# LP_c_char +class PCHAR(_Pointer[CHAR]): ... + +# LP_c_wchar +class PWCHAR(_Pointer[WCHAR]): ... + +# LP_c_void_p +class PHANDLE(_Pointer[HANDLE]): ... + +LPHANDLE = PHANDLE +PHKEY = PHANDLE +LPHKL = PHANDLE +LPSC_HANDLE = PHANDLE + +# LP_FILETIME +class PFILETIME(_Pointer[FILETIME]): ... + +LPFILETIME = PFILETIME + +# LP_MSG +class PMSG(_Pointer[MSG]): ... + +LPMSG = PMSG + +# LP_POINT +class PPOINT(_Pointer[POINT]): ... + +LPPOINT = PPOINT +PPOINTL = PPOINT + +# LP_RECT +class PRECT(_Pointer[RECT]): ... + +LPRECT = PRECT +PRECTL = PRECT +LPRECTL = PRECT + +# LP_SIZE +class PSIZE(_Pointer[SIZE]): ... + +LPSIZE = PSIZE +PSIZEL = PSIZE +LPSIZEL = PSIZE + +# LP__SMALL_RECT +class PSMALL_RECT(_Pointer[SMALL_RECT]): ... + +# LP_WIN32_FIND_DATAA +class PWIN32_FIND_DATAA(_Pointer[WIN32_FIND_DATAA]): ... + +LPWIN32_FIND_DATAA = PWIN32_FIND_DATAA + +# LP_WIN32_FIND_DATAW +class PWIN32_FIND_DATAW(_Pointer[WIN32_FIND_DATAW]): ... + +LPWIN32_FIND_DATAW = PWIN32_FIND_DATAW diff --git a/mypy/typeshed/stdlib/curses/__init__.pyi b/mypy/typeshed/stdlib/curses/__init__.pyi index db44fa6a6be7..1df184dbaa60 100644 --- a/mypy/typeshed/stdlib/curses/__init__.pyi +++ b/mypy/typeshed/stdlib/curses/__init__.pyi @@ -1,21 +1,22 @@ -import sys +from _curses import * +from _curses import _CursesWindow as _CursesWindow from collections.abc import Callable from typing import TypeVar from typing_extensions import Concatenate, ParamSpec -if sys.platform != "win32": - from _curses import * - from _curses import _CursesWindow as _CursesWindow +# NOTE: The _curses module is ordinarily only available on Unix, but the +# windows-curses package makes it available on Windows as well with the same +# contents. - _T = TypeVar("_T") - _P = ParamSpec("_P") +_T = TypeVar("_T") +_P = ParamSpec("_P") - # available after calling `curses.initscr()` - LINES: int - COLS: int +# available after calling `curses.initscr()` +LINES: int +COLS: int - # available after calling `curses.start_color()` - COLORS: int - COLOR_PAIRS: int +# available after calling `curses.start_color()` +COLORS: int +COLOR_PAIRS: int - def wrapper(__func: Callable[Concatenate[_CursesWindow, _P], _T], *arg: _P.args, **kwds: _P.kwargs) -> _T: ... +def wrapper(func: Callable[Concatenate[_CursesWindow, _P], _T], /, *arg: _P.args, **kwds: _P.kwargs) -> _T: ... diff --git a/mypy/typeshed/stdlib/curses/ascii.pyi b/mypy/typeshed/stdlib/curses/ascii.pyi index 25de8f605bda..66efbe36a7df 100644 --- a/mypy/typeshed/stdlib/curses/ascii.pyi +++ b/mypy/typeshed/stdlib/curses/ascii.pyi @@ -1,63 +1,62 @@ -import sys from typing import TypeVar -if sys.platform != "win32": - _CharT = TypeVar("_CharT", str, int) +_CharT = TypeVar("_CharT", str, int) - NUL: int - SOH: int - STX: int - ETX: int - EOT: int - ENQ: int - ACK: int - BEL: int - BS: int - TAB: int - HT: int - LF: int - NL: int - VT: int - FF: int - CR: int - SO: int - SI: int - DLE: int - DC1: int - DC2: int - DC3: int - DC4: int - NAK: int - SYN: int - ETB: int - CAN: int - EM: int - SUB: int - ESC: int - FS: int - GS: int - RS: int - US: int - SP: int - DEL: int +NUL: int +SOH: int +STX: int +ETX: int +EOT: int +ENQ: int +ACK: int +BEL: int +BS: int +TAB: int +HT: int +LF: int +NL: int +VT: int +FF: int +CR: int +SO: int +SI: int +DLE: int +DC1: int +DC2: int +DC3: int +DC4: int +NAK: int +SYN: int +ETB: int +CAN: int +EM: int +SUB: int +ESC: int +FS: int +GS: int +RS: int +US: int +SP: int +DEL: int - controlnames: list[int] - def isalnum(c: str | int) -> bool: ... - def isalpha(c: str | int) -> bool: ... - def isascii(c: str | int) -> bool: ... - def isblank(c: str | int) -> bool: ... - def iscntrl(c: str | int) -> bool: ... - def isdigit(c: str | int) -> bool: ... - def isgraph(c: str | int) -> bool: ... - def islower(c: str | int) -> bool: ... - def isprint(c: str | int) -> bool: ... - def ispunct(c: str | int) -> bool: ... - def isspace(c: str | int) -> bool: ... - def isupper(c: str | int) -> bool: ... - def isxdigit(c: str | int) -> bool: ... - def isctrl(c: str | int) -> bool: ... - def ismeta(c: str | int) -> bool: ... - def ascii(c: _CharT) -> _CharT: ... - def ctrl(c: _CharT) -> _CharT: ... - def alt(c: _CharT) -> _CharT: ... - def unctrl(c: str | int) -> str: ... +controlnames: list[int] + +def isalnum(c: str | int) -> bool: ... +def isalpha(c: str | int) -> bool: ... +def isascii(c: str | int) -> bool: ... +def isblank(c: str | int) -> bool: ... +def iscntrl(c: str | int) -> bool: ... +def isdigit(c: str | int) -> bool: ... +def isgraph(c: str | int) -> bool: ... +def islower(c: str | int) -> bool: ... +def isprint(c: str | int) -> bool: ... +def ispunct(c: str | int) -> bool: ... +def isspace(c: str | int) -> bool: ... +def isupper(c: str | int) -> bool: ... +def isxdigit(c: str | int) -> bool: ... +def isctrl(c: str | int) -> bool: ... +def ismeta(c: str | int) -> bool: ... +def ascii(c: _CharT) -> _CharT: ... +def ctrl(c: _CharT) -> _CharT: ... +def alt(c: _CharT) -> _CharT: ... +def unctrl(c: str | int) -> str: ... diff --git a/mypy/typeshed/stdlib/curses/has_key.pyi b/mypy/typeshed/stdlib/curses/has_key.pyi index ff728aedf84b..3811060b916a 100644 --- a/mypy/typeshed/stdlib/curses/has_key.pyi +++ b/mypy/typeshed/stdlib/curses/has_key.pyi @@ -1,4 +1 @@ -import sys - -if sys.platform != "win32": - def has_key(ch: int | str) -> bool: ... +def has_key(ch: int | str) -> bool: ... diff --git a/mypy/typeshed/stdlib/curses/panel.pyi b/mypy/typeshed/stdlib/curses/panel.pyi index 30803791f039..3d3448bd9584 100644 --- a/mypy/typeshed/stdlib/curses/panel.pyi +++ b/mypy/typeshed/stdlib/curses/panel.pyi @@ -1,25 +1,22 @@ -import sys +from _curses import _CursesWindow -if sys.platform != "win32": - from _curses import _CursesWindow +version: str - version: str +class _Curses_Panel: # type is (note the space in the class name) + def above(self) -> _Curses_Panel: ... + def below(self) -> _Curses_Panel: ... + def bottom(self) -> None: ... + def hidden(self) -> bool: ... + def hide(self) -> None: ... + def move(self, y: int, x: int) -> None: ... + def replace(self, win: _CursesWindow) -> None: ... + def set_userptr(self, obj: object) -> None: ... + def show(self) -> None: ... + def top(self) -> None: ... + def userptr(self) -> object: ... + def window(self) -> _CursesWindow: ... - class _Curses_Panel: # type is (note the space in the class name) - def above(self) -> _Curses_Panel: ... - def below(self) -> _Curses_Panel: ... - def bottom(self) -> None: ... - def hidden(self) -> bool: ... - def hide(self) -> None: ... - def move(self, y: int, x: int) -> None: ... - def replace(self, win: _CursesWindow) -> None: ... - def set_userptr(self, obj: object) -> None: ... - def show(self) -> None: ... - def top(self) -> None: ... - def userptr(self) -> object: ... - def window(self) -> _CursesWindow: ... - - def bottom_panel() -> _Curses_Panel: ... - def new_panel(__win: _CursesWindow) -> _Curses_Panel: ... - def top_panel() -> _Curses_Panel: ... - def update_panels() -> _Curses_Panel: ... +def bottom_panel() -> _Curses_Panel: ... +def new_panel(win: _CursesWindow, /) -> _Curses_Panel: ... +def top_panel() -> _Curses_Panel: ... +def update_panels() -> _Curses_Panel: ... diff --git a/mypy/typeshed/stdlib/curses/textpad.pyi b/mypy/typeshed/stdlib/curses/textpad.pyi index 4d28b4dfbcdc..ce6eed09b289 100644 --- a/mypy/typeshed/stdlib/curses/textpad.pyi +++ b/mypy/typeshed/stdlib/curses/textpad.pyi @@ -1,13 +1,11 @@ -import sys +from _curses import _CursesWindow from collections.abc import Callable -if sys.platform != "win32": - from _curses import _CursesWindow - def rectangle(win: _CursesWindow, uly: int, ulx: int, lry: int, lrx: int) -> None: ... +def rectangle(win: _CursesWindow, uly: int, ulx: int, lry: int, lrx: int) -> None: ... - class Textbox: - stripspaces: bool - def __init__(self, win: _CursesWindow, insert_mode: bool = False) -> None: ... - def edit(self, validate: Callable[[int], int] | None = None) -> str: ... - def do_command(self, ch: str | int) -> None: ... - def gather(self) -> str: ... +class Textbox: + stripspaces: bool + def __init__(self, win: _CursesWindow, insert_mode: bool = False) -> None: ... + def edit(self, validate: Callable[[int], int] | None = None) -> str: ... + def do_command(self, ch: str | int) -> None: ... + def gather(self) -> str: ... diff --git a/mypy/typeshed/stdlib/dataclasses.pyi b/mypy/typeshed/stdlib/dataclasses.pyi index c02aaabe6196..c361122704a5 100644 --- a/mypy/typeshed/stdlib/dataclasses.pyi +++ b/mypy/typeshed/stdlib/dataclasses.pyi @@ -4,8 +4,8 @@ import types from _typeshed import DataclassInstance from builtins import type as Type # alias to avoid name clashes with fields named "type" from collections.abc import Callable, Iterable, Mapping -from typing import Any, Generic, Protocol, TypeVar, overload -from typing_extensions import Literal, TypeAlias, TypeGuard +from typing import Any, Generic, Literal, Protocol, TypeVar, overload +from typing_extensions import TypeAlias, TypeGuard if sys.version_info >= (3, 9): from types import GenericAlias @@ -54,19 +54,10 @@ def asdict(obj: DataclassInstance, *, dict_factory: Callable[[list[tuple[str, An def astuple(obj: DataclassInstance) -> tuple[Any, ...]: ... @overload def astuple(obj: DataclassInstance, *, tuple_factory: Callable[[list[Any]], _T]) -> _T: ... - -if sys.version_info >= (3, 8): - # cls argument is now positional-only - @overload - def dataclass(__cls: None) -> Callable[[type[_T]], type[_T]]: ... - @overload - def dataclass(__cls: type[_T]) -> type[_T]: ... - -else: - @overload - def dataclass(_cls: None) -> Callable[[type[_T]], type[_T]]: ... - @overload - def dataclass(_cls: type[_T]) -> type[_T]: ... +@overload +def dataclass(cls: None, /) -> Callable[[type[_T]], type[_T]]: ... +@overload +def dataclass(cls: type[_T], /) -> type[_T]: ... if sys.version_info >= (3, 11): @overload @@ -223,7 +214,7 @@ else: def fields(class_or_instance: DataclassInstance | type[DataclassInstance]) -> tuple[Field[Any], ...]: ... @overload -def is_dataclass(obj: DataclassInstance | type[DataclassInstance]) -> Literal[True]: ... +def is_dataclass(obj: DataclassInstance) -> Literal[True]: ... @overload def is_dataclass(obj: type) -> TypeGuard[type[DataclassInstance]]: ... @overload @@ -236,23 +227,45 @@ if sys.version_info >= (3, 9): else: class _InitVarMeta(type): # Not used, instead `InitVar.__class_getitem__` is called. - def __getitem__(self, params: Any) -> InitVar[Any]: ... + # pyright ignore is needed because pyright (not unreasonably) thinks this + # is an invalid use of InitVar. + def __getitem__(self, params: Any) -> InitVar[Any]: ... # pyright: ignore class InitVar(Generic[_T], metaclass=_InitVarMeta): type: Type[_T] def __init__(self, type: Type[_T]) -> None: ... if sys.version_info >= (3, 9): @overload - def __class_getitem__(cls, type: Type[_T]) -> InitVar[_T]: ... + def __class_getitem__(cls, type: Type[_T]) -> InitVar[_T]: ... # pyright: ignore @overload - def __class_getitem__(cls, type: Any) -> InitVar[Any]: ... + def __class_getitem__(cls, type: Any) -> InitVar[Any]: ... # pyright: ignore -if sys.version_info >= (3, 11): +if sys.version_info >= (3, 12): + def make_dataclass( + cls_name: str, + fields: Iterable[str | tuple[str, Any] | tuple[str, Any, Any]], + *, + bases: tuple[type, ...] = (), + namespace: dict[str, Any] | None = None, + init: bool = True, + repr: bool = True, + eq: bool = True, + order: bool = False, + unsafe_hash: bool = False, + frozen: bool = False, + match_args: bool = True, + kw_only: bool = False, + slots: bool = False, + weakref_slot: bool = False, + module: str | None = None, + ) -> type: ... + +elif sys.version_info >= (3, 11): def make_dataclass( cls_name: str, - fields: Iterable[str | tuple[str, type] | tuple[str, type, Any]], + fields: Iterable[str | tuple[str, Any] | tuple[str, Any, Any]], *, - bases: tuple[type, ...] = ..., + bases: tuple[type, ...] = (), namespace: dict[str, Any] | None = None, init: bool = True, repr: bool = True, @@ -269,9 +282,9 @@ if sys.version_info >= (3, 11): elif sys.version_info >= (3, 10): def make_dataclass( cls_name: str, - fields: Iterable[str | tuple[str, type] | tuple[str, type, Any]], + fields: Iterable[str | tuple[str, Any] | tuple[str, Any, Any]], *, - bases: tuple[type, ...] = ..., + bases: tuple[type, ...] = (), namespace: dict[str, Any] | None = None, init: bool = True, repr: bool = True, @@ -287,9 +300,9 @@ elif sys.version_info >= (3, 10): else: def make_dataclass( cls_name: str, - fields: Iterable[str | tuple[str, type] | tuple[str, type, Any]], + fields: Iterable[str | tuple[str, Any] | tuple[str, Any, Any]], *, - bases: tuple[type, ...] = ..., + bases: tuple[type, ...] = (), namespace: dict[str, Any] | None = None, init: bool = True, repr: bool = True, @@ -299,4 +312,4 @@ else: frozen: bool = False, ) -> type: ... -def replace(__obj: _DataclassT, **changes: Any) -> _DataclassT: ... +def replace(obj: _DataclassT, /, **changes: Any) -> _DataclassT: ... diff --git a/mypy/typeshed/stdlib/datetime.pyi b/mypy/typeshed/stdlib/datetime.pyi index 4da5501ce76d..7b890ca010dc 100644 --- a/mypy/typeshed/stdlib/datetime.pyi +++ b/mypy/typeshed/stdlib/datetime.pyi @@ -1,27 +1,25 @@ import sys from abc import abstractmethod from time import struct_time -from typing import ClassVar, NamedTuple, NoReturn, TypeVar, overload -from typing_extensions import Literal, Self, TypeAlias, final +from typing import ClassVar, Literal, NamedTuple, NoReturn, SupportsIndex, final, overload +from typing_extensions import Self, TypeAlias, deprecated if sys.version_info >= (3, 11): __all__ = ("date", "datetime", "time", "timedelta", "timezone", "tzinfo", "MINYEAR", "MAXYEAR", "UTC") elif sys.version_info >= (3, 9): __all__ = ("date", "datetime", "time", "timedelta", "timezone", "tzinfo", "MINYEAR", "MAXYEAR") -_D = TypeVar("_D", bound=date) - MINYEAR: Literal[1] MAXYEAR: Literal[9999] class tzinfo: @abstractmethod - def tzname(self, __dt: datetime | None) -> str | None: ... + def tzname(self, dt: datetime | None, /) -> str | None: ... @abstractmethod - def utcoffset(self, __dt: datetime | None) -> timedelta | None: ... + def utcoffset(self, dt: datetime | None, /) -> timedelta | None: ... @abstractmethod - def dst(self, __dt: datetime | None) -> timedelta | None: ... - def fromutc(self, __dt: datetime) -> datetime: ... + def dst(self, dt: datetime | None, /) -> timedelta | None: ... + def fromutc(self, dt: datetime, /) -> datetime: ... # Alias required to avoid name conflicts with date(time).tzinfo. _TzInfo: TypeAlias = tzinfo @@ -32,9 +30,11 @@ class timezone(tzinfo): min: ClassVar[timezone] max: ClassVar[timezone] def __init__(self, offset: timedelta, name: str = ...) -> None: ... - def tzname(self, __dt: datetime | None) -> str: ... - def utcoffset(self, __dt: datetime | None) -> timedelta: ... - def dst(self, __dt: datetime | None) -> None: ... + def tzname(self, dt: datetime | None, /) -> str: ... + def utcoffset(self, dt: datetime | None, /) -> timedelta: ... + def dst(self, dt: datetime | None, /) -> None: ... + def __hash__(self) -> int: ... + def __eq__(self, value: object, /) -> bool: ... if sys.version_info >= (3, 11): UTC: timezone @@ -49,19 +49,17 @@ class date: min: ClassVar[date] max: ClassVar[date] resolution: ClassVar[timedelta] - def __new__(cls, year: int, month: int, day: int) -> Self: ... + def __new__(cls, year: SupportsIndex, month: SupportsIndex, day: SupportsIndex) -> Self: ... @classmethod - def fromtimestamp(cls, __timestamp: float) -> Self: ... + def fromtimestamp(cls, timestamp: float, /) -> Self: ... @classmethod def today(cls) -> Self: ... @classmethod - def fromordinal(cls, __n: int) -> Self: ... + def fromordinal(cls, n: int, /) -> Self: ... @classmethod - def fromisoformat(cls, __date_string: str) -> Self: ... - if sys.version_info >= (3, 8): - @classmethod - def fromisocalendar(cls, year: int, week: int, day: int) -> Self: ... - + def fromisoformat(cls, date_string: str, /) -> Self: ... + @classmethod + def fromisocalendar(cls, year: int, week: int, day: int) -> Self: ... @property def year(self) -> int: ... @property @@ -75,37 +73,27 @@ class date: if sys.version_info >= (3, 12): def strftime(self, format: str) -> str: ... else: - def strftime(self, __format: str) -> str: ... + def strftime(self, format: str, /) -> str: ... - def __format__(self, __fmt: str) -> str: ... + def __format__(self, fmt: str, /) -> str: ... def isoformat(self) -> str: ... def timetuple(self) -> struct_time: ... def toordinal(self) -> int: ... - def replace(self, year: int = ..., month: int = ..., day: int = ...) -> Self: ... - def __le__(self, __other: date) -> bool: ... - def __lt__(self, __other: date) -> bool: ... - def __ge__(self, __other: date) -> bool: ... - def __gt__(self, __other: date) -> bool: ... - if sys.version_info >= (3, 8): - def __add__(self, __other: timedelta) -> Self: ... - def __radd__(self, __other: timedelta) -> Self: ... - @overload - def __sub__(self, __other: timedelta) -> Self: ... - @overload - def __sub__(self, __other: datetime) -> NoReturn: ... - @overload - def __sub__(self: _D, __other: _D) -> timedelta: ... - else: - # Prior to Python 3.8, arithmetic operations always returned `date`, even in subclasses - def __add__(self, __other: timedelta) -> date: ... - def __radd__(self, __other: timedelta) -> date: ... - @overload - def __sub__(self, __other: timedelta) -> date: ... - @overload - def __sub__(self, __other: datetime) -> NoReturn: ... - @overload - def __sub__(self, __other: date) -> timedelta: ... - + def replace(self, year: SupportsIndex = ..., month: SupportsIndex = ..., day: SupportsIndex = ...) -> Self: ... + def __le__(self, value: date, /) -> bool: ... + def __lt__(self, value: date, /) -> bool: ... + def __ge__(self, value: date, /) -> bool: ... + def __gt__(self, value: date, /) -> bool: ... + def __eq__(self, value: object, /) -> bool: ... + def __add__(self, value: timedelta, /) -> Self: ... + def __radd__(self, value: timedelta, /) -> Self: ... + @overload + def __sub__(self, value: datetime, /) -> NoReturn: ... + @overload + def __sub__(self, value: Self, /) -> timedelta: ... + @overload + def __sub__(self, value: timedelta, /) -> Self: ... + def __hash__(self) -> int: ... def weekday(self) -> int: ... def isoweekday(self) -> int: ... if sys.version_info >= (3, 9): @@ -119,10 +107,10 @@ class time: resolution: ClassVar[timedelta] def __new__( cls, - hour: int = ..., - minute: int = ..., - second: int = ..., - microsecond: int = ..., + hour: SupportsIndex = ..., + minute: SupportsIndex = ..., + second: SupportsIndex = ..., + microsecond: SupportsIndex = ..., tzinfo: _TzInfo | None = ..., *, fold: int = ..., @@ -139,31 +127,33 @@ class time: def tzinfo(self) -> _TzInfo | None: ... @property def fold(self) -> int: ... - def __le__(self, __other: time) -> bool: ... - def __lt__(self, __other: time) -> bool: ... - def __ge__(self, __other: time) -> bool: ... - def __gt__(self, __other: time) -> bool: ... + def __le__(self, value: time, /) -> bool: ... + def __lt__(self, value: time, /) -> bool: ... + def __ge__(self, value: time, /) -> bool: ... + def __gt__(self, value: time, /) -> bool: ... + def __eq__(self, value: object, /) -> bool: ... + def __hash__(self) -> int: ... def isoformat(self, timespec: str = ...) -> str: ... @classmethod - def fromisoformat(cls, __time_string: str) -> Self: ... + def fromisoformat(cls, time_string: str, /) -> Self: ... # On <3.12, the name of the parameter in the pure-Python implementation # didn't match the name in the C implementation, # meaning it is only *safe* to pass it as a keyword argument on 3.12+ if sys.version_info >= (3, 12): def strftime(self, format: str) -> str: ... else: - def strftime(self, __format: str) -> str: ... + def strftime(self, format: str, /) -> str: ... - def __format__(self, __fmt: str) -> str: ... + def __format__(self, fmt: str, /) -> str: ... def utcoffset(self) -> timedelta | None: ... def tzname(self) -> str | None: ... def dst(self) -> timedelta | None: ... def replace( self, - hour: int = ..., - minute: int = ..., - second: int = ..., - microsecond: int = ..., + hour: SupportsIndex = ..., + minute: SupportsIndex = ..., + second: SupportsIndex = ..., + microsecond: SupportsIndex = ..., tzinfo: _TzInfo | None = ..., *, fold: int = ..., @@ -193,43 +183,45 @@ class timedelta: @property def microseconds(self) -> int: ... def total_seconds(self) -> float: ... - def __add__(self, __other: timedelta) -> timedelta: ... - def __radd__(self, __other: timedelta) -> timedelta: ... - def __sub__(self, __other: timedelta) -> timedelta: ... - def __rsub__(self, __other: timedelta) -> timedelta: ... + def __add__(self, value: timedelta, /) -> timedelta: ... + def __radd__(self, value: timedelta, /) -> timedelta: ... + def __sub__(self, value: timedelta, /) -> timedelta: ... + def __rsub__(self, value: timedelta, /) -> timedelta: ... def __neg__(self) -> timedelta: ... def __pos__(self) -> timedelta: ... def __abs__(self) -> timedelta: ... - def __mul__(self, __other: float) -> timedelta: ... - def __rmul__(self, __other: float) -> timedelta: ... + def __mul__(self, value: float, /) -> timedelta: ... + def __rmul__(self, value: float, /) -> timedelta: ... @overload - def __floordiv__(self, __other: timedelta) -> int: ... + def __floordiv__(self, value: timedelta, /) -> int: ... @overload - def __floordiv__(self, __other: int) -> timedelta: ... + def __floordiv__(self, value: int, /) -> timedelta: ... @overload - def __truediv__(self, __other: timedelta) -> float: ... + def __truediv__(self, value: timedelta, /) -> float: ... @overload - def __truediv__(self, __other: float) -> timedelta: ... - def __mod__(self, __other: timedelta) -> timedelta: ... - def __divmod__(self, __other: timedelta) -> tuple[int, timedelta]: ... - def __le__(self, __other: timedelta) -> bool: ... - def __lt__(self, __other: timedelta) -> bool: ... - def __ge__(self, __other: timedelta) -> bool: ... - def __gt__(self, __other: timedelta) -> bool: ... + def __truediv__(self, value: float, /) -> timedelta: ... + def __mod__(self, value: timedelta, /) -> timedelta: ... + def __divmod__(self, value: timedelta, /) -> tuple[int, timedelta]: ... + def __le__(self, value: timedelta, /) -> bool: ... + def __lt__(self, value: timedelta, /) -> bool: ... + def __ge__(self, value: timedelta, /) -> bool: ... + def __gt__(self, value: timedelta, /) -> bool: ... + def __eq__(self, value: object, /) -> bool: ... def __bool__(self) -> bool: ... + def __hash__(self) -> int: ... class datetime(date): min: ClassVar[datetime] max: ClassVar[datetime] def __new__( cls, - year: int, - month: int, - day: int, - hour: int = ..., - minute: int = ..., - second: int = ..., - microsecond: int = ..., + year: SupportsIndex, + month: SupportsIndex, + day: SupportsIndex, + hour: SupportsIndex = ..., + minute: SupportsIndex = ..., + second: SupportsIndex = ..., + microsecond: SupportsIndex = ..., tzinfo: _TzInfo | None = ..., *, fold: int = ..., @@ -254,22 +246,15 @@ class datetime(date): def fromtimestamp(cls, timestamp: float, tz: _TzInfo | None = ...) -> Self: ... else: @classmethod - def fromtimestamp(cls, __timestamp: float, tz: _TzInfo | None = ...) -> Self: ... + def fromtimestamp(cls, timestamp: float, /, tz: _TzInfo | None = ...) -> Self: ... @classmethod - def utcfromtimestamp(cls, __t: float) -> Self: ... - if sys.version_info >= (3, 8): - @classmethod - def now(cls, tz: _TzInfo | None = None) -> Self: ... - else: - @overload - @classmethod - def now(cls, tz: None = None) -> Self: ... - @overload - @classmethod - def now(cls, tz: _TzInfo) -> datetime: ... - + @deprecated("Use timezone-aware objects to represent datetimes in UTC; e.g. by calling .fromtimestamp(datetime.UTC)") + def utcfromtimestamp(cls, t: float, /) -> Self: ... + @classmethod + def now(cls, tz: _TzInfo | None = None) -> Self: ... @classmethod + @deprecated("Use timezone-aware objects to represent datetimes in UTC; e.g. by calling .now(datetime.UTC)") def utcnow(cls) -> Self: ... @classmethod def combine(cls, date: _Date, time: _Time, tzinfo: _TzInfo | None = ...) -> Self: ... @@ -280,42 +265,31 @@ class datetime(date): def timetz(self) -> _Time: ... def replace( self, - year: int = ..., - month: int = ..., - day: int = ..., - hour: int = ..., - minute: int = ..., - second: int = ..., - microsecond: int = ..., + year: SupportsIndex = ..., + month: SupportsIndex = ..., + day: SupportsIndex = ..., + hour: SupportsIndex = ..., + minute: SupportsIndex = ..., + second: SupportsIndex = ..., + microsecond: SupportsIndex = ..., tzinfo: _TzInfo | None = ..., *, fold: int = ..., ) -> Self: ... - if sys.version_info >= (3, 8): - def astimezone(self, tz: _TzInfo | None = ...) -> Self: ... - else: - def astimezone(self, tz: _TzInfo | None = ...) -> datetime: ... - + def astimezone(self, tz: _TzInfo | None = ...) -> Self: ... def isoformat(self, sep: str = ..., timespec: str = ...) -> str: ... @classmethod - def strptime(cls, __date_string: str, __format: str) -> Self: ... + def strptime(cls, date_string: str, format: str, /) -> Self: ... def utcoffset(self) -> timedelta | None: ... def tzname(self) -> str | None: ... def dst(self) -> timedelta | None: ... - def __le__(self, __other: datetime) -> bool: ... # type: ignore[override] - def __lt__(self, __other: datetime) -> bool: ... # type: ignore[override] - def __ge__(self, __other: datetime) -> bool: ... # type: ignore[override] - def __gt__(self, __other: datetime) -> bool: ... # type: ignore[override] - if sys.version_info >= (3, 8): - @overload # type: ignore[override] - def __sub__(self, __other: timedelta) -> Self: ... - @overload - def __sub__(self: _D, __other: _D) -> timedelta: ... - else: - # Prior to Python 3.8, arithmetic operations always returned `datetime`, even in subclasses - def __add__(self, __other: timedelta) -> datetime: ... - def __radd__(self, __other: timedelta) -> datetime: ... - @overload # type: ignore[override] - def __sub__(self, __other: datetime) -> timedelta: ... - @overload - def __sub__(self, __other: timedelta) -> datetime: ... + def __le__(self, value: datetime, /) -> bool: ... # type: ignore[override] + def __lt__(self, value: datetime, /) -> bool: ... # type: ignore[override] + def __ge__(self, value: datetime, /) -> bool: ... # type: ignore[override] + def __gt__(self, value: datetime, /) -> bool: ... # type: ignore[override] + def __eq__(self, value: object, /) -> bool: ... + def __hash__(self) -> int: ... + @overload # type: ignore[override] + def __sub__(self, value: Self, /) -> timedelta: ... + @overload + def __sub__(self, value: timedelta, /) -> Self: ... diff --git a/mypy/typeshed/stdlib/dbm/__init__.pyi b/mypy/typeshed/stdlib/dbm/__init__.pyi index 0068d67b6ad1..f414763e02a6 100644 --- a/mypy/typeshed/stdlib/dbm/__init__.pyi +++ b/mypy/typeshed/stdlib/dbm/__init__.pyi @@ -1,6 +1,9 @@ +import sys +from _typeshed import StrOrBytesPath from collections.abc import Iterator, MutableMapping from types import TracebackType -from typing_extensions import Literal, Self, TypeAlias +from typing import Literal +from typing_extensions import Self, TypeAlias __all__ = ["open", "whichdb", "error"] @@ -90,5 +93,10 @@ class _error(Exception): ... error: tuple[type[_error], type[OSError]] -def whichdb(filename: str) -> str: ... -def open(file: str, flag: _TFlags = "r", mode: int = 0o666) -> _Database: ... +if sys.version_info >= (3, 11): + def whichdb(filename: StrOrBytesPath) -> str | None: ... + def open(file: StrOrBytesPath, flag: _TFlags = "r", mode: int = 0o666) -> _Database: ... + +else: + def whichdb(filename: str) -> str | None: ... + def open(file: str, flag: _TFlags = "r", mode: int = 0o666) -> _Database: ... diff --git a/mypy/typeshed/stdlib/dbm/dumb.pyi b/mypy/typeshed/stdlib/dbm/dumb.pyi index 1fc68cf71f9d..1c0b7756f292 100644 --- a/mypy/typeshed/stdlib/dbm/dumb.pyi +++ b/mypy/typeshed/stdlib/dbm/dumb.pyi @@ -1,3 +1,5 @@ +import sys +from _typeshed import StrOrBytesPath from collections.abc import Iterator, MutableMapping from types import TracebackType from typing_extensions import Self, TypeAlias @@ -28,4 +30,8 @@ class _Database(MutableMapping[_KeyType, bytes]): self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None ) -> None: ... -def open(file: str, flag: str = "c", mode: int = 0o666) -> _Database: ... +if sys.version_info >= (3, 11): + def open(file: StrOrBytesPath, flag: str = "c", mode: int = 0o666) -> _Database: ... + +else: + def open(file: str, flag: str = "c", mode: int = 0o666) -> _Database: ... diff --git a/mypy/typeshed/stdlib/dbm/gnu.pyi b/mypy/typeshed/stdlib/dbm/gnu.pyi index 3dc66a30c370..e80441cbb25b 100644 --- a/mypy/typeshed/stdlib/dbm/gnu.pyi +++ b/mypy/typeshed/stdlib/dbm/gnu.pyi @@ -1,5 +1,5 @@ import sys -from _typeshed import ReadOnlyBuffer +from _typeshed import ReadOnlyBuffer, StrOrBytesPath from types import TracebackType from typing import TypeVar, overload from typing_extensions import Self, TypeAlias @@ -37,4 +37,8 @@ if sys.platform != "win32": # Don't exist at runtime __new__: None # type: ignore[assignment] __init__: None # type: ignore[assignment] - def open(__filename: str, __flags: str = "r", __mode: int = 0o666) -> _gdbm: ... + + if sys.version_info >= (3, 11): + def open(filename: StrOrBytesPath, flags: str = "r", mode: int = 0o666, /) -> _gdbm: ... + else: + def open(filename: str, flags: str = "r", mode: int = 0o666, /) -> _gdbm: ... diff --git a/mypy/typeshed/stdlib/dbm/ndbm.pyi b/mypy/typeshed/stdlib/dbm/ndbm.pyi index 1106fb2a8e7e..02bf23ec181c 100644 --- a/mypy/typeshed/stdlib/dbm/ndbm.pyi +++ b/mypy/typeshed/stdlib/dbm/ndbm.pyi @@ -1,5 +1,5 @@ import sys -from _typeshed import ReadOnlyBuffer +from _typeshed import ReadOnlyBuffer, StrOrBytesPath from types import TracebackType from typing import TypeVar, overload from typing_extensions import Self, TypeAlias @@ -33,4 +33,8 @@ if sys.platform != "win32": # Don't exist at runtime __new__: None # type: ignore[assignment] __init__: None # type: ignore[assignment] - def open(__filename: str, __flags: str = "r", __mode: int = 0o666) -> _dbm: ... + + if sys.version_info >= (3, 11): + def open(filename: StrOrBytesPath, flags: str = "r", mode: int = 0o666, /) -> _dbm: ... + else: + def open(filename: str, flags: str = "r", mode: int = 0o666, /) -> _dbm: ... diff --git a/mypy/typeshed/stdlib/difflib.pyi b/mypy/typeshed/stdlib/difflib.pyi index 310519602695..d5b77b8f0e2c 100644 --- a/mypy/typeshed/stdlib/difflib.pyi +++ b/mypy/typeshed/stdlib/difflib.pyi @@ -1,6 +1,6 @@ import sys from collections.abc import Callable, Iterable, Iterator, Sequence -from typing import Any, AnyStr, Generic, NamedTuple, TypeVar, overload +from typing import Any, AnyStr, Generic, Literal, NamedTuple, TypeVar, overload if sys.version_info >= (3, 9): from types import GenericAlias @@ -49,7 +49,7 @@ class SequenceMatcher(Generic[_T]): def find_longest_match(self, alo: int, ahi: int, blo: int, bhi: int) -> Match: ... def get_matching_blocks(self) -> list[Match]: ... - def get_opcodes(self) -> list[tuple[str, int, int, int, int]]: ... + def get_opcodes(self) -> list[tuple[Literal["replace", "delete", "insert", "equal"], int, int, int, int]]: ... def get_grouped_opcodes(self, n: int = 3) -> Iterable[list[tuple[str, int, int, int, int]]]: ... def ratio(self) -> float: ... def quick_ratio(self) -> float: ... @@ -57,9 +57,8 @@ class SequenceMatcher(Generic[_T]): if sys.version_info >= (3, 9): def __class_getitem__(cls, item: Any) -> GenericAlias: ... -# mypy thinks the signatures of the overloads overlap, but the types still work fine @overload -def get_close_matches(word: AnyStr, possibilities: Iterable[AnyStr], n: int = 3, cutoff: float = 0.6) -> list[AnyStr]: ... # type: ignore[misc] +def get_close_matches(word: AnyStr, possibilities: Iterable[AnyStr], n: int = 3, cutoff: float = 0.6) -> list[AnyStr]: ... @overload def get_close_matches( word: Sequence[_T], possibilities: Iterable[Sequence[_T]], n: int = 3, cutoff: float = 0.6 diff --git a/mypy/typeshed/stdlib/dis.pyi b/mypy/typeshed/stdlib/dis.pyi index ac0c5356f5f9..796d81d8bf70 100644 --- a/mypy/typeshed/stdlib/dis.pyi +++ b/mypy/typeshed/stdlib/dis.pyi @@ -29,9 +29,12 @@ __all__ = [ "opmap", "HAVE_ARGUMENT", "EXTENDED_ARG", - "hasnargs", "stack_effect", ] +if sys.version_info >= (3, 12): + __all__ += ["hasarg", "hasexc"] +else: + __all__ += ["hasnargs"] # Strictly this should not have to include Callable, but mypy doesn't use FunctionType # for functions (python/mypy#3171) @@ -39,13 +42,13 @@ _HaveCodeType: TypeAlias = types.MethodType | types.FunctionType | types.CodeTyp if sys.version_info >= (3, 11): class Positions(NamedTuple): - lineno: int | None = ... - end_lineno: int | None = ... - col_offset: int | None = ... - end_col_offset: int | None = ... + lineno: int | None = None + end_lineno: int | None = None + col_offset: int | None = None + end_col_offset: int | None = None if sys.version_info >= (3, 11): - class Instruction(NamedTuple): + class _Instruction(NamedTuple): opname: str opcode: int arg: int | None @@ -54,10 +57,10 @@ if sys.version_info >= (3, 11): offset: int starts_line: int | None is_jump_target: bool - positions: Positions | None = ... + positions: Positions | None = None else: - class Instruction(NamedTuple): + class _Instruction(NamedTuple): opname: str opcode: int arg: int | None @@ -67,6 +70,9 @@ else: starts_line: int | None is_jump_target: bool +class Instruction(_Instruction): + def _disassemble(self, lineno_width: int = 3, mark_as_current: bool = False, offset_width: int = 4) -> str: ... + class Bytecode: codeobj: types.CodeType first_line: int diff --git a/mypy/typeshed/stdlib/distutils/__init__.pyi b/mypy/typeshed/stdlib/distutils/__init__.pyi index e69de29bb2d1..328a5b783441 100644 --- a/mypy/typeshed/stdlib/distutils/__init__.pyi +++ b/mypy/typeshed/stdlib/distutils/__init__.pyi @@ -0,0 +1,5 @@ +# Attempts to improve these stubs are probably not the best use of time: +# - distutils is deleted in Python 3.12 and newer +# - Most users already do not use stdlib distutils, due to setuptools monkeypatching +# - We have very little quality assurance on these stubs, since due to the two above issues +# we allowlist all distutils errors in stubtest. diff --git a/mypy/typeshed/stdlib/distutils/ccompiler.pyi b/mypy/typeshed/stdlib/distutils/ccompiler.pyi index e7277aa3f9c4..cc097728f77c 100644 --- a/mypy/typeshed/stdlib/distutils/ccompiler.pyi +++ b/mypy/typeshed/stdlib/distutils/ccompiler.pyi @@ -56,7 +56,7 @@ class CCompiler: self, sources: list[str], output_dir: str | None = None, - macros: _Macro | None = None, + macros: list[_Macro] | None = None, include_dirs: list[str] | None = None, debug: bool = ..., extra_preargs: list[str] | None = None, diff --git a/mypy/typeshed/stdlib/distutils/cmd.pyi b/mypy/typeshed/stdlib/distutils/cmd.pyi index d9ffee9cb832..61fce37b80bc 100644 --- a/mypy/typeshed/stdlib/distutils/cmd.pyi +++ b/mypy/typeshed/stdlib/distutils/cmd.pyi @@ -1,9 +1,11 @@ +from _typeshed import Incomplete from abc import abstractmethod from collections.abc import Callable, Iterable from distutils.dist import Distribution from typing import Any class Command: + distribution: Distribution sub_commands: list[tuple[str, Callable[[Command], bool] | None]] def __init__(self, dist: Distribution) -> None: ... @abstractmethod @@ -60,3 +62,5 @@ class Command: skip_msg: str | None = None, level: Any = 1, ) -> None: ... # level is not used + def ensure_finalized(self) -> None: ... + def dump_options(self, header: Incomplete | None = None, indent: str = "") -> None: ... diff --git a/mypy/typeshed/stdlib/distutils/command/build_ext.pyi b/mypy/typeshed/stdlib/distutils/command/build_ext.pyi index 80cd78936cb9..5eb541fb9101 100644 --- a/mypy/typeshed/stdlib/distutils/command/build_ext.pyi +++ b/mypy/typeshed/stdlib/distutils/command/build_ext.pyi @@ -43,8 +43,8 @@ class build_ext(Command): def build_extension(self, ext) -> None: ... def swig_sources(self, sources, extension): ... def find_swig(self): ... - def get_ext_fullpath(self, ext_name): ... - def get_ext_fullname(self, ext_name): ... - def get_ext_filename(self, ext_name): ... + def get_ext_fullpath(self, ext_name: str) -> str: ... + def get_ext_fullname(self, ext_name: str) -> str: ... + def get_ext_filename(self, ext_name: str) -> str: ... def get_export_symbols(self, ext): ... def get_libraries(self, ext): ... diff --git a/mypy/typeshed/stdlib/distutils/command/config.pyi b/mypy/typeshed/stdlib/distutils/command/config.pyi index 81fdf76b2b59..7077c9a4c158 100644 --- a/mypy/typeshed/stdlib/distutils/command/config.pyi +++ b/mypy/typeshed/stdlib/distutils/command/config.pyi @@ -74,7 +74,7 @@ class config(Command): library_dirs: Sequence[str] | None = None, headers: Sequence[str] | None = None, include_dirs: Sequence[str] | None = None, - other_libraries: list[str] = ..., + other_libraries: list[str] = [], ) -> bool: ... def check_header( self, header: str, include_dirs: Sequence[str] | None = None, library_dirs: Sequence[str] | None = None, lang: str = "c" diff --git a/mypy/typeshed/stdlib/distutils/core.pyi b/mypy/typeshed/stdlib/distutils/core.pyi index 56081f921378..c41c8ba19a8b 100644 --- a/mypy/typeshed/stdlib/distutils/core.pyi +++ b/mypy/typeshed/stdlib/distutils/core.pyi @@ -1,9 +1,17 @@ +from _typeshed import StrOrBytesPath from collections.abc import Mapping from distutils.cmd import Command as Command from distutils.dist import Distribution as Distribution from distutils.extension import Extension as Extension from typing import Any +USAGE: str + +def gen_usage(script_name: StrOrBytesPath) -> str: ... + +setup_keywords: tuple[str, ...] +extension_keywords: tuple[str, ...] + def setup( *, name: str = ..., @@ -45,5 +53,5 @@ def setup( password: str = ..., fullname: str = ..., **attrs: Any, -) -> None: ... +) -> Distribution: ... def run_setup(script_name: str, script_args: list[str] | None = None, stop_after: str = "run") -> Distribution: ... diff --git a/mypy/typeshed/stdlib/distutils/cygwinccompiler.pyi b/mypy/typeshed/stdlib/distutils/cygwinccompiler.pyi index 1f85b254860b..5f2e623eeff6 100644 --- a/mypy/typeshed/stdlib/distutils/cygwinccompiler.pyi +++ b/mypy/typeshed/stdlib/distutils/cygwinccompiler.pyi @@ -1,4 +1,20 @@ from distutils.unixccompiler import UnixCCompiler +from distutils.version import LooseVersion +from re import Pattern +from typing import Literal + +def get_msvcr() -> list[str] | None: ... class CygwinCCompiler(UnixCCompiler): ... class Mingw32CCompiler(CygwinCCompiler): ... + +CONFIG_H_OK: str +CONFIG_H_NOTOK: str +CONFIG_H_UNCERTAIN: str + +def check_config_h() -> tuple[Literal["ok", "not ok", "uncertain"], str]: ... + +RE_VERSION: Pattern[bytes] + +def get_versions() -> tuple[LooseVersion | None, ...]: ... +def is_cygwingcc() -> bool: ... diff --git a/mypy/typeshed/stdlib/distutils/dist.pyi b/mypy/typeshed/stdlib/distutils/dist.pyi index b411324c4ce6..b296b11f73ba 100644 --- a/mypy/typeshed/stdlib/distutils/dist.pyi +++ b/mypy/typeshed/stdlib/distutils/dist.pyi @@ -1,7 +1,14 @@ -from _typeshed import FileDescriptorOrPath, SupportsWrite +from _typeshed import FileDescriptorOrPath, Incomplete, SupportsWrite from collections.abc import Iterable, Mapping from distutils.cmd import Command -from typing import IO, Any +from re import Pattern +from typing import IO, Any, ClassVar, TypeVar, overload +from typing_extensions import TypeAlias + +command_re: Pattern[str] + +_OptionsList: TypeAlias = list[tuple[str, str | None, str, int] | tuple[str, str | None, str]] +_CommandT = TypeVar("_CommandT", bound=Command) class DistributionMetadata: def __init__(self, path: FileDescriptorOrPath | None = None) -> None: ... @@ -56,4 +63,84 @@ class Distribution: def __init__(self, attrs: Mapping[str, Any] | None = None) -> None: ... def get_option_dict(self, command: str) -> dict[str, tuple[str, str]]: ... def parse_config_files(self, filenames: Iterable[str] | None = None) -> None: ... - def get_command_obj(self, command: str, create: bool = ...) -> Command | None: ... + def get_command_obj(self, command: str, create: bool = True) -> Command | None: ... + global_options: ClassVar[_OptionsList] + common_usage: ClassVar[str] + display_options: ClassVar[_OptionsList] + display_option_names: ClassVar[list[str]] + negative_opt: ClassVar[dict[str, str]] + verbose: int + dry_run: int + help: int + command_packages: list[str] | None + script_name: str | None + script_args: list[str] | None + command_options: dict[str, dict[str, tuple[str, str]]] + dist_files: list[tuple[str, str, str]] + packages: Incomplete + package_data: dict[str, list[str]] + package_dir: Incomplete + py_modules: Incomplete + libraries: Incomplete + headers: Incomplete + ext_modules: Incomplete + ext_package: Incomplete + include_dirs: Incomplete + extra_path: Incomplete + scripts: Incomplete + data_files: Incomplete + password: str + command_obj: Incomplete + have_run: Incomplete + want_user_cfg: bool + def dump_option_dicts( + self, header: Incomplete | None = None, commands: Incomplete | None = None, indent: str = "" + ) -> None: ... + def find_config_files(self): ... + commands: Incomplete + def parse_command_line(self): ... + def finalize_options(self) -> None: ... + def handle_display_options(self, option_order): ... + def print_command_list(self, commands, header, max_length) -> None: ... + def print_commands(self) -> None: ... + def get_command_list(self): ... + def get_command_packages(self): ... + def get_command_class(self, command: str) -> type[Command]: ... + @overload + def reinitialize_command(self, command: str, reinit_subcommands: bool = False) -> Command: ... + @overload + def reinitialize_command(self, command: _CommandT, reinit_subcommands: bool = False) -> _CommandT: ... + def announce(self, msg, level: int = 2) -> None: ... + def run_commands(self) -> None: ... + def run_command(self, command: str) -> None: ... + def has_pure_modules(self) -> bool: ... + def has_ext_modules(self) -> bool: ... + def has_c_libraries(self) -> bool: ... + def has_modules(self) -> bool: ... + def has_headers(self) -> bool: ... + def has_scripts(self) -> bool: ... + def has_data_files(self) -> bool: ... + def is_pure(self) -> bool: ... + + # Getter methods generated in __init__ + def get_name(self) -> str: ... + def get_version(self) -> str: ... + def get_fullname(self) -> str: ... + def get_author(self) -> str: ... + def get_author_email(self) -> str: ... + def get_maintainer(self) -> str: ... + def get_maintainer_email(self) -> str: ... + def get_contact(self) -> str: ... + def get_contact_email(self) -> str: ... + def get_url(self) -> str: ... + def get_license(self) -> str: ... + def get_licence(self) -> str: ... + def get_description(self) -> str: ... + def get_long_description(self) -> str: ... + def get_keywords(self) -> str | list[str]: ... + def get_platforms(self) -> str | list[str]: ... + def get_classifiers(self) -> str | list[str]: ... + def get_download_url(self) -> str: ... + def get_requires(self) -> list[str]: ... + def get_provides(self) -> list[str]: ... + def get_obsoletes(self) -> list[str]: ... diff --git a/mypy/typeshed/stdlib/distutils/fancy_getopt.pyi b/mypy/typeshed/stdlib/distutils/fancy_getopt.pyi index 153583be6b5d..f9916d4511b2 100644 --- a/mypy/typeshed/stdlib/distutils/fancy_getopt.pyi +++ b/mypy/typeshed/stdlib/distutils/fancy_getopt.pyi @@ -1,14 +1,15 @@ from collections.abc import Iterable, Mapping +from re import Pattern from typing import Any, overload from typing_extensions import TypeAlias _Option: TypeAlias = tuple[str, str | None, str] _GR: TypeAlias = tuple[list[str], OptionDummy] -def fancy_getopt( - options: list[_Option], negative_opt: Mapping[_Option, _Option], object: Any, args: list[str] | None -) -> list[str] | _GR: ... -def wrap_text(text: str, width: int) -> list[str]: ... +longopt_pat: str +longopt_re: Pattern[str] +neg_alias_re: Pattern[str] +longopt_xlate: dict[int, int] class FancyGetopt: def __init__(self, option_table: list[_Option] | None = None) -> None: ... @@ -20,5 +21,14 @@ class FancyGetopt: def get_option_order(self) -> list[tuple[str, str]]: ... def generate_help(self, header: str | None = None) -> list[str]: ... +def fancy_getopt( + options: list[_Option], negative_opt: Mapping[_Option, _Option], object: Any, args: list[str] | None +) -> list[str] | _GR: ... + +WS_TRANS: dict[int, str] + +def wrap_text(text: str, width: int) -> list[str]: ... +def translate_longopt(opt: str) -> str: ... + class OptionDummy: - def __init__(self, options: Iterable[str] = ...) -> None: ... + def __init__(self, options: Iterable[str] = []) -> None: ... diff --git a/mypy/typeshed/stdlib/distutils/filelist.pyi b/mypy/typeshed/stdlib/distutils/filelist.pyi index bea48ac16ac5..25db2f3cb6cc 100644 --- a/mypy/typeshed/stdlib/distutils/filelist.pyi +++ b/mypy/typeshed/stdlib/distutils/filelist.pyi @@ -1,7 +1,6 @@ from collections.abc import Iterable from re import Pattern -from typing import overload -from typing_extensions import Literal +from typing import Literal, overload # class is entirely undocumented class FileList: diff --git a/mypy/typeshed/stdlib/distutils/sysconfig.pyi b/mypy/typeshed/stdlib/distutils/sysconfig.pyi index 8b291e8b94a5..e2399a6cf36b 100644 --- a/mypy/typeshed/stdlib/distutils/sysconfig.pyi +++ b/mypy/typeshed/stdlib/distutils/sysconfig.pyi @@ -1,13 +1,31 @@ +import sys from collections.abc import Mapping from distutils.ccompiler import CCompiler +from typing import Literal, overload +from typing_extensions import deprecated PREFIX: str EXEC_PREFIX: str +BASE_PREFIX: str +BASE_EXEC_PREFIX: str +project_base: str +python_build: bool +def expand_makefile_vars(s: str, vars: Mapping[str, str]) -> str: ... +@overload +@deprecated("SO is deprecated, use EXT_SUFFIX. Support is removed in Python 3.11") +def get_config_var(name: Literal["SO"]) -> int | str | None: ... +@overload def get_config_var(name: str) -> int | str | None: ... -def get_config_vars(*args: str) -> Mapping[str, int | str]: ... +@overload +def get_config_vars() -> dict[str, str | int]: ... +@overload +def get_config_vars(arg: str, /, *args: str) -> list[str | int]: ... def get_config_h_filename() -> str: ... def get_makefile_filename() -> str: ... def get_python_inc(plat_specific: bool = ..., prefix: str | None = None) -> str: ... def get_python_lib(plat_specific: bool = ..., standard_lib: bool = ..., prefix: str | None = None) -> str: ... def customize_compiler(compiler: CCompiler) -> None: ... + +if sys.version_info < (3, 10): + def get_python_version() -> str: ... diff --git a/mypy/typeshed/stdlib/distutils/util.pyi b/mypy/typeshed/stdlib/distutils/util.pyi index f03844307581..835266edde59 100644 --- a/mypy/typeshed/stdlib/distutils/util.pyi +++ b/mypy/typeshed/stdlib/distutils/util.pyi @@ -1,8 +1,8 @@ from _typeshed import StrPath, Unused from collections.abc import Callable, Container, Iterable, Mapping -from typing import Any -from typing_extensions import Literal +from typing import Any, Literal +def get_host_platform() -> str: ... def get_platform() -> str: ... def convert_path(pathname: str) -> str: ... def change_root(new_root: str, pathname: str) -> str: ... diff --git a/mypy/typeshed/stdlib/doctest.pyi b/mypy/typeshed/stdlib/doctest.pyi index 88d066fdc23c..7e334ef0c504 100644 --- a/mypy/typeshed/stdlib/doctest.pyi +++ b/mypy/typeshed/stdlib/doctest.pyi @@ -85,6 +85,7 @@ class Example: indent: int = 0, options: dict[int, bool] | None = None, ) -> None: ... + def __hash__(self) -> int: ... def __eq__(self, other: object) -> bool: ... class DocTest: @@ -103,6 +104,7 @@ class DocTest: lineno: int | None, docstring: str | None, ) -> None: ... + def __hash__(self) -> int: ... def __lt__(self, other: DocTest) -> bool: ... def __eq__(self, other: object) -> bool: ... @@ -204,12 +206,13 @@ class DocTestCase(unittest.TestCase): self, test: DocTest, optionflags: int = 0, - setUp: Callable[[DocTest], Any] | None = None, - tearDown: Callable[[DocTest], Any] | None = None, + setUp: Callable[[DocTest], object] | None = None, + tearDown: Callable[[DocTest], object] | None = None, checker: OutputChecker | None = None, ) -> None: ... def runTest(self) -> None: ... def format_failure(self, err: str) -> str: ... + def __hash__(self) -> int: ... def __eq__(self, other: object) -> bool: ... class SkipDocTestCase(DocTestCase): diff --git a/mypy/typeshed/stdlib/email/_header_value_parser.pyi b/mypy/typeshed/stdlib/email/_header_value_parser.pyi index 97008140ec5d..806fc84cf784 100644 --- a/mypy/typeshed/stdlib/email/_header_value_parser.pyi +++ b/mypy/typeshed/stdlib/email/_header_value_parser.pyi @@ -1,10 +1,9 @@ -import sys from collections.abc import Iterable, Iterator from email.errors import HeaderParseError, MessageDefect from email.policy import Policy from re import Pattern -from typing import Any -from typing_extensions import Final, Self +from typing import Any, Final +from typing_extensions import Self WSP: Final[set[str]] CFWS_LEADER: Final[set[str]] @@ -195,10 +194,9 @@ class DotAtomText(TokenList): token_type: str as_ew_allowed: bool -if sys.version_info >= (3, 8): - class NoFoldLiteral(TokenList): - token_type: str - as_ew_allowed: bool +class NoFoldLiteral(TokenList): + token_type: str + as_ew_allowed: bool class AddrSpec(TokenList): token_type: str @@ -296,17 +294,16 @@ class HeaderLabel(TokenList): token_type: str as_ew_allowed: bool -if sys.version_info >= (3, 8): - class MsgID(TokenList): - token_type: str - as_ew_allowed: bool - def fold(self, policy: Policy) -> str: ... +class MsgID(TokenList): + token_type: str + as_ew_allowed: bool + def fold(self, policy: Policy) -> str: ... - class MessageID(MsgID): - token_type: str +class MessageID(MsgID): + token_type: str - class InvalidMessageID(MessageID): - token_type: str +class InvalidMessageID(MessageID): + token_type: str class Header(TokenList): token_type: str @@ -375,12 +372,9 @@ def get_group_list(value: str) -> tuple[GroupList, str]: ... def get_group(value: str) -> tuple[Group, str]: ... def get_address(value: str) -> tuple[Address, str]: ... def get_address_list(value: str) -> tuple[AddressList, str]: ... - -if sys.version_info >= (3, 8): - def get_no_fold_literal(value: str) -> tuple[NoFoldLiteral, str]: ... - def get_msg_id(value: str) -> tuple[MsgID, str]: ... - def parse_message_id(value: str) -> MessageID: ... - +def get_no_fold_literal(value: str) -> tuple[NoFoldLiteral, str]: ... +def get_msg_id(value: str) -> tuple[MsgID, str]: ... +def parse_message_id(value: str) -> MessageID: ... def parse_mime_version(value: str) -> MIMEVersion: ... def get_invalid_parameter(value: str) -> tuple[InvalidParameter, str]: ... def get_ttext(value: str) -> tuple[ValueTerminal, str]: ... diff --git a/mypy/typeshed/stdlib/email/_policybase.pyi b/mypy/typeshed/stdlib/email/_policybase.pyi new file mode 100644 index 000000000000..a3dd61a282ce --- /dev/null +++ b/mypy/typeshed/stdlib/email/_policybase.pyi @@ -0,0 +1,51 @@ +from abc import ABCMeta, abstractmethod +from collections.abc import Callable +from email.errors import MessageDefect +from email.header import Header +from email.message import Message +from typing import Any +from typing_extensions import Self + +class _PolicyBase: + def __add__(self, other: Any) -> Self: ... + def clone(self, **kw: Any) -> Self: ... + +class Policy(_PolicyBase, metaclass=ABCMeta): + max_line_length: int | None + linesep: str + cte_type: str + raise_on_defect: bool + mangle_from_: bool + message_factory: Callable[[Policy], Message] | None + def __init__( + self, + *, + max_line_length: int | None = 78, + linesep: str = "\n", + cte_type: str = "8bit", + raise_on_defect: bool = False, + mangle_from_: bool = False, + message_factory: Callable[[Policy], Message] | None = None, + ) -> None: ... + def handle_defect(self, obj: Message, defect: MessageDefect) -> None: ... + def register_defect(self, obj: Message, defect: MessageDefect) -> None: ... + def header_max_count(self, name: str) -> int | None: ... + @abstractmethod + def header_source_parse(self, sourcelines: list[str]) -> tuple[str, str]: ... + @abstractmethod + def header_store_parse(self, name: str, value: str) -> tuple[str, str]: ... + @abstractmethod + def header_fetch_parse(self, name: str, value: str) -> str: ... + @abstractmethod + def fold(self, name: str, value: str) -> str: ... + @abstractmethod + def fold_binary(self, name: str, value: str) -> bytes: ... + +class Compat32(Policy): + def header_source_parse(self, sourcelines: list[str]) -> tuple[str, str]: ... + def header_store_parse(self, name: str, value: str) -> tuple[str, str]: ... + def header_fetch_parse(self, name: str, value: str) -> str | Header: ... # type: ignore[override] + def fold(self, name: str, value: str) -> str: ... + def fold_binary(self, name: str, value: str) -> bytes: ... + +compat32: Compat32 diff --git a/mypy/typeshed/stdlib/email/charset.pyi b/mypy/typeshed/stdlib/email/charset.pyi index 24b8fd768b7b..2d12df337207 100644 --- a/mypy/typeshed/stdlib/email/charset.pyi +++ b/mypy/typeshed/stdlib/email/charset.pyi @@ -1,4 +1,6 @@ -from collections.abc import Iterator +from collections.abc import Callable, Iterator +from email.message import Message +from typing import overload __all__ = ["Charset", "add_alias", "add_charset", "add_codec"] @@ -14,13 +16,16 @@ class Charset: input_codec: str | None output_codec: str | None def __init__(self, input_charset: str = "us-ascii") -> None: ... - def get_body_encoding(self) -> str: ... + def get_body_encoding(self) -> str | Callable[[Message], None]: ... def get_output_charset(self) -> str | None: ... def header_encode(self, string: str) -> str: ... - def header_encode_lines(self, string: str, maxlengths: Iterator[int]) -> list[str]: ... - def body_encode(self, string: str) -> str: ... + def header_encode_lines(self, string: str, maxlengths: Iterator[int]) -> list[str | None]: ... + @overload + def body_encode(self, string: None) -> None: ... + @overload + def body_encode(self, string: str | bytes) -> str: ... def __eq__(self, other: object) -> bool: ... - def __ne__(self, __other: object) -> bool: ... + def __ne__(self, value: object, /) -> bool: ... def add_charset( charset: str, header_enc: int | None = None, body_enc: int | None = None, output_charset: str | None = None diff --git a/mypy/typeshed/stdlib/email/feedparser.pyi b/mypy/typeshed/stdlib/email/feedparser.pyi index 4b7f73b9c015..22920fc99c69 100644 --- a/mypy/typeshed/stdlib/email/feedparser.pyi +++ b/mypy/typeshed/stdlib/email/feedparser.pyi @@ -15,10 +15,9 @@ class FeedParser(Generic[_MessageT]): def feed(self, data: str) -> None: ... def close(self) -> _MessageT: ... -class BytesFeedParser(Generic[_MessageT]): +class BytesFeedParser(FeedParser[_MessageT]): @overload def __init__(self: BytesFeedParser[Message], _factory: None = None, *, policy: Policy = ...) -> None: ... @overload def __init__(self, _factory: Callable[[], _MessageT], *, policy: Policy = ...) -> None: ... - def feed(self, data: bytes | bytearray) -> None: ... - def close(self) -> _MessageT: ... + def feed(self, data: bytes | bytearray) -> None: ... # type: ignore[override] diff --git a/mypy/typeshed/stdlib/email/generator.pyi b/mypy/typeshed/stdlib/email/generator.pyi index 8362dd9c4ff6..faa6551fc925 100644 --- a/mypy/typeshed/stdlib/email/generator.pyi +++ b/mypy/typeshed/stdlib/email/generator.pyi @@ -1,11 +1,12 @@ from _typeshed import SupportsWrite from email.message import Message from email.policy import Policy +from typing_extensions import Self __all__ = ["Generator", "DecodedGenerator", "BytesGenerator"] class Generator: - def clone(self, fp: SupportsWrite[str]) -> Generator: ... + def clone(self, fp: SupportsWrite[str]) -> Self: ... def write(self, s: str) -> None: ... def __init__( self, @@ -17,9 +18,7 @@ class Generator: ) -> None: ... def flatten(self, msg: Message, unixfrom: bool = False, linesep: str | None = None) -> None: ... -class BytesGenerator: - def clone(self, fp: SupportsWrite[bytes]) -> BytesGenerator: ... - def write(self, s: str) -> None: ... +class BytesGenerator(Generator): def __init__( self, outfp: SupportsWrite[bytes], @@ -28,7 +27,6 @@ class BytesGenerator: *, policy: Policy | None = None, ) -> None: ... - def flatten(self, msg: Message, unixfrom: bool = False, linesep: str | None = None) -> None: ... class DecodedGenerator(Generator): def __init__( diff --git a/mypy/typeshed/stdlib/email/header.pyi b/mypy/typeshed/stdlib/email/header.pyi index c6f0c6fbf6fc..212132c6be18 100644 --- a/mypy/typeshed/stdlib/email/header.pyi +++ b/mypy/typeshed/stdlib/email/header.pyi @@ -17,7 +17,7 @@ class Header: def append(self, s: bytes | bytearray | str, charset: Charset | str | None = None, errors: str = "strict") -> None: ... def encode(self, splitchars: str = ";, \t", maxlinelen: int | None = None, linesep: str = "\n") -> str: ... def __eq__(self, other: object) -> bool: ... - def __ne__(self, __other: object) -> bool: ... + def __ne__(self, value: object, /) -> bool: ... # decode_header() either returns list[tuple[str, None]] if the header # contains no encoded parts, or list[tuple[bytes, str | None]] if the header diff --git a/mypy/typeshed/stdlib/email/headerregistry.pyi b/mypy/typeshed/stdlib/email/headerregistry.pyi index e158e89818f7..2ffdca9b2f22 100644 --- a/mypy/typeshed/stdlib/email/headerregistry.pyi +++ b/mypy/typeshed/stdlib/email/headerregistry.pyi @@ -1,4 +1,3 @@ -import sys import types from collections.abc import Iterable, Mapping from datetime import datetime as _datetime @@ -7,14 +6,15 @@ from email._header_value_parser import ( ContentDisposition, ContentTransferEncoding, ContentType, + MessageID, MIMEVersion, TokenList, UnstructuredTokenList, ) from email.errors import MessageDefect from email.policy import Policy -from typing import Any, ClassVar, Protocol -from typing_extensions import Literal, Self +from typing import Any, ClassVar, Literal, Protocol +from typing_extensions import Self class BaseHeader(str): # max_count is actually more of an abstract ClassVar (not defined on the base class, but expected to be defined in subclasses) @@ -130,22 +130,19 @@ class ContentTransferEncodingHeader: @staticmethod def value_parser(value: str) -> ContentTransferEncoding: ... -if sys.version_info >= (3, 8): - from email._header_value_parser import MessageID - - class MessageIDHeader: - max_count: ClassVar[Literal[1]] - @classmethod - def parse(cls, value: str, kwds: dict[str, Any]) -> None: ... - @staticmethod - def value_parser(value: str) -> MessageID: ... +class MessageIDHeader: + max_count: ClassVar[Literal[1]] + @classmethod + def parse(cls, value: str, kwds: dict[str, Any]) -> None: ... + @staticmethod + def value_parser(value: str) -> MessageID: ... class _HeaderParser(Protocol): max_count: ClassVar[Literal[1] | None] @staticmethod - def value_parser(value: str) -> TokenList: ... + def value_parser(value: str, /) -> TokenList: ... @classmethod - def parse(cls, value: str, kwds: dict[str, Any]) -> None: ... + def parse(cls, value: str, kwds: dict[str, Any], /) -> None: ... class HeaderRegistry: registry: dict[str, type[_HeaderParser]] diff --git a/mypy/typeshed/stdlib/email/message.pyi b/mypy/typeshed/stdlib/email/message.pyi index 14e018073103..4032bc6136d4 100644 --- a/mypy/typeshed/stdlib/email/message.pyi +++ b/mypy/typeshed/stdlib/email/message.pyi @@ -4,18 +4,32 @@ from email.charset import Charset from email.contentmanager import ContentManager from email.errors import MessageDefect from email.policy import Policy -from typing import Any, TypeVar, overload +from typing import Any, Generic, Literal, Protocol, TypeVar, overload from typing_extensions import Self, TypeAlias __all__ = ["Message", "EmailMessage"] _T = TypeVar("_T") +# Type returned by Policy.header_fetch_parse, often str or Header. +_HeaderT = TypeVar("_HeaderT", default=str) +_HeaderParamT = TypeVar("_HeaderParamT", default=str) +# Represents headers constructed by HeaderRegistry. Those are sub-classes +# of BaseHeader and another header type. +_HeaderRegistryT = TypeVar("_HeaderRegistryT", default=Any) +_HeaderRegistryParamT = TypeVar("_HeaderRegistryParamT", default=Any) -_PayloadType: TypeAlias = list[Message] | str | bytes | bytearray +_PayloadType: TypeAlias = Message | str +_EncodedPayloadType: TypeAlias = Message | bytes +_MultipartPayloadType: TypeAlias = list[_PayloadType] _CharsetType: TypeAlias = Charset | str | None -_HeaderType: TypeAlias = Any -class Message: +class _SupportsEncodeToPayload(Protocol): + def encode(self, encoding: str, /) -> _PayloadType | _MultipartPayloadType | _SupportsDecodeToPayload: ... + +class _SupportsDecodeToPayload(Protocol): + def decode(self, encoding: str, errors: str, /) -> _PayloadType | _MultipartPayloadType: ... + +class Message(Generic[_HeaderT, _HeaderParamT]): policy: Policy # undocumented preamble: str | None epilogue: str | None @@ -23,30 +37,56 @@ class Message: def is_multipart(self) -> bool: ... def set_unixfrom(self, unixfrom: str) -> None: ... def get_unixfrom(self) -> str | None: ... - def attach(self, payload: Message) -> None: ... - def get_payload(self, i: int | None = None, decode: bool = False) -> Any: ... # returns _PayloadType | None - def set_payload(self, payload: _PayloadType, charset: _CharsetType = None) -> None: ... + def attach(self, payload: _PayloadType) -> None: ... + # `i: int` without a multipart payload results in an error + # `| Any`: can be None for cleared or unset payload, but annoying to check + @overload # multipart + def get_payload(self, i: int, decode: Literal[True]) -> None: ... + @overload # multipart + def get_payload(self, i: int, decode: Literal[False] = False) -> _PayloadType | Any: ... + @overload # either + def get_payload(self, i: None = None, decode: Literal[False] = False) -> _PayloadType | _MultipartPayloadType | Any: ... + @overload # not multipart + def get_payload(self, i: None = None, *, decode: Literal[True]) -> _EncodedPayloadType | Any: ... + @overload # not multipart, IDEM but w/o kwarg + def get_payload(self, i: None, decode: Literal[True]) -> _EncodedPayloadType | Any: ... + # If `charset=None` and payload supports both `encode` AND `decode`, then an invalid payload could be passed, but this is unlikely + # Not[_SupportsEncodeToPayload] + @overload + def set_payload( + self, payload: _SupportsDecodeToPayload | _PayloadType | _MultipartPayloadType, charset: None = None + ) -> None: ... + @overload + def set_payload( + self, + payload: _SupportsEncodeToPayload | _SupportsDecodeToPayload | _PayloadType | _MultipartPayloadType, + charset: Charset | str, + ) -> None: ... def set_charset(self, charset: _CharsetType) -> None: ... def get_charset(self) -> _CharsetType: ... def __len__(self) -> int: ... def __contains__(self, name: str) -> bool: ... def __iter__(self) -> Iterator[str]: ... - def __getitem__(self, name: str) -> _HeaderType: ... - def __setitem__(self, name: str, val: _HeaderType) -> None: ... + # Same as `get` with `failobj=None`, but with the expectation that it won't return None in most scenarios + # This is important for protocols using __getitem__, like SupportsKeysAndGetItem + # Morally, the return type should be `AnyOf[_HeaderType, None]`, + # so using "the Any trick" instead. + def __getitem__(self, name: str) -> _HeaderT | Any: ... + def __setitem__(self, name: str, val: _HeaderParamT) -> None: ... def __delitem__(self, name: str) -> None: ... def keys(self) -> list[str]: ... - def values(self) -> list[_HeaderType]: ... - def items(self) -> list[tuple[str, _HeaderType]]: ... + def values(self) -> list[_HeaderT]: ... + def items(self) -> list[tuple[str, _HeaderT]]: ... @overload - def get(self, name: str, failobj: None = None) -> _HeaderType | None: ... + def get(self, name: str, failobj: None = None) -> _HeaderT | None: ... @overload - def get(self, name: str, failobj: _T) -> _HeaderType | _T: ... + def get(self, name: str, failobj: _T) -> _HeaderT | _T: ... @overload - def get_all(self, name: str, failobj: None = None) -> list[_HeaderType] | None: ... + def get_all(self, name: str, failobj: None = None) -> list[_HeaderT] | None: ... @overload - def get_all(self, name: str, failobj: _T) -> list[_HeaderType] | _T: ... + def get_all(self, name: str, failobj: _T) -> list[_HeaderT] | _T: ... def add_header(self, _name: str, _value: str, **_params: _ParamsType) -> None: ... - def replace_header(self, _name: str, _value: _HeaderType) -> None: ... + def replace_header(self, _name: str, _value: _HeaderParamT) -> None: ... def get_content_type(self) -> str: ... def get_content_maintype(self) -> str: ... def get_content_subtype(self) -> str: ... @@ -80,9 +120,9 @@ class Message: @overload def get_content_charset(self, failobj: _T) -> str | _T: ... @overload - def get_charsets(self, failobj: None = None) -> list[str] | None: ... + def get_charsets(self, failobj: None = None) -> list[str | None]: ... @overload - def get_charsets(self, failobj: _T) -> list[str] | _T: ... + def get_charsets(self, failobj: _T) -> list[str | _T]: ... def walk(self) -> Generator[Self, None, None]: ... def get_content_disposition(self) -> str | None: ... def as_string(self, unixfrom: bool = False, maxheaderlen: int = 0, policy: Policy | None = None) -> str: ... @@ -100,14 +140,14 @@ class Message: ) -> None: ... def __init__(self, policy: Policy = ...) -> None: ... # The following two methods are undocumented, but a source code comment states that they are public API - def set_raw(self, name: str, value: _HeaderType) -> None: ... - def raw_items(self) -> Iterator[tuple[str, _HeaderType]]: ... + def set_raw(self, name: str, value: _HeaderParamT) -> None: ... + def raw_items(self) -> Iterator[tuple[str, _HeaderT]]: ... -class MIMEPart(Message): +class MIMEPart(Message[_HeaderRegistryT, _HeaderRegistryParamT]): def __init__(self, policy: Policy | None = None) -> None: ... - def get_body(self, preferencelist: Sequence[str] = ...) -> Message | None: ... - def iter_attachments(self) -> Iterator[Message]: ... - def iter_parts(self) -> Iterator[Message]: ... + def get_body(self, preferencelist: Sequence[str] = ("related", "html", "plain")) -> MIMEPart[_HeaderRegistryT] | None: ... + def iter_attachments(self) -> Iterator[MIMEPart[_HeaderRegistryT]]: ... + def iter_parts(self) -> Iterator[MIMEPart[_HeaderRegistryT]]: ... def get_content(self, *args: Any, content_manager: ContentManager | None = None, **kw: Any) -> Any: ... def set_content(self, *args: Any, content_manager: ContentManager | None = None, **kw: Any) -> None: ... def make_related(self, boundary: str | None = None) -> None: ... diff --git a/mypy/typeshed/stdlib/email/parser.pyi b/mypy/typeshed/stdlib/email/parser.pyi index 28b6aca856ca..fecb29d90b2e 100644 --- a/mypy/typeshed/stdlib/email/parser.pyi +++ b/mypy/typeshed/stdlib/email/parser.pyi @@ -3,24 +3,34 @@ from collections.abc import Callable from email.feedparser import BytesFeedParser as BytesFeedParser, FeedParser as FeedParser from email.message import Message from email.policy import Policy -from typing import IO +from io import _WrappedBuffer +from typing import Generic, TypeVar, overload __all__ = ["Parser", "HeaderParser", "BytesParser", "BytesHeaderParser", "FeedParser", "BytesFeedParser"] -class Parser: - def __init__(self, _class: Callable[[], Message] | None = None, *, policy: Policy = ...) -> None: ... - def parse(self, fp: SupportsRead[str], headersonly: bool = False) -> Message: ... - def parsestr(self, text: str, headersonly: bool = False) -> Message: ... +_MessageT = TypeVar("_MessageT", bound=Message, default=Message) -class HeaderParser(Parser): - def parse(self, fp: SupportsRead[str], headersonly: bool = True) -> Message: ... - def parsestr(self, text: str, headersonly: bool = True) -> Message: ... +class Parser(Generic[_MessageT]): + @overload + def __init__(self: Parser[Message[str, str]], _class: None = None, *, policy: Policy = ...) -> None: ... + @overload + def __init__(self, _class: Callable[[], _MessageT], *, policy: Policy = ...) -> None: ... + def parse(self, fp: SupportsRead[str], headersonly: bool = False) -> _MessageT: ... + def parsestr(self, text: str, headersonly: bool = False) -> _MessageT: ... -class BytesParser: - def __init__(self, _class: Callable[[], Message] = ..., *, policy: Policy = ...) -> None: ... - def parse(self, fp: IO[bytes], headersonly: bool = False) -> Message: ... - def parsebytes(self, text: bytes | bytearray, headersonly: bool = False) -> Message: ... +class HeaderParser(Parser[_MessageT]): + def parse(self, fp: SupportsRead[str], headersonly: bool = True) -> _MessageT: ... + def parsestr(self, text: str, headersonly: bool = True) -> _MessageT: ... -class BytesHeaderParser(BytesParser): - def parse(self, fp: IO[bytes], headersonly: bool = True) -> Message: ... - def parsebytes(self, text: bytes | bytearray, headersonly: bool = True) -> Message: ... +class BytesParser(Generic[_MessageT]): + parser: Parser[_MessageT] + @overload + def __init__(self: BytesParser[Message[str, str]], _class: None = None, *, policy: Policy = ...) -> None: ... + @overload + def __init__(self, _class: Callable[[], _MessageT], *, policy: Policy = ...) -> None: ... + def parse(self, fp: _WrappedBuffer, headersonly: bool = False) -> _MessageT: ... + def parsebytes(self, text: bytes | bytearray, headersonly: bool = False) -> _MessageT: ... + +class BytesHeaderParser(BytesParser[_MessageT]): + def parse(self, fp: _WrappedBuffer, headersonly: bool = True) -> _MessageT: ... + def parsebytes(self, text: bytes | bytearray, headersonly: bool = True) -> _MessageT: ... diff --git a/mypy/typeshed/stdlib/email/policy.pyi b/mypy/typeshed/stdlib/email/policy.pyi index 4df3c1e48b07..5f1cf934eb3c 100644 --- a/mypy/typeshed/stdlib/email/policy.pyi +++ b/mypy/typeshed/stdlib/email/policy.pyi @@ -1,58 +1,15 @@ -from abc import ABCMeta, abstractmethod from collections.abc import Callable +from email._policybase import Compat32 as Compat32, Policy as Policy, compat32 as compat32 from email.contentmanager import ContentManager -from email.errors import MessageDefect -from email.header import Header from email.message import Message from typing import Any __all__ = ["Compat32", "compat32", "Policy", "EmailPolicy", "default", "strict", "SMTP", "HTTP"] -class Policy(metaclass=ABCMeta): - max_line_length: int | None - linesep: str - cte_type: str - raise_on_defect: bool - mangle_from_: bool - message_factory: Callable[[Policy], Message] | None - def __init__( - self, - *, - max_line_length: int | None = ..., - linesep: str = ..., - cte_type: str = ..., - raise_on_defect: bool = ..., - mangle_from_: bool = ..., - message_factory: Callable[[Policy], Message] | None = ..., - ) -> None: ... - def clone(self, **kw: Any) -> Policy: ... - def handle_defect(self, obj: Message, defect: MessageDefect) -> None: ... - def register_defect(self, obj: Message, defect: MessageDefect) -> None: ... - def header_max_count(self, name: str) -> int | None: ... - @abstractmethod - def header_source_parse(self, sourcelines: list[str]) -> tuple[str, str]: ... - @abstractmethod - def header_store_parse(self, name: str, value: str) -> tuple[str, str]: ... - @abstractmethod - def header_fetch_parse(self, name: str, value: str) -> str: ... - @abstractmethod - def fold(self, name: str, value: str) -> str: ... - @abstractmethod - def fold_binary(self, name: str, value: str) -> bytes: ... - -class Compat32(Policy): - def header_source_parse(self, sourcelines: list[str]) -> tuple[str, str]: ... - def header_store_parse(self, name: str, value: str) -> tuple[str, str]: ... - def header_fetch_parse(self, name: str, value: str) -> str | Header: ... # type: ignore[override] - def fold(self, name: str, value: str) -> str: ... - def fold_binary(self, name: str, value: str) -> bytes: ... - -compat32: Compat32 - class EmailPolicy(Policy): utf8: bool refold_source: str - header_factory: Callable[[str, str], str] + header_factory: Callable[[str, Any], Any] content_manager: ContentManager def __init__( self, @@ -69,9 +26,9 @@ class EmailPolicy(Policy): content_manager: ContentManager = ..., ) -> None: ... def header_source_parse(self, sourcelines: list[str]) -> tuple[str, str]: ... - def header_store_parse(self, name: str, value: str) -> tuple[str, str]: ... - def header_fetch_parse(self, name: str, value: str) -> str: ... - def fold(self, name: str, value: str) -> str: ... + def header_store_parse(self, name: str, value: Any) -> tuple[str, Any]: ... + def header_fetch_parse(self, name: str, value: str) -> Any: ... + def fold(self, name: str, value: str) -> Any: ... def fold_binary(self, name: str, value: str) -> bytes: ... default: EmailPolicy diff --git a/mypy/typeshed/stdlib/email/utils.pyi b/mypy/typeshed/stdlib/email/utils.pyi index 090ddf9e31bc..0b62647532db 100644 --- a/mypy/typeshed/stdlib/email/utils.pyi +++ b/mypy/typeshed/stdlib/email/utils.pyi @@ -1,9 +1,10 @@ import datetime import sys +from _typeshed import Unused from email import _ParamType from email.charset import Charset from typing import overload -from typing_extensions import TypeAlias +from typing_extensions import TypeAlias, deprecated __all__ = [ "collapse_rfc2231_value", @@ -51,9 +52,19 @@ else: def mktime_tz(data: _PDTZ) -> int: ... def formatdate(timeval: float | None = None, localtime: bool = False, usegmt: bool = False) -> str: ... def format_datetime(dt: datetime.datetime, usegmt: bool = False) -> str: ... -def localtime(dt: datetime.datetime | None = None, isdst: int = -1) -> datetime.datetime: ... + +if sys.version_info >= (3, 12): + @overload + def localtime(dt: datetime.datetime | None = None) -> datetime.datetime: ... + @overload + @deprecated("The `isdst` parameter does nothing and will be removed in Python 3.14.") + def localtime(dt: datetime.datetime | None = None, isdst: Unused = None) -> datetime.datetime: ... + +else: + def localtime(dt: datetime.datetime | None = None, isdst: int = -1) -> datetime.datetime: ... + def make_msgid(idstring: str | None = None, domain: str | None = None) -> str: ... -def decode_rfc2231(s: str) -> tuple[str | None, str | None, str]: ... +def decode_rfc2231(s: str) -> tuple[str | None, str | None, str]: ... # May return list[str]. See issue #10431 for details. def encode_rfc2231(s: str, charset: str | None = None, language: str | None = None) -> str: ... def collapse_rfc2231_value(value: _ParamType, errors: str = "replace", fallback_charset: str = "us-ascii") -> str: ... def decode_params(params: list[tuple[str, str]]) -> list[tuple[str, _ParamType]]: ... diff --git a/mypy/typeshed/stdlib/encodings/utf_8.pyi b/mypy/typeshed/stdlib/encodings/utf_8.pyi index 0de51026f9f5..bb745399eb8c 100644 --- a/mypy/typeshed/stdlib/encodings/utf_8.pyi +++ b/mypy/typeshed/stdlib/encodings/utf_8.pyi @@ -6,16 +6,16 @@ class IncrementalEncoder(codecs.IncrementalEncoder): class IncrementalDecoder(codecs.BufferedIncrementalDecoder): @staticmethod - def _buffer_decode(__data: ReadableBuffer, __errors: str | None = None, __final: bool = False) -> tuple[str, int]: ... + def _buffer_decode(data: ReadableBuffer, errors: str | None = None, final: bool = False, /) -> tuple[str, int]: ... class StreamWriter(codecs.StreamWriter): @staticmethod - def encode(__str: str, __errors: str | None = None) -> tuple[bytes, int]: ... + def encode(str: str, errors: str | None = None, /) -> tuple[bytes, int]: ... class StreamReader(codecs.StreamReader): @staticmethod - def decode(__data: ReadableBuffer, __errors: str | None = None, __final: bool = False) -> tuple[str, int]: ... + def decode(data: ReadableBuffer, errors: str | None = None, final: bool = False, /) -> tuple[str, int]: ... def getregentry() -> codecs.CodecInfo: ... -def encode(__str: str, __errors: str | None = None) -> tuple[bytes, int]: ... +def encode(str: str, errors: str | None = None, /) -> tuple[bytes, int]: ... def decode(input: ReadableBuffer, errors: str | None = "strict") -> tuple[str, int]: ... diff --git a/mypy/typeshed/stdlib/encodings/utf_8_sig.pyi b/mypy/typeshed/stdlib/encodings/utf_8_sig.pyi index 150fe22f8f6e..af69217d6732 100644 --- a/mypy/typeshed/stdlib/encodings/utf_8_sig.pyi +++ b/mypy/typeshed/stdlib/encodings/utf_8_sig.pyi @@ -4,7 +4,7 @@ from _typeshed import ReadableBuffer class IncrementalEncoder(codecs.IncrementalEncoder): def __init__(self, errors: str = "strict") -> None: ... def encode(self, input: str, final: bool = False) -> bytes: ... - def getstate(self) -> int: ... # type: ignore[override] + def getstate(self) -> int: ... def setstate(self, state: int) -> None: ... # type: ignore[override] class IncrementalDecoder(codecs.BufferedIncrementalDecoder): diff --git a/mypy/typeshed/stdlib/enum.pyi b/mypy/typeshed/stdlib/enum.pyi index b46fe429cacb..96cb2264ea20 100644 --- a/mypy/typeshed/stdlib/enum.pyi +++ b/mypy/typeshed/stdlib/enum.pyi @@ -2,11 +2,10 @@ import _typeshed import sys import types from _typeshed import SupportsKeysAndGetItem, Unused -from abc import ABCMeta from builtins import property as _builtins_property -from collections.abc import Iterable, Iterator, Mapping -from typing import Any, Generic, TypeVar, overload -from typing_extensions import Literal, Self, TypeAlias +from collections.abc import Callable, Iterable, Iterator, Mapping +from typing import Any, Generic, Literal, TypeVar, overload +from typing_extensions import Self, TypeAlias __all__ = ["EnumMeta", "Enum", "IntEnum", "Flag", "IntFlag", "auto", "unique"] @@ -34,6 +33,9 @@ if sys.version_info >= (3, 11): "verify", ] +if sys.version_info >= (3, 11): + __all__ += ["pickle_by_enum_name", "pickle_by_global_name"] + _EnumMemberT = TypeVar("_EnumMemberT") _EnumerationT = TypeVar("_EnumerationT", bound=type[Enum]) @@ -73,12 +75,8 @@ class _EnumDict(dict[str, Any]): @overload def update(self, members: Iterable[tuple[str, Any]], **more_members: Any) -> None: ... -# Note: EnumMeta actually subclasses type directly, not ABCMeta. -# This is a temporary workaround to allow multiple creation of enums with builtins -# such as str as mixins, which due to the handling of ABCs of builtin types, cause -# spurious inconsistent metaclass structure. See #1595. # Structurally: Iterable[T], Reversible[T], Container[T] where T is the enum itself -class EnumMeta(ABCMeta): +class EnumMeta(type): if sys.version_info >= (3, 11): def __new__( metacls: type[_typeshed.Self], @@ -106,17 +104,27 @@ class EnumMeta(ABCMeta): def __iter__(self: type[_EnumMemberT]) -> Iterator[_EnumMemberT]: ... def __reversed__(self: type[_EnumMemberT]) -> Iterator[_EnumMemberT]: ... - def __contains__(self: type[Any], obj: object) -> bool: ... + if sys.version_info >= (3, 12): + def __contains__(self: type[Any], value: object) -> bool: ... + elif sys.version_info >= (3, 11): + def __contains__(self: type[Any], member: object) -> bool: ... + elif sys.version_info >= (3, 10): + def __contains__(self: type[Any], obj: object) -> bool: ... + else: + def __contains__(self: type[Any], member: object) -> bool: ... + def __getitem__(self: type[_EnumMemberT], name: str) -> _EnumMemberT: ... @_builtins_property def __members__(self: type[_EnumMemberT]) -> types.MappingProxyType[str, _EnumMemberT]: ... def __len__(self) -> int: ... def __bool__(self) -> Literal[True]: ... def __dir__(self) -> list[str]: ... - # Simple value lookup - @overload # type: ignore[override] + + # Overload 1: Value lookup on an already existing enum class (simple case) + @overload def __call__(cls: type[_EnumMemberT], value: Any, names: None = None) -> _EnumMemberT: ... - # Functional Enum API + + # Overload 2: Functional API for constructing new enum classes. if sys.version_info >= (3, 11): @overload def __call__( @@ -142,6 +150,18 @@ class EnumMeta(ABCMeta): type: type | None = None, start: int = 1, ) -> type[Enum]: ... + + # Overload 3 (py312+ only): Value lookup on an already existing enum class (complex case) + # + # >>> class Foo(enum.Enum): + # ... X = 1, 2, 3 + # >>> Foo(1, 2, 3) + # + # + if sys.version_info >= (3, 12): + @overload + def __call__(cls: type[_EnumMemberT], value: Any, *values: Any) -> _EnumMemberT: ... + _member_names_: list[str] # undocumented _member_map_: dict[str, Enum] # undocumented _value2member_map_: dict[Any, Enum] # undocumented @@ -154,6 +174,8 @@ if sys.version_info >= (3, 11): def __set_name__(self, ownerclass: type[Enum], name: str) -> None: ... name: str clsname: str + member: Enum | None + _magic_enum_attr = property else: _magic_enum_attr = types.DynamicClassAttribute @@ -179,8 +201,15 @@ class Enum(metaclass=EnumMeta): # and in practice using `object` here has the same effect as using `Any`. def __new__(cls, value: object) -> Self: ... def __dir__(self) -> list[str]: ... + def __hash__(self) -> int: ... def __format__(self, format_spec: str) -> str: ... def __reduce_ex__(self, proto: Unused) -> tuple[Any, ...]: ... + if sys.version_info >= (3, 11): + def __copy__(self) -> Self: ... + def __deepcopy__(self, memo: Any) -> Self: ... + if sys.version_info >= (3, 12): + @classmethod + def __signature__(cls) -> str: ... if sys.version_info >= (3, 11): class ReprEnum(Enum): ... @@ -200,13 +229,6 @@ def unique(enumeration: _EnumerationT) -> _EnumerationT: ... _auto_null: Any -# subclassing IntFlag so it picks up all implemented base functions, best modeling behavior of enum.auto() -class auto(IntFlag): - _value_: Any - @_magic_enum_attr - def value(self) -> Any: ... - def __new__(cls) -> Self: ... - class Flag(Enum): _name_: str | None # type: ignore[assignment] _value_: int @@ -227,38 +249,20 @@ class Flag(Enum): __rand__ = __and__ __rxor__ = __xor__ -if sys.version_info >= (3, 11): - # The body of the class is the same, but the base classes are different. - class IntFlag(int, ReprEnum, Flag, boundary=KEEP): # type: ignore[misc] # complaints about incompatible bases - def __new__(cls, value: int) -> Self: ... - def __or__(self, other: int) -> Self: ... - def __and__(self, other: int) -> Self: ... - def __xor__(self, other: int) -> Self: ... - __ror__ = __or__ - __rand__ = __and__ - __rxor__ = __xor__ - -else: - class IntFlag(int, Flag): # type: ignore[misc] # complaints about incompatible bases - def __new__(cls, value: int) -> Self: ... - def __or__(self, other: int) -> Self: ... - def __and__(self, other: int) -> Self: ... - def __xor__(self, other: int) -> Self: ... - __ror__ = __or__ - __rand__ = __and__ - __rxor__ = __xor__ - if sys.version_info >= (3, 11): class StrEnum(str, ReprEnum): def __new__(cls, value: str) -> Self: ... _value_: str @_magic_enum_attr def value(self) -> str: ... + @staticmethod + def _generate_next_value_(name: str, start: int, count: int, last_values: list[str]) -> str: ... class EnumCheck(StrEnum): CONTINUOUS: str NAMED_FLAGS: str UNIQUE: str + CONTINUOUS = EnumCheck.CONTINUOUS NAMED_FLAGS = EnumCheck.NAMED_FLAGS UNIQUE = EnumCheck.UNIQUE @@ -272,6 +276,7 @@ if sys.version_info >= (3, 11): CONFORM: str EJECT: str KEEP: str + STRICT = FlagBoundary.STRICT CONFORM = FlagBoundary.CONFORM EJECT = FlagBoundary.EJECT @@ -281,3 +286,35 @@ if sys.version_info >= (3, 11): def global_enum(cls: _EnumerationT, update_str: bool = False) -> _EnumerationT: ... def global_enum_repr(self: Enum) -> str: ... def global_flag_repr(self: Flag) -> str: ... + +if sys.version_info >= (3, 11): + # The body of the class is the same, but the base classes are different. + class IntFlag(int, ReprEnum, Flag, boundary=KEEP): # type: ignore[misc] # complaints about incompatible bases + def __new__(cls, value: int) -> Self: ... + def __or__(self, other: int) -> Self: ... + def __and__(self, other: int) -> Self: ... + def __xor__(self, other: int) -> Self: ... + __ror__ = __or__ + __rand__ = __and__ + __rxor__ = __xor__ + +else: + class IntFlag(int, Flag): # type: ignore[misc] # complaints about incompatible bases + def __new__(cls, value: int) -> Self: ... + def __or__(self, other: int) -> Self: ... + def __and__(self, other: int) -> Self: ... + def __xor__(self, other: int) -> Self: ... + __ror__ = __or__ + __rand__ = __and__ + __rxor__ = __xor__ + +# subclassing IntFlag so it picks up all implemented base functions, best modeling behavior of enum.auto() +class auto(IntFlag): + _value_: Any + @_magic_enum_attr + def value(self) -> Any: ... + def __new__(cls) -> Self: ... + +if sys.version_info >= (3, 11): + def pickle_by_global_name(self: Enum, proto: int) -> str: ... + def pickle_by_enum_name(self: _EnumMemberT, proto: int) -> tuple[Callable[..., Any], tuple[type[_EnumMemberT], str]]: ... diff --git a/mypy/typeshed/stdlib/errno.pyi b/mypy/typeshed/stdlib/errno.pyi index 28874d44ff5f..84d2b44a6a61 100644 --- a/mypy/typeshed/stdlib/errno.pyi +++ b/mypy/typeshed/stdlib/errno.pyi @@ -91,9 +91,15 @@ ECANCELED: int # undocumented ENOTRECOVERABLE: int # undocumented EOWNERDEAD: int # undocumented +if sys.platform == "sunos5" or sys.platform == "solaris": # noqa: Y008 + ELOCKUNMAPPED: int + ENOTACTIVE: int + if sys.platform != "win32": ENOTBLK: int EMULTIHOP: int + +if sys.platform == "darwin": # All of the below are undocumented EAUTH: int EBADARCH: int @@ -112,9 +118,8 @@ if sys.platform != "win32": EPWROFF: int ERPCMISMATCH: int ESHLIBVERS: int - - if sys.platform != "darwin" or sys.version_info >= (3, 11): - EQFULL: int # undocumented + if sys.version_info >= (3, 11): + EQFULL: int if sys.platform != "darwin": EDEADLOCK: int @@ -164,9 +169,6 @@ if sys.platform != "win32" and sys.platform != "darwin": ENOKEY: int ENOMEDIUM: int ERFKILL: int - EL: int - ELOCKUNMAPPED: int - ENOTACTIVE: int if sys.platform == "win32": # All of these are undocumented diff --git a/mypy/typeshed/stdlib/fcntl.pyi b/mypy/typeshed/stdlib/fcntl.pyi index 90676e365712..ccf638205bbe 100644 --- a/mypy/typeshed/stdlib/fcntl.pyi +++ b/mypy/typeshed/stdlib/fcntl.pyi @@ -1,7 +1,7 @@ import sys from _typeshed import FileDescriptorLike, ReadOnlyBuffer, WriteableBuffer -from typing import Any, overload -from typing_extensions import Literal +from typing import Any, Literal, overload +from typing_extensions import Buffer if sys.platform != "win32": FASYNC: int @@ -20,6 +20,9 @@ if sys.platform != "win32": F_SETOWN: int F_UNLCK: int F_WRLCK: int + + F_GETLEASE: int + F_SETLEASE: int if sys.platform == "darwin": F_FULLFSYNC: int F_NOCACHE: int @@ -30,19 +33,16 @@ if sys.platform != "win32": F_SETSIG: int F_SHLCK: int F_SETLK64: int - F_SETLEASE: int F_GETSIG: int F_NOTIFY: int F_EXLCK: int - F_GETLEASE: int F_GETLK64: int - if sys.version_info >= (3, 8): - F_ADD_SEALS: int - F_GET_SEALS: int - F_SEAL_GROW: int - F_SEAL_SEAL: int - F_SEAL_SHRINK: int - F_SEAL_WRITE: int + F_ADD_SEALS: int + F_GET_SEALS: int + F_SEAL_GROW: int + F_SEAL_SEAL: int + F_SEAL_SHRINK: int + F_SEAL_WRITE: int if sys.version_info >= (3, 9): F_OFD_GETLK: int F_OFD_SETLK: int @@ -100,17 +100,28 @@ if sys.platform != "win32": I_STR: int I_SWROPT: int I_UNLINK: int + + if sys.version_info >= (3, 12) and sys.platform == "linux": + FICLONE: int + FICLONERANGE: int + @overload - def fcntl(__fd: FileDescriptorLike, __cmd: int, __arg: int = 0) -> int: ... + def fcntl(fd: FileDescriptorLike, cmd: int, arg: int = 0, /) -> int: ... @overload - def fcntl(__fd: FileDescriptorLike, __cmd: int, __arg: str | ReadOnlyBuffer) -> bytes: ... + def fcntl(fd: FileDescriptorLike, cmd: int, arg: str | ReadOnlyBuffer, /) -> bytes: ... + # If arg is an int, return int @overload - def ioctl(__fd: FileDescriptorLike, __request: int, __arg: int = 0, __mutate_flag: bool = True) -> int: ... + def ioctl(fd: FileDescriptorLike, request: int, arg: int = 0, mutate_flag: bool = True, /) -> int: ... + # The return type works as follows: + # - If arg is a read-write buffer, return int if mutate_flag is True, otherwise bytes + # - If arg is a read-only buffer, return bytes (and ignore the value of mutate_flag) + # We can't represent that precisely as we can't distinguish between read-write and read-only + # buffers, so we add overloads for a few unambiguous cases and use Any for the rest. @overload - def ioctl(__fd: FileDescriptorLike, __request: int, __arg: WriteableBuffer, __mutate_flag: Literal[True] = True) -> int: ... + def ioctl(fd: FileDescriptorLike, request: int, arg: bytes, mutate_flag: bool = True, /) -> bytes: ... @overload - def ioctl(__fd: FileDescriptorLike, __request: int, __arg: WriteableBuffer, __mutate_flag: Literal[False]) -> bytes: ... + def ioctl(fd: FileDescriptorLike, request: int, arg: WriteableBuffer, mutate_flag: Literal[False], /) -> bytes: ... @overload - def ioctl(__fd: FileDescriptorLike, __request: int, __arg: ReadOnlyBuffer, __mutate_flag: bool = True) -> bytes: ... - def flock(__fd: FileDescriptorLike, __operation: int) -> None: ... - def lockf(__fd: FileDescriptorLike, __cmd: int, __len: int = 0, __start: int = 0, __whence: int = 0) -> Any: ... + def ioctl(fd: FileDescriptorLike, request: int, arg: Buffer, mutate_flag: bool = True, /) -> Any: ... + def flock(fd: FileDescriptorLike, operation: int, /) -> None: ... + def lockf(fd: FileDescriptorLike, cmd: int, len: int = 0, start: int = 0, whence: int = 0, /) -> Any: ... diff --git a/mypy/typeshed/stdlib/filecmp.pyi b/mypy/typeshed/stdlib/filecmp.pyi index 008d7a44e6c4..4f54a9bff6ee 100644 --- a/mypy/typeshed/stdlib/filecmp.pyi +++ b/mypy/typeshed/stdlib/filecmp.pyi @@ -1,8 +1,7 @@ import sys from _typeshed import GenericPath, StrOrBytesPath from collections.abc import Callable, Iterable, Sequence -from typing import Any, AnyStr, Generic -from typing_extensions import Literal +from typing import Any, AnyStr, Generic, Literal if sys.version_info >= (3, 9): from types import GenericAlias diff --git a/mypy/typeshed/stdlib/fileinput.pyi b/mypy/typeshed/stdlib/fileinput.pyi index e9f3713b4eaf..e8d5dd8d2d5b 100644 --- a/mypy/typeshed/stdlib/fileinput.pyi +++ b/mypy/typeshed/stdlib/fileinput.pyi @@ -2,8 +2,8 @@ import sys from _typeshed import AnyStr_co, StrOrBytesPath from collections.abc import Callable, Iterable, Iterator from types import TracebackType -from typing import IO, Any, AnyStr, Generic, Protocol, overload -from typing_extensions import Literal, Self, TypeAlias +from typing import IO, Any, AnyStr, Literal, Protocol, overload +from typing_extensions import Self, TypeAlias if sys.version_info >= (3, 9): from types import GenericAlias @@ -68,7 +68,7 @@ if sys.version_info >= (3, 10): errors: str | None = None, ) -> FileInput[Any]: ... -elif sys.version_info >= (3, 8): +else: # bufsize is dropped and mode and openhook become keyword-only @overload def input( @@ -98,57 +98,6 @@ elif sys.version_info >= (3, 8): openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = None, ) -> FileInput[Any]: ... -else: - @overload - def input( - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, - inplace: bool = False, - backup: str = "", - bufsize: int = 0, - mode: _TextMode = "r", - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[str]] | None = None, - ) -> FileInput[str]: ... - # Because mode isn't keyword-only here yet, we need two overloads each for - # the bytes case and the fallback case. - @overload - def input( - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, - inplace: bool = False, - backup: str = "", - bufsize: int = 0, - *, - mode: Literal["rb"], - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[bytes]] | None = None, - ) -> FileInput[bytes]: ... - @overload - def input( - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None, - inplace: bool, - backup: str, - bufsize: int, - mode: Literal["rb"], - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[bytes]] | None = None, - ) -> FileInput[bytes]: ... - @overload - def input( - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, - inplace: bool = False, - backup: str = "", - bufsize: int = 0, - *, - mode: str, - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = None, - ) -> FileInput[Any]: ... - @overload - def input( - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None, - inplace: bool, - backup: str, - bufsize: int, - mode: str, - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = None, - ) -> FileInput[Any]: ... - def close() -> None: ... def nextfile() -> None: ... def filename() -> str: ... @@ -158,7 +107,7 @@ def fileno() -> int: ... def isfirstline() -> bool: ... def isstdin() -> bool: ... -class FileInput(Iterator[AnyStr], Generic[AnyStr]): +class FileInput(Iterator[AnyStr]): if sys.version_info >= (3, 10): # encoding and errors are added @overload @@ -198,7 +147,7 @@ class FileInput(Iterator[AnyStr], Generic[AnyStr]): errors: str | None = None, ) -> None: ... - elif sys.version_info >= (3, 8): + else: # bufsize is dropped and mode and openhook become keyword-only @overload def __init__( @@ -231,62 +180,6 @@ class FileInput(Iterator[AnyStr], Generic[AnyStr]): openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = None, ) -> None: ... - else: - @overload - def __init__( - self: FileInput[str], - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, - inplace: bool = False, - backup: str = "", - bufsize: int = 0, - mode: _TextMode = "r", - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[str]] | None = None, - ) -> None: ... - # Because mode isn't keyword-only here yet, we need two overloads each for - # the bytes case and the fallback case. - @overload - def __init__( - self: FileInput[bytes], - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, - inplace: bool = False, - backup: str = "", - bufsize: int = 0, - *, - mode: Literal["rb"], - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[bytes]] | None = None, - ) -> None: ... - @overload - def __init__( - self: FileInput[bytes], - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None, - inplace: bool, - backup: str, - bufsize: int, - mode: Literal["rb"], - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[bytes]] | None = None, - ) -> None: ... - @overload - def __init__( - self: FileInput[Any], - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, - inplace: bool = False, - backup: str = "", - bufsize: int = 0, - *, - mode: str, - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = None, - ) -> None: ... - @overload - def __init__( - self: FileInput[Any], - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None, - inplace: bool, - backup: str, - bufsize: int, - mode: str, - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = None, - ) -> None: ... - def __del__(self) -> None: ... def close(self) -> None: ... def __enter__(self) -> Self: ... diff --git a/mypy/typeshed/stdlib/fractions.pyi b/mypy/typeshed/stdlib/fractions.pyi index 97cefc916d9b..086aff50344c 100644 --- a/mypy/typeshed/stdlib/fractions.pyi +++ b/mypy/typeshed/stdlib/fractions.pyi @@ -2,8 +2,8 @@ import sys from collections.abc import Callable from decimal import Decimal from numbers import Integral, Rational, Real -from typing import Any, overload -from typing_extensions import Literal, Self, SupportsIndex, TypeAlias +from typing import Any, Literal, SupportsIndex, overload +from typing_extensions import Self, TypeAlias _ComparableNum: TypeAlias = int | float | Decimal | Real @@ -22,18 +22,17 @@ else: class Fraction(Rational): @overload - def __new__( - cls, numerator: int | Rational = 0, denominator: int | Rational | None = None, *, _normalize: bool = True - ) -> Self: ... + def __new__(cls, numerator: int | Rational = 0, denominator: int | Rational | None = None) -> Self: ... @overload - def __new__(cls, __value: float | Decimal | str, *, _normalize: bool = True) -> Self: ... + def __new__(cls, value: float | Decimal | str, /) -> Self: ... @classmethod def from_float(cls, f: float) -> Self: ... @classmethod def from_decimal(cls, dec: Decimal) -> Self: ... def limit_denominator(self, max_denominator: int = 1000000) -> Fraction: ... - if sys.version_info >= (3, 8): - def as_integer_ratio(self) -> tuple[int, int]: ... + def as_integer_ratio(self) -> tuple[int, int]: ... + if sys.version_info >= (3, 12): + def is_integer(self) -> bool: ... @property def numerator(a) -> int: ... @@ -108,9 +107,9 @@ class Fraction(Rational): @overload def __divmod__(a, b: float) -> tuple[float, Fraction]: ... @overload - def __rdivmod__(b, a: int | Fraction) -> tuple[int, Fraction]: ... + def __rdivmod__(a, b: int | Fraction) -> tuple[int, Fraction]: ... @overload - def __rdivmod__(b, a: float) -> tuple[float, Fraction]: ... + def __rdivmod__(a, b: float) -> tuple[float, Fraction]: ... @overload def __pow__(a, b: int) -> Fraction: ... @overload diff --git a/mypy/typeshed/stdlib/ftplib.pyi b/mypy/typeshed/stdlib/ftplib.pyi index 76d9dc02a5da..9e7097ddc56e 100644 --- a/mypy/typeshed/stdlib/ftplib.pyi +++ b/mypy/typeshed/stdlib/ftplib.pyi @@ -4,8 +4,8 @@ from collections.abc import Callable, Iterable, Iterator from socket import socket from ssl import SSLContext from types import TracebackType -from typing import Any, TextIO -from typing_extensions import Literal, Self +from typing import Any, Literal, TextIO +from typing_extensions import Self __all__ = ["FTP", "error_reply", "error_temp", "error_perm", "error_proto", "all_errors", "FTP_TLS"] @@ -31,7 +31,7 @@ class FTP: sock: socket | None welcome: str | None passiveserver: int - timeout: int + timeout: float | None af: int lastresp: str file: TextIO | None @@ -48,7 +48,7 @@ class FTP: user: str = "", passwd: str = "", acct: str = "", - timeout: float = ..., + timeout: float | None = ..., source_address: tuple[str, int] | None = None, *, encoding: str = "utf-8", @@ -60,7 +60,7 @@ class FTP: user: str = "", passwd: str = "", acct: str = "", - timeout: float = ..., + timeout: float | None = ..., source_address: tuple[str, int] | None = None, ) -> None: ... @@ -87,7 +87,7 @@ class FTP: def makepasv(self) -> tuple[str, int]: ... def login(self, user: str = "", passwd: str = "", acct: str = "") -> str: ... # In practice, `rest` rest can actually be anything whose str() is an integer sequence, so to make it simple we allow integers. - def ntransfercmd(self, cmd: str, rest: int | str | None = None) -> tuple[socket, int]: ... + def ntransfercmd(self, cmd: str, rest: int | str | None = None) -> tuple[socket, int | None]: ... def transfercmd(self, cmd: str, rest: int | str | None = None) -> socket: ... def retrbinary( self, cmd: str, callback: Callable[[bytes], object], blocksize: int = 8192, rest: int | str | None = None @@ -106,7 +106,7 @@ class FTP: def nlst(self, *args: str) -> list[str]: ... # Technically only the last arg can be a Callable but ... def dir(self, *args: str | Callable[[str], object]) -> None: ... - def mlsd(self, path: str = "", facts: Iterable[str] = ...) -> Iterator[tuple[str, dict[str, str]]]: ... + def mlsd(self, path: str = "", facts: Iterable[str] = []) -> Iterator[tuple[str, dict[str, str]]]: ... def rename(self, fromname: str, toname: str) -> str: ... def delete(self, filename: str) -> str: ... def cwd(self, dirname: str) -> str: ... @@ -118,7 +118,20 @@ class FTP: def close(self) -> None: ... class FTP_TLS(FTP): - if sys.version_info >= (3, 9): + if sys.version_info >= (3, 12): + def __init__( + self, + host: str = "", + user: str = "", + passwd: str = "", + acct: str = "", + *, + context: SSLContext | None = None, + timeout: float | None = ..., + source_address: tuple[str, int] | None = None, + encoding: str = "utf-8", + ) -> None: ... + elif sys.version_info >= (3, 9): def __init__( self, host: str = "", @@ -128,7 +141,7 @@ class FTP_TLS(FTP): keyfile: str | None = None, certfile: str | None = None, context: SSLContext | None = None, - timeout: float = ..., + timeout: float | None = ..., source_address: tuple[str, int] | None = None, *, encoding: str = "utf-8", @@ -143,7 +156,7 @@ class FTP_TLS(FTP): keyfile: str | None = None, certfile: str | None = None, context: SSLContext | None = None, - timeout: float = ..., + timeout: float | None = ..., source_address: tuple[str, int] | None = None, ) -> None: ... ssl_version: int diff --git a/mypy/typeshed/stdlib/functools.pyi b/mypy/typeshed/stdlib/functools.pyi index 1214e349f605..27550cfe08e6 100644 --- a/mypy/typeshed/stdlib/functools.pyi +++ b/mypy/typeshed/stdlib/functools.pyi @@ -1,9 +1,9 @@ import sys import types -from _typeshed import IdentityFunction, SupportsAllComparisons, SupportsItems +from _typeshed import SupportsAllComparisons, SupportsItems from collections.abc import Callable, Hashable, Iterable, Sequence, Sized -from typing import Any, Generic, NamedTuple, TypeVar, overload -from typing_extensions import Literal, Self, TypeAlias, final +from typing import Any, Generic, Literal, NamedTuple, TypedDict, TypeVar, final, overload +from typing_extensions import ParamSpec, Self, TypeAlias if sys.version_info >= (3, 9): from types import GenericAlias @@ -20,55 +20,105 @@ __all__ = [ "partial", "partialmethod", "singledispatch", + "cached_property", + "singledispatchmethod", ] -if sys.version_info >= (3, 8): - __all__ += ["cached_property", "singledispatchmethod"] - if sys.version_info >= (3, 9): __all__ += ["cache"] -_AnyCallable: TypeAlias = Callable[..., object] - _T = TypeVar("_T") +_T_co = TypeVar("_T_co", covariant=True) _S = TypeVar("_S") +_PWrapped = ParamSpec("_PWrapped") +_RWrapped = TypeVar("_RWrapped") +_PWrapper = ParamSpec("_PWrapper") +_RWrapper = TypeVar("_RWrapper") @overload -def reduce(function: Callable[[_T, _S], _T], sequence: Iterable[_S], initial: _T) -> _T: ... +def reduce(function: Callable[[_T, _S], _T], sequence: Iterable[_S], initial: _T, /) -> _T: ... @overload -def reduce(function: Callable[[_T, _T], _T], sequence: Iterable[_T]) -> _T: ... +def reduce(function: Callable[[_T, _T], _T], sequence: Iterable[_T], /) -> _T: ... class _CacheInfo(NamedTuple): hits: int misses: int - maxsize: int + maxsize: int | None currsize: int +if sys.version_info >= (3, 9): + class _CacheParameters(TypedDict): + maxsize: int + typed: bool + @final class _lru_cache_wrapper(Generic[_T]): __wrapped__: Callable[..., _T] def __call__(self, *args: Hashable, **kwargs: Hashable) -> _T: ... def cache_info(self) -> _CacheInfo: ... def cache_clear(self) -> None: ... - def __copy__(self) -> _lru_cache_wrapper[_T]: ... - def __deepcopy__(self, __memo: Any) -> _lru_cache_wrapper[_T]: ... + if sys.version_info >= (3, 9): + def cache_parameters(self) -> _CacheParameters: ... -if sys.version_info >= (3, 8): - @overload - def lru_cache(maxsize: int | None = 128, typed: bool = False) -> Callable[[Callable[..., _T]], _lru_cache_wrapper[_T]]: ... - @overload - def lru_cache(maxsize: Callable[..., _T], typed: bool = False) -> _lru_cache_wrapper[_T]: ... + def __copy__(self) -> _lru_cache_wrapper[_T]: ... + def __deepcopy__(self, memo: Any, /) -> _lru_cache_wrapper[_T]: ... +@overload +def lru_cache(maxsize: int | None = 128, typed: bool = False) -> Callable[[Callable[..., _T]], _lru_cache_wrapper[_T]]: ... +@overload +def lru_cache(maxsize: Callable[..., _T], typed: bool = False) -> _lru_cache_wrapper[_T]: ... + +if sys.version_info >= (3, 12): + WRAPPER_ASSIGNMENTS: tuple[ + Literal["__module__"], + Literal["__name__"], + Literal["__qualname__"], + Literal["__doc__"], + Literal["__annotations__"], + Literal["__type_params__"], + ] else: - def lru_cache(maxsize: int | None = 128, typed: bool = False) -> Callable[[Callable[..., _T]], _lru_cache_wrapper[_T]]: ... - -WRAPPER_ASSIGNMENTS: tuple[ - Literal["__module__"], Literal["__name__"], Literal["__qualname__"], Literal["__doc__"], Literal["__annotations__"] -] + WRAPPER_ASSIGNMENTS: tuple[ + Literal["__module__"], Literal["__name__"], Literal["__qualname__"], Literal["__doc__"], Literal["__annotations__"] + ] WRAPPER_UPDATES: tuple[Literal["__dict__"]] -def update_wrapper(wrapper: _T, wrapped: _AnyCallable, assigned: Sequence[str] = ..., updated: Sequence[str] = ...) -> _T: ... -def wraps(wrapped: _AnyCallable, assigned: Sequence[str] = ..., updated: Sequence[str] = ...) -> IdentityFunction: ... +class _Wrapped(Generic[_PWrapped, _RWrapped, _PWrapper, _RWrapper]): + __wrapped__: Callable[_PWrapped, _RWrapped] + def __call__(self, *args: _PWrapper.args, **kwargs: _PWrapper.kwargs) -> _RWrapper: ... + # as with ``Callable``, we'll assume that these attributes exist + __name__: str + __qualname__: str + +class _Wrapper(Generic[_PWrapped, _RWrapped]): + def __call__(self, f: Callable[_PWrapper, _RWrapper]) -> _Wrapped[_PWrapped, _RWrapped, _PWrapper, _RWrapper]: ... + +if sys.version_info >= (3, 12): + def update_wrapper( + wrapper: Callable[_PWrapper, _RWrapper], + wrapped: Callable[_PWrapped, _RWrapped], + assigned: Sequence[str] = ("__module__", "__name__", "__qualname__", "__doc__", "__annotations__", "__type_params__"), + updated: Sequence[str] = ("__dict__",), + ) -> _Wrapped[_PWrapped, _RWrapped, _PWrapper, _RWrapper]: ... + def wraps( + wrapped: Callable[_PWrapped, _RWrapped], + assigned: Sequence[str] = ("__module__", "__name__", "__qualname__", "__doc__", "__annotations__", "__type_params__"), + updated: Sequence[str] = ("__dict__",), + ) -> _Wrapper[_PWrapped, _RWrapped]: ... + +else: + def update_wrapper( + wrapper: Callable[_PWrapper, _RWrapper], + wrapped: Callable[_PWrapped, _RWrapped], + assigned: Sequence[str] = ("__module__", "__name__", "__qualname__", "__doc__", "__annotations__"), + updated: Sequence[str] = ("__dict__",), + ) -> _Wrapped[_PWrapped, _RWrapped, _PWrapper, _RWrapper]: ... + def wraps( + wrapped: Callable[_PWrapped, _RWrapped], + assigned: Sequence[str] = ("__module__", "__name__", "__qualname__", "__doc__", "__annotations__"), + updated: Sequence[str] = ("__dict__",), + ) -> _Wrapper[_PWrapped, _RWrapped]: ... + def total_ordering(cls: type[_T]) -> type[_T]: ... def cmp_to_key(mycmp: Callable[[_T, _T], int]) -> Callable[[_T], SupportsAllComparisons]: ... @@ -79,8 +129,8 @@ class partial(Generic[_T]): def args(self) -> tuple[Any, ...]: ... @property def keywords(self) -> dict[str, Any]: ... - def __new__(cls, __func: Callable[..., _T], *args: Any, **kwargs: Any) -> Self: ... - def __call__(__self, *args: Any, **kwargs: Any) -> _T: ... + def __new__(cls, func: Callable[..., _T], /, *args: Any, **kwargs: Any) -> Self: ... + def __call__(self, /, *args: Any, **kwargs: Any) -> _T: ... if sys.version_info >= (3, 9): def __class_getitem__(cls, item: Any) -> GenericAlias: ... @@ -92,14 +142,10 @@ class partialmethod(Generic[_T]): args: tuple[Any, ...] keywords: dict[str, Any] @overload - def __init__(self, __func: Callable[..., _T], *args: Any, **keywords: Any) -> None: ... + def __init__(self, func: Callable[..., _T], /, *args: Any, **keywords: Any) -> None: ... @overload - def __init__(self, __func: _Descriptor, *args: Any, **keywords: Any) -> None: ... - if sys.version_info >= (3, 8): - def __get__(self, obj: Any, cls: type[Any] | None = None) -> Callable[..., _T]: ... - else: - def __get__(self, obj: Any, cls: type[Any] | None) -> Callable[..., _T]: ... - + def __init__(self, func: _Descriptor, /, *args: Any, **keywords: Any) -> None: ... + def __get__(self, obj: Any, cls: type[Any] | None = None) -> Callable[..., _T]: ... @property def __isabstractmethod__(self) -> bool: ... if sys.version_info >= (3, 9): @@ -120,39 +166,40 @@ class _SingleDispatchCallable(Generic[_T]): @overload def register(self, cls: type[Any], func: Callable[..., _T]) -> Callable[..., _T]: ... def _clear_cache(self) -> None: ... - def __call__(__self, *args: Any, **kwargs: Any) -> _T: ... + def __call__(self, /, *args: Any, **kwargs: Any) -> _T: ... def singledispatch(func: Callable[..., _T]) -> _SingleDispatchCallable[_T]: ... -if sys.version_info >= (3, 8): - class singledispatchmethod(Generic[_T]): - dispatcher: _SingleDispatchCallable[_T] - func: Callable[..., _T] - def __init__(self, func: Callable[..., _T]) -> None: ... - @property - def __isabstractmethod__(self) -> bool: ... - @overload - def register(self, cls: type[Any], method: None = None) -> Callable[[Callable[..., _T]], Callable[..., _T]]: ... - @overload - def register(self, cls: Callable[..., _T], method: None = None) -> Callable[..., _T]: ... - @overload - def register(self, cls: type[Any], method: Callable[..., _T]) -> Callable[..., _T]: ... - def __get__(self, obj: _S, cls: type[_S] | None = None) -> Callable[..., _T]: ... - - class cached_property(Generic[_T]): - func: Callable[[Any], _T] - attrname: str | None - def __init__(self, func: Callable[[Any], _T]) -> None: ... - @overload - def __get__(self, instance: None, owner: type[Any] | None = None) -> cached_property[_T]: ... - @overload - def __get__(self, instance: object, owner: type[Any] | None = None) -> _T: ... - def __set_name__(self, owner: type[Any], name: str) -> None: ... - if sys.version_info >= (3, 9): - def __class_getitem__(cls, item: Any) -> GenericAlias: ... +class singledispatchmethod(Generic[_T]): + dispatcher: _SingleDispatchCallable[_T] + func: Callable[..., _T] + def __init__(self, func: Callable[..., _T]) -> None: ... + @property + def __isabstractmethod__(self) -> bool: ... + @overload + def register(self, cls: type[Any], method: None = None) -> Callable[[Callable[..., _T]], Callable[..., _T]]: ... + @overload + def register(self, cls: Callable[..., _T], method: None = None) -> Callable[..., _T]: ... + @overload + def register(self, cls: type[Any], method: Callable[..., _T]) -> Callable[..., _T]: ... + def __get__(self, obj: _S, cls: type[_S] | None = None) -> Callable[..., _T]: ... + +class cached_property(Generic[_T_co]): + func: Callable[[Any], _T_co] + attrname: str | None + def __init__(self, func: Callable[[Any], _T_co]) -> None: ... + @overload + def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ... + @overload + def __get__(self, instance: object, owner: type[Any] | None = None) -> _T_co: ... + def __set_name__(self, owner: type[Any], name: str) -> None: ... + # __set__ is not defined at runtime, but @cached_property is designed to be settable + def __set__(self, instance: object, value: _T_co) -> None: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] + if sys.version_info >= (3, 9): + def __class_getitem__(cls, item: Any) -> GenericAlias: ... if sys.version_info >= (3, 9): - def cache(__user_function: Callable[..., _T]) -> _lru_cache_wrapper[_T]: ... + def cache(user_function: Callable[..., _T], /) -> _lru_cache_wrapper[_T]: ... def _make_key( args: tuple[Hashable, ...], diff --git a/mypy/typeshed/stdlib/gc.pyi b/mypy/typeshed/stdlib/gc.pyi index 27cee726ba09..31179add314c 100644 --- a/mypy/typeshed/stdlib/gc.pyi +++ b/mypy/typeshed/stdlib/gc.pyi @@ -1,7 +1,7 @@ import sys from collections.abc import Callable -from typing import Any -from typing_extensions import Literal, TypeAlias +from typing import Any, Literal +from typing_extensions import TypeAlias DEBUG_COLLECTABLE: Literal[2] DEBUG_LEAK: Literal[38] @@ -19,13 +19,7 @@ def disable() -> None: ... def enable() -> None: ... def get_count() -> tuple[int, int, int]: ... def get_debug() -> int: ... - -if sys.version_info >= (3, 8): - def get_objects(generation: int | None = None) -> list[Any]: ... - -else: - def get_objects() -> list[Any]: ... - +def get_objects(generation: int | None = None) -> list[Any]: ... def freeze() -> None: ... def unfreeze() -> None: ... def get_freeze_count() -> int: ... @@ -33,11 +27,11 @@ def get_referents(*objs: Any) -> list[Any]: ... def get_referrers(*objs: Any) -> list[Any]: ... def get_stats() -> list[dict[str, Any]]: ... def get_threshold() -> tuple[int, int, int]: ... -def is_tracked(__obj: Any) -> bool: ... +def is_tracked(obj: Any, /) -> bool: ... if sys.version_info >= (3, 9): - def is_finalized(__obj: Any) -> bool: ... + def is_finalized(obj: Any, /) -> bool: ... def isenabled() -> bool: ... -def set_debug(__flags: int) -> None: ... +def set_debug(flags: int, /) -> None: ... def set_threshold(threshold0: int, threshold1: int = ..., threshold2: int = ...) -> None: ... diff --git a/mypy/typeshed/stdlib/genericpath.pyi b/mypy/typeshed/stdlib/genericpath.pyi index 46426b63c852..0dd5dec4b2ec 100644 --- a/mypy/typeshed/stdlib/genericpath.pyi +++ b/mypy/typeshed/stdlib/genericpath.pyi @@ -1,8 +1,9 @@ import os -from _typeshed import BytesPath, FileDescriptorOrPath, StrPath, SupportsRichComparisonT +import sys +from _typeshed import BytesPath, FileDescriptorOrPath, StrOrBytesPath, StrPath, SupportsRichComparisonT from collections.abc import Sequence -from typing import overload -from typing_extensions import Literal, LiteralString +from typing import Literal, overload +from typing_extensions import LiteralString __all__ = [ "commonprefix", @@ -17,6 +18,8 @@ __all__ = [ "sameopenfile", "samestat", ] +if sys.version_info >= (3, 12): + __all__ += ["islink"] # All overloads can return empty string. Ideally, Literal[""] would be a valid # Iterable[T], so that list[T] | Literal[""] could be used as a return @@ -36,6 +39,9 @@ def getsize(filename: FileDescriptorOrPath) -> int: ... def isfile(path: FileDescriptorOrPath) -> bool: ... def isdir(s: FileDescriptorOrPath) -> bool: ... +if sys.version_info >= (3, 12): + def islink(path: StrOrBytesPath) -> bool: ... + # These return float if os.stat_float_times() == True, # but int is a subclass of float. def getatime(filename: FileDescriptorOrPath) -> float: ... diff --git a/mypy/typeshed/stdlib/getopt.pyi b/mypy/typeshed/stdlib/getopt.pyi index 14d63dbd6f99..bc9d4da4796b 100644 --- a/mypy/typeshed/stdlib/getopt.pyi +++ b/mypy/typeshed/stdlib/getopt.pyi @@ -1,7 +1,7 @@ __all__ = ["GetoptError", "error", "getopt", "gnu_getopt"] -def getopt(args: list[str], shortopts: str, longopts: list[str] = ...) -> tuple[list[tuple[str, str]], list[str]]: ... -def gnu_getopt(args: list[str], shortopts: str, longopts: list[str] = ...) -> tuple[list[tuple[str, str]], list[str]]: ... +def getopt(args: list[str], shortopts: str, longopts: list[str] = []) -> tuple[list[tuple[str, str]], list[str]]: ... +def gnu_getopt(args: list[str], shortopts: str, longopts: list[str] = []) -> tuple[list[tuple[str, str]], list[str]]: ... class GetoptError(Exception): msg: str diff --git a/mypy/typeshed/stdlib/gettext.pyi b/mypy/typeshed/stdlib/gettext.pyi index 5d98227ec1f4..4c17c0dc5de4 100644 --- a/mypy/typeshed/stdlib/gettext.pyi +++ b/mypy/typeshed/stdlib/gettext.pyi @@ -2,8 +2,7 @@ import io import sys from _typeshed import StrPath from collections.abc import Callable, Container, Iterable, Sequence -from typing import Any, Protocol, TypeVar, overload -from typing_extensions import Final, Literal +from typing import Any, Final, Literal, Protocol, TypeVar, overload __all__ = [ "NullTranslations", @@ -18,14 +17,15 @@ __all__ = [ "dngettext", "gettext", "ngettext", + "dnpgettext", + "dpgettext", + "npgettext", + "pgettext", ] if sys.version_info < (3, 11): __all__ += ["bind_textdomain_codeset", "ldgettext", "ldngettext", "lgettext", "lngettext"] -if sys.version_info >= (3, 8): - __all__ += ["dnpgettext", "dpgettext", "npgettext", "pgettext"] - class _TranslationsReader(Protocol): def read(self) -> bytes: ... # optional: @@ -37,10 +37,8 @@ class NullTranslations: def add_fallback(self, fallback: NullTranslations) -> None: ... def gettext(self, message: str) -> str: ... def ngettext(self, msgid1: str, msgid2: str, n: int) -> str: ... - if sys.version_info >= (3, 8): - def pgettext(self, context: str, message: str) -> str: ... - def npgettext(self, context: str, msgid1: str, msgid2: str, n: int) -> str: ... - + def pgettext(self, context: str, message: str) -> str: ... + def npgettext(self, context: str, msgid1: str, msgid2: str, n: int) -> str: ... def info(self) -> dict[str, str]: ... def charset(self) -> str | None: ... if sys.version_info < (3, 11): @@ -57,8 +55,8 @@ class GNUTranslations(NullTranslations): CONTEXT: str VERSIONS: Sequence[int] -@overload # ignores incompatible overloads -def find( # type: ignore[misc] +@overload +def find( domain: str, localedir: StrPath | None = None, languages: Iterable[str] | None = None, all: Literal[False] = False ) -> str | None: ... @overload @@ -156,12 +154,10 @@ def dgettext(domain: str, message: str) -> str: ... def dngettext(domain: str, msgid1: str, msgid2: str, n: int) -> str: ... def gettext(message: str) -> str: ... def ngettext(msgid1: str, msgid2: str, n: int) -> str: ... - -if sys.version_info >= (3, 8): - def pgettext(context: str, message: str) -> str: ... - def dpgettext(domain: str, context: str, message: str) -> str: ... - def npgettext(context: str, msgid1: str, msgid2: str, n: int) -> str: ... - def dnpgettext(domain: str, context: str, msgid1: str, msgid2: str, n: int) -> str: ... +def pgettext(context: str, message: str) -> str: ... +def dpgettext(domain: str, context: str, message: str) -> str: ... +def npgettext(context: str, msgid1: str, msgid2: str, n: int) -> str: ... +def dnpgettext(domain: str, context: str, msgid1: str, msgid2: str, n: int) -> str: ... if sys.version_info < (3, 11): def lgettext(message: str) -> str: ... diff --git a/mypy/typeshed/stdlib/grp.pyi b/mypy/typeshed/stdlib/grp.pyi index 4b66b84b6389..965ecece2a56 100644 --- a/mypy/typeshed/stdlib/grp.pyi +++ b/mypy/typeshed/stdlib/grp.pyi @@ -1,13 +1,13 @@ import sys from _typeshed import structseq -from typing import Any -from typing_extensions import Final, final +from typing import Any, Final, final if sys.platform != "win32": @final class struct_group(structseq[Any], tuple[str, str | None, int, list[str]]): if sys.version_info >= (3, 10): __match_args__: Final = ("gr_name", "gr_passwd", "gr_gid", "gr_mem") + @property def gr_name(self) -> str: ... @property diff --git a/mypy/typeshed/stdlib/gzip.pyi b/mypy/typeshed/stdlib/gzip.pyi index 6a794f381ad6..7f43795dd01f 100644 --- a/mypy/typeshed/stdlib/gzip.pyi +++ b/mypy/typeshed/stdlib/gzip.pyi @@ -1,15 +1,12 @@ import _compression import sys import zlib -from _typeshed import ReadableBuffer, StrOrBytesPath, _BufferWithLen +from _typeshed import ReadableBuffer, SizedBuffer, StrOrBytesPath from io import FileIO -from typing import Protocol, TextIO, overload -from typing_extensions import Literal, TypeAlias +from typing import Literal, Protocol, TextIO, overload +from typing_extensions import TypeAlias -if sys.version_info >= (3, 8): - __all__ = ["BadGzipFile", "GzipFile", "open", "compress", "decompress"] -else: - __all__ = ["GzipFile", "open", "compress", "decompress"] +__all__ = ["BadGzipFile", "GzipFile", "open", "compress", "decompress"] _ReadBinaryMode: TypeAlias = Literal["r", "rb"] _WriteBinaryMode: TypeAlias = Literal["a", "ab", "w", "wb", "x", "xb"] @@ -25,15 +22,15 @@ FNAME: int # actually Literal[8] # undocumented FCOMMENT: int # actually Literal[16] # undocumented class _ReadableFileobj(Protocol): - def read(self, __n: int) -> bytes: ... - def seek(self, __n: int) -> object: ... + def read(self, n: int, /) -> bytes: ... + def seek(self, n: int, /) -> object: ... # The following attributes and methods are optional: # name: str # mode: str # def fileno() -> int: ... class _WritableFileobj(Protocol): - def write(self, __b: bytes) -> object: ... + def write(self, b: bytes, /) -> object: ... def flush(self) -> object: ... # The following attributes and methods are optional: # name: str @@ -85,8 +82,7 @@ class _PaddedFile: def seek(self, off: int) -> int: ... def seekable(self) -> bool: ... -if sys.version_info >= (3, 8): - class BadGzipFile(OSError): ... +class BadGzipFile(OSError): ... class GzipFile(_compression.BaseStream): myfileobj: FileIO | None @@ -139,8 +135,10 @@ class GzipFile(_compression.BaseStream): fileobj: _ReadableFileobj | _WritableFileobj | None = None, mtime: float | None = None, ) -> None: ... - @property - def filename(self) -> str: ... + if sys.version_info < (3, 12): + @property + def filename(self) -> str: ... + @property def mtime(self) -> int | None: ... crc: int @@ -158,10 +156,5 @@ class GzipFile(_compression.BaseStream): class _GzipReader(_compression.DecompressReader): def __init__(self, fp: _ReadableFileobj) -> None: ... -if sys.version_info >= (3, 8): - def compress(data: _BufferWithLen, compresslevel: int = 9, *, mtime: float | None = None) -> bytes: ... - -else: - def compress(data: _BufferWithLen, compresslevel: int = 9) -> bytes: ... - +def compress(data: SizedBuffer, compresslevel: int = 9, *, mtime: float | None = None) -> bytes: ... def decompress(data: ReadableBuffer) -> bytes: ... diff --git a/mypy/typeshed/stdlib/hashlib.pyi b/mypy/typeshed/stdlib/hashlib.pyi index 18b1ab549764..93bd986c9d31 100644 --- a/mypy/typeshed/stdlib/hashlib.pyi +++ b/mypy/typeshed/stdlib/hashlib.pyi @@ -1,8 +1,8 @@ import sys from _typeshed import ReadableBuffer from collections.abc import Callable, Set as AbstractSet -from typing import Protocol -from typing_extensions import Self, final +from typing import Protocol, final +from typing_extensions import Self if sys.version_info >= (3, 11): __all__ = ( @@ -59,7 +59,7 @@ class _Hash: def copy(self) -> Self: ... def digest(self) -> bytes: ... def hexdigest(self) -> str: ... - def update(self, __data: ReadableBuffer) -> None: ... + def update(self, data: ReadableBuffer, /) -> None: ... if sys.version_info >= (3, 9): def new(name: str, data: ReadableBuffer = b"", *, usedforsecurity: bool = ...) -> _Hash: ... @@ -70,7 +70,7 @@ if sys.version_info >= (3, 9): def sha384(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> _Hash: ... def sha512(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> _Hash: ... -elif sys.version_info >= (3, 8): +else: def new(name: str, data: ReadableBuffer = b"") -> _Hash: ... def md5(string: ReadableBuffer = b"") -> _Hash: ... def sha1(string: ReadableBuffer = b"") -> _Hash: ... @@ -79,15 +79,6 @@ elif sys.version_info >= (3, 8): def sha384(string: ReadableBuffer = b"") -> _Hash: ... def sha512(string: ReadableBuffer = b"") -> _Hash: ... -else: - def new(name: str, data: ReadableBuffer = b"") -> _Hash: ... - def md5(__string: ReadableBuffer = ...) -> _Hash: ... - def sha1(__string: ReadableBuffer = ...) -> _Hash: ... - def sha224(__string: ReadableBuffer = ...) -> _Hash: ... - def sha256(__string: ReadableBuffer = ...) -> _Hash: ... - def sha384(__string: ReadableBuffer = ...) -> _Hash: ... - def sha512(__string: ReadableBuffer = ...) -> _Hash: ... - algorithms_guaranteed: AbstractSet[str] algorithms_available: AbstractSet[str] @@ -101,9 +92,9 @@ class _VarLenHash: name: str def __init__(self, data: ReadableBuffer = ...) -> None: ... def copy(self) -> _VarLenHash: ... - def digest(self, __length: int) -> bytes: ... - def hexdigest(self, __length: int) -> str: ... - def update(self, __data: ReadableBuffer) -> None: ... + def digest(self, length: int, /) -> bytes: ... + def hexdigest(self, length: int, /) -> str: ... + def update(self, data: ReadableBuffer, /) -> None: ... sha3_224 = _Hash sha3_256 = _Hash @@ -113,14 +104,7 @@ shake_128 = _VarLenHash shake_256 = _VarLenHash def scrypt( - password: ReadableBuffer, - *, - salt: ReadableBuffer | None = None, - n: int | None = None, - r: int | None = None, - p: int | None = None, - maxmem: int = 0, - dklen: int = 64, + password: ReadableBuffer, *, salt: ReadableBuffer, n: int, r: int, p: int, maxmem: int = 0, dklen: int = 64 ) -> bytes: ... @final class _BlakeHash(_Hash): @@ -132,7 +116,8 @@ class _BlakeHash(_Hash): if sys.version_info >= (3, 9): def __init__( self, - __data: ReadableBuffer = ..., + data: ReadableBuffer = ..., + /, *, digest_size: int = ..., key: ReadableBuffer = ..., @@ -150,7 +135,8 @@ class _BlakeHash(_Hash): else: def __init__( self, - __data: ReadableBuffer = ..., + data: ReadableBuffer = ..., + /, *, digest_size: int = ..., key: ReadableBuffer = ..., @@ -173,9 +159,9 @@ if sys.version_info >= (3, 11): def getbuffer(self) -> ReadableBuffer: ... class _FileDigestFileObj(Protocol): - def readinto(self, __buf: bytearray) -> int: ... + def readinto(self, buf: bytearray, /) -> int: ... def readable(self) -> bool: ... def file_digest( - __fileobj: _BytesIOLike | _FileDigestFileObj, __digest: str | Callable[[], _Hash], *, _bufsize: int = 262144 + fileobj: _BytesIOLike | _FileDigestFileObj, digest: str | Callable[[], _Hash], /, *, _bufsize: int = 262144 ) -> _Hash: ... diff --git a/mypy/typeshed/stdlib/heapq.pyi b/mypy/typeshed/stdlib/heapq.pyi index 61418b3704d6..7a3aa8b442a5 100644 --- a/mypy/typeshed/stdlib/heapq.pyi +++ b/mypy/typeshed/stdlib/heapq.pyi @@ -1,8 +1,7 @@ from _heapq import * from _typeshed import SupportsRichComparison from collections.abc import Callable, Iterable -from typing import Any, TypeVar -from typing_extensions import Final +from typing import Any, Final, TypeVar __all__ = ["heappush", "heappop", "heapify", "heapreplace", "merge", "nlargest", "nsmallest", "heappushpop"] @@ -15,4 +14,4 @@ def merge( ) -> Iterable[_S]: ... def nlargest(n: int, iterable: Iterable[_S], key: Callable[[_S], SupportsRichComparison] | None = None) -> list[_S]: ... def nsmallest(n: int, iterable: Iterable[_S], key: Callable[[_S], SupportsRichComparison] | None = None) -> list[_S]: ... -def _heapify_max(__x: list[Any]) -> None: ... # undocumented +def _heapify_max(heap: list[Any], /) -> None: ... # undocumented diff --git a/mypy/typeshed/stdlib/hmac.pyi b/mypy/typeshed/stdlib/hmac.pyi index b9a867f7bd61..ac1372dd1e9c 100644 --- a/mypy/typeshed/stdlib/hmac.pyi +++ b/mypy/typeshed/stdlib/hmac.pyi @@ -1,5 +1,4 @@ -import sys -from _typeshed import ReadableBuffer, _BufferWithLen +from _typeshed import ReadableBuffer, SizedBuffer from collections.abc import Callable from types import ModuleType from typing import Any, AnyStr, overload @@ -14,16 +13,12 @@ trans_36: bytes digest_size: None -if sys.version_info >= (3, 8): - # In reality digestmod has a default value, but the function always throws an error - # if the argument is not given, so we pretend it is a required argument. - @overload - def new(key: bytes | bytearray, msg: ReadableBuffer | None, digestmod: _DigestMod) -> HMAC: ... - @overload - def new(key: bytes | bytearray, *, digestmod: _DigestMod) -> HMAC: ... - -else: - def new(key: bytes | bytearray, msg: ReadableBuffer | None = None, digestmod: _DigestMod | None = None) -> HMAC: ... +# In reality digestmod has a default value, but the function always throws an error +# if the argument is not given, so we pretend it is a required argument. +@overload +def new(key: bytes | bytearray, msg: ReadableBuffer | None, digestmod: _DigestMod) -> HMAC: ... +@overload +def new(key: bytes | bytearray, *, digestmod: _DigestMod) -> HMAC: ... class HMAC: digest_size: int @@ -37,7 +32,7 @@ class HMAC: def copy(self) -> HMAC: ... @overload -def compare_digest(__a: ReadableBuffer, __b: ReadableBuffer) -> bool: ... +def compare_digest(a: ReadableBuffer, b: ReadableBuffer, /) -> bool: ... @overload -def compare_digest(__a: AnyStr, __b: AnyStr) -> bool: ... -def digest(key: _BufferWithLen, msg: ReadableBuffer, digest: _DigestMod) -> bytes: ... +def compare_digest(a: AnyStr, b: AnyStr, /) -> bool: ... +def digest(key: SizedBuffer, msg: ReadableBuffer, digest: _DigestMod) -> bytes: ... diff --git a/mypy/typeshed/stdlib/http/__init__.pyi b/mypy/typeshed/stdlib/http/__init__.pyi index d4b44f2eb99b..bb5737cc0481 100644 --- a/mypy/typeshed/stdlib/http/__init__.pyi +++ b/mypy/typeshed/stdlib/http/__init__.pyi @@ -1,6 +1,6 @@ import sys from enum import IntEnum -from typing_extensions import Literal +from typing import Literal if sys.version_info >= (3, 11): from enum import StrEnum @@ -15,81 +15,91 @@ class HTTPStatus(IntEnum): def phrase(self) -> str: ... @property def description(self) -> str: ... - CONTINUE: int - SWITCHING_PROTOCOLS: int - PROCESSING: int - OK: int - CREATED: int - ACCEPTED: int - NON_AUTHORITATIVE_INFORMATION: int - NO_CONTENT: int - RESET_CONTENT: int - PARTIAL_CONTENT: int - MULTI_STATUS: int - ALREADY_REPORTED: int - IM_USED: int - MULTIPLE_CHOICES: int - MOVED_PERMANENTLY: int - FOUND: int - SEE_OTHER: int - NOT_MODIFIED: int - USE_PROXY: int - TEMPORARY_REDIRECT: int - PERMANENT_REDIRECT: int - BAD_REQUEST: int - UNAUTHORIZED: int - PAYMENT_REQUIRED: int - FORBIDDEN: int - NOT_FOUND: int - METHOD_NOT_ALLOWED: int - NOT_ACCEPTABLE: int - PROXY_AUTHENTICATION_REQUIRED: int - REQUEST_TIMEOUT: int - CONFLICT: int - GONE: int - LENGTH_REQUIRED: int - PRECONDITION_FAILED: int - REQUEST_ENTITY_TOO_LARGE: int - REQUEST_URI_TOO_LONG: int - UNSUPPORTED_MEDIA_TYPE: int - REQUESTED_RANGE_NOT_SATISFIABLE: int - EXPECTATION_FAILED: int - UNPROCESSABLE_ENTITY: int - LOCKED: int - FAILED_DEPENDENCY: int - UPGRADE_REQUIRED: int - PRECONDITION_REQUIRED: int - TOO_MANY_REQUESTS: int - REQUEST_HEADER_FIELDS_TOO_LARGE: int - INTERNAL_SERVER_ERROR: int - NOT_IMPLEMENTED: int - BAD_GATEWAY: int - SERVICE_UNAVAILABLE: int - GATEWAY_TIMEOUT: int - HTTP_VERSION_NOT_SUPPORTED: int - VARIANT_ALSO_NEGOTIATES: int - INSUFFICIENT_STORAGE: int - LOOP_DETECTED: int - NOT_EXTENDED: int - NETWORK_AUTHENTICATION_REQUIRED: int - MISDIRECTED_REQUEST: int - if sys.version_info >= (3, 8): - UNAVAILABLE_FOR_LEGAL_REASONS: int + CONTINUE = 100 + SWITCHING_PROTOCOLS = 101 + PROCESSING = 102 + OK = 200 + CREATED = 201 + ACCEPTED = 202 + NON_AUTHORITATIVE_INFORMATION = 203 + NO_CONTENT = 204 + RESET_CONTENT = 205 + PARTIAL_CONTENT = 206 + MULTI_STATUS = 207 + ALREADY_REPORTED = 208 + IM_USED = 226 + MULTIPLE_CHOICES = 300 + MOVED_PERMANENTLY = 301 + FOUND = 302 + SEE_OTHER = 303 + NOT_MODIFIED = 304 + USE_PROXY = 305 + TEMPORARY_REDIRECT = 307 + PERMANENT_REDIRECT = 308 + BAD_REQUEST = 400 + UNAUTHORIZED = 401 + PAYMENT_REQUIRED = 402 + FORBIDDEN = 403 + NOT_FOUND = 404 + METHOD_NOT_ALLOWED = 405 + NOT_ACCEPTABLE = 406 + PROXY_AUTHENTICATION_REQUIRED = 407 + REQUEST_TIMEOUT = 408 + CONFLICT = 409 + GONE = 410 + LENGTH_REQUIRED = 411 + PRECONDITION_FAILED = 412 + REQUEST_ENTITY_TOO_LARGE = 413 + REQUEST_URI_TOO_LONG = 414 + UNSUPPORTED_MEDIA_TYPE = 415 + REQUESTED_RANGE_NOT_SATISFIABLE = 416 + EXPECTATION_FAILED = 417 + UNPROCESSABLE_ENTITY = 422 + LOCKED = 423 + FAILED_DEPENDENCY = 424 + UPGRADE_REQUIRED = 426 + PRECONDITION_REQUIRED = 428 + TOO_MANY_REQUESTS = 429 + REQUEST_HEADER_FIELDS_TOO_LARGE = 431 + INTERNAL_SERVER_ERROR = 500 + NOT_IMPLEMENTED = 501 + BAD_GATEWAY = 502 + SERVICE_UNAVAILABLE = 503 + GATEWAY_TIMEOUT = 504 + HTTP_VERSION_NOT_SUPPORTED = 505 + VARIANT_ALSO_NEGOTIATES = 506 + INSUFFICIENT_STORAGE = 507 + LOOP_DETECTED = 508 + NOT_EXTENDED = 510 + NETWORK_AUTHENTICATION_REQUIRED = 511 + MISDIRECTED_REQUEST = 421 + UNAVAILABLE_FOR_LEGAL_REASONS = 451 if sys.version_info >= (3, 9): EARLY_HINTS: Literal[103] IM_A_TEAPOT: Literal[418] TOO_EARLY: Literal[425] + if sys.version_info >= (3, 12): + @property + def is_informational(self) -> bool: ... + @property + def is_success(self) -> bool: ... + @property + def is_redirection(self) -> bool: ... + @property + def is_client_error(self) -> bool: ... + @property + def is_server_error(self) -> bool: ... if sys.version_info >= (3, 11): class HTTPMethod(StrEnum): @property def description(self) -> str: ... - CONNECT: str - DELETE: str - GET: str - HEAD: str - OPTIONS: str - PATCH: str - POST: str - PUT: str - TRACE: str + CONNECT = "CONNECT" + DELETE = "DELETE" + GET = "GET" + HEAD = "HEAD" + OPTIONS = "OPTIONS" + PATCH = "PATCH" + POST = "POST" + PUT = "PUT" + TRACE = "TRACE" diff --git a/mypy/typeshed/stdlib/http/client.pyi b/mypy/typeshed/stdlib/http/client.pyi index b1506b50e750..f68d9d0ca7d7 100644 --- a/mypy/typeshed/stdlib/http/client.pyi +++ b/mypy/typeshed/stdlib/http/client.pyi @@ -1,8 +1,9 @@ import email.message import io import ssl +import sys import types -from _typeshed import ReadableBuffer, SupportsRead, WriteableBuffer +from _typeshed import ReadableBuffer, SupportsRead, SupportsReadline, WriteableBuffer from collections.abc import Callable, Iterable, Iterator, Mapping from socket import socket from typing import Any, BinaryIO, TypeVar, overload @@ -32,6 +33,7 @@ __all__ = [ _DataType: TypeAlias = SupportsRead[bytes] | Iterable[ReadableBuffer] | ReadableBuffer _T = TypeVar("_T") +_MessageT = TypeVar("_MessageT", bound=email.message.Message) HTTP_PORT: int HTTPS_PORT: int @@ -96,12 +98,15 @@ NETWORK_AUTHENTICATION_REQUIRED: int responses: dict[int, str] -class HTTPMessage(email.message.Message): +class HTTPMessage(email.message.Message[str, str]): def getallmatchingheaders(self, name: str) -> list[str]: ... # undocumented -def parse_headers(fp: io.BufferedIOBase, _class: Callable[[], email.message.Message] = ...) -> HTTPMessage: ... +@overload +def parse_headers(fp: SupportsReadline[bytes], _class: Callable[[], _MessageT]) -> _MessageT: ... +@overload +def parse_headers(fp: SupportsReadline[bytes]) -> HTTPMessage: ... -class HTTPResponse(io.BufferedIOBase, BinaryIO): +class HTTPResponse(io.BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible method definitions in the base classes msg: HTTPMessage headers: HTTPMessage version: int @@ -114,6 +119,10 @@ class HTTPResponse(io.BufferedIOBase, BinaryIO): chunk_left: int | None length: int | None will_close: bool + # url is set on instances of the class in urllib.request.AbstractHTTPHandler.do_open + # to match urllib.response.addinfourl's interface. + # It's not set in HTTPResponse.__init__ or any other method on the class + url: str def __init__(self, sock: socket, debuglevel: int = 0, method: str | None = None, url: str | None = None) -> None: ... def peek(self, n: int = -1) -> bytes: ... def read(self, amt: int | None = None) -> bytes: ... @@ -158,36 +167,51 @@ class HTTPConnection: method: str, url: str, body: _DataType | str | None = None, - headers: Mapping[str, str] = ..., + headers: Mapping[str, str] = {}, *, encode_chunked: bool = False, ) -> None: ... def getresponse(self) -> HTTPResponse: ... def set_debuglevel(self, level: int) -> None: ... + if sys.version_info >= (3, 12): + def get_proxy_response_headers(self) -> HTTPMessage | None: ... + def set_tunnel(self, host: str, port: int | None = None, headers: Mapping[str, str] | None = None) -> None: ... def connect(self) -> None: ... def close(self) -> None: ... def putrequest(self, method: str, url: str, skip_host: bool = False, skip_accept_encoding: bool = False) -> None: ... - def putheader(self, header: str, *argument: str) -> None: ... + def putheader(self, header: str | bytes, *argument: str | bytes) -> None: ... def endheaders(self, message_body: _DataType | None = None, *, encode_chunked: bool = False) -> None: ... def send(self, data: _DataType | str) -> None: ... class HTTPSConnection(HTTPConnection): # Can be `None` if `.connect()` was not called: - sock: ssl.SSLSocket | Any # type: ignore[override] - def __init__( - self, - host: str, - port: int | None = None, - key_file: str | None = None, - cert_file: str | None = None, - timeout: float | None = ..., - source_address: tuple[str, int] | None = None, - *, - context: ssl.SSLContext | None = None, - check_hostname: bool | None = None, - blocksize: int = 8192, - ) -> None: ... + sock: ssl.SSLSocket | Any + if sys.version_info >= (3, 12): + def __init__( + self, + host: str, + port: int | None = None, + *, + timeout: float | None = ..., + source_address: tuple[str, int] | None = None, + context: ssl.SSLContext | None = None, + blocksize: int = 8192, + ) -> None: ... + else: + def __init__( + self, + host: str, + port: int | None = None, + key_file: str | None = None, + cert_file: str | None = None, + timeout: float | None = ..., + source_address: tuple[str, int] | None = None, + *, + context: ssl.SSLContext | None = None, + check_hostname: bool | None = None, + blocksize: int = 8192, + ) -> None: ... class HTTPException(Exception): ... diff --git a/mypy/typeshed/stdlib/http/cookiejar.pyi b/mypy/typeshed/stdlib/http/cookiejar.pyi index 7f2c9c6cc8f4..faac20d13125 100644 --- a/mypy/typeshed/stdlib/http/cookiejar.pyi +++ b/mypy/typeshed/stdlib/http/cookiejar.pyi @@ -44,13 +44,7 @@ class CookieJar(Iterable[Cookie]): class FileCookieJar(CookieJar): filename: str delayload: bool - if sys.version_info >= (3, 8): - def __init__( - self, filename: StrPath | None = None, delayload: bool = False, policy: CookiePolicy | None = None - ) -> None: ... - else: - def __init__(self, filename: str | None = None, delayload: bool = False, policy: CookiePolicy | None = None) -> None: ... - + def __init__(self, filename: StrPath | None = None, delayload: bool = False, policy: CookiePolicy | None = None) -> None: ... def save(self, filename: str | None = None, ignore_discard: bool = False, ignore_expires: bool = False) -> None: ... def load(self, filename: str | None = None, ignore_discard: bool = False, ignore_expires: bool = False) -> None: ... def revert(self, filename: str | None = None, ignore_discard: bool = False, ignore_expires: bool = False) -> None: ... @@ -84,40 +78,22 @@ class DefaultCookiePolicy(CookiePolicy): DomainRFC2965Match: ClassVar[int] DomainLiberal: ClassVar[int] DomainStrict: ClassVar[int] - if sys.version_info >= (3, 8): - def __init__( - self, - blocked_domains: Sequence[str] | None = None, - allowed_domains: Sequence[str] | None = None, - netscape: bool = True, - rfc2965: bool = False, - rfc2109_as_netscape: bool | None = None, - hide_cookie2: bool = False, - strict_domain: bool = False, - strict_rfc2965_unverifiable: bool = True, - strict_ns_unverifiable: bool = False, - strict_ns_domain: int = 0, - strict_ns_set_initial_dollar: bool = False, - strict_ns_set_path: bool = False, - secure_protocols: Sequence[str] = ..., - ) -> None: ... - else: - def __init__( - self, - blocked_domains: Sequence[str] | None = None, - allowed_domains: Sequence[str] | None = None, - netscape: bool = True, - rfc2965: bool = False, - rfc2109_as_netscape: bool | None = None, - hide_cookie2: bool = False, - strict_domain: bool = False, - strict_rfc2965_unverifiable: bool = True, - strict_ns_unverifiable: bool = False, - strict_ns_domain: int = 0, - strict_ns_set_initial_dollar: bool = False, - strict_ns_set_path: bool = False, - ) -> None: ... - + def __init__( + self, + blocked_domains: Sequence[str] | None = None, + allowed_domains: Sequence[str] | None = None, + netscape: bool = True, + rfc2965: bool = False, + rfc2109_as_netscape: bool | None = None, + hide_cookie2: bool = False, + strict_domain: bool = False, + strict_rfc2965_unverifiable: bool = True, + strict_ns_unverifiable: bool = False, + strict_ns_domain: int = 0, + strict_ns_set_initial_dollar: bool = False, + strict_ns_set_path: bool = False, + secure_protocols: Sequence[str] = ("https", "wss"), + ) -> None: ... def blocked_domains(self) -> tuple[str, ...]: ... def set_blocked_domains(self, blocked_domains: Sequence[str]) -> None: ... def is_blocked(self, domain: str) -> bool: ... diff --git a/mypy/typeshed/stdlib/http/cookies.pyi b/mypy/typeshed/stdlib/http/cookies.pyi index e24ef9cbdd2e..3d19bb108c2d 100644 --- a/mypy/typeshed/stdlib/http/cookies.pyi +++ b/mypy/typeshed/stdlib/http/cookies.pyi @@ -49,12 +49,12 @@ class Morsel(dict[str, Any], Generic[_T]): class BaseCookie(dict[str, Morsel[_T]], Generic[_T]): def __init__(self, input: _DataType | None = None) -> None: ... - def value_decode(self, val: str) -> _T: ... - def value_encode(self, val: _T) -> str: ... + def value_decode(self, val: str) -> tuple[_T, str]: ... + def value_encode(self, val: _T) -> tuple[_T, str]: ... def output(self, attrs: list[str] | None = None, header: str = "Set-Cookie:", sep: str = "\r\n") -> str: ... __str__ = output def js_output(self, attrs: list[str] | None = None) -> str: ... def load(self, rawdata: _DataType) -> None: ... def __setitem__(self, key: str, value: str | Morsel[_T]) -> None: ... -class SimpleCookie(BaseCookie[_T], Generic[_T]): ... +class SimpleCookie(BaseCookie[str]): ... diff --git a/mypy/typeshed/stdlib/http/server.pyi b/mypy/typeshed/stdlib/http/server.pyi index c9700f70e791..07cde553c1df 100644 --- a/mypy/typeshed/stdlib/http/server.pyi +++ b/mypy/typeshed/stdlib/http/server.pyi @@ -45,7 +45,7 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): def log_error(self, format: str, *args: Any) -> None: ... def log_message(self, format: str, *args: Any) -> None: ... def version_string(self) -> str: ... - def date_time_string(self, timestamp: int | None = None) -> str: ... + def date_time_string(self, timestamp: float | None = None) -> str: ... def log_date_time_string(self) -> str: ... def address_string(self) -> str: ... def parse_request(self) -> bool: ... # undocumented @@ -54,6 +54,7 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): extensions_map: dict[str, str] if sys.version_info >= (3, 12): index_pages: ClassVar[tuple[str, ...]] + directory: str def __init__( self, request: socketserver._RequestType, diff --git a/mypy/typeshed/stdlib/imaplib.pyi b/mypy/typeshed/stdlib/imaplib.pyi index 1c2112dd37c8..6a4d8b2e720a 100644 --- a/mypy/typeshed/stdlib/imaplib.pyi +++ b/mypy/typeshed/stdlib/imaplib.pyi @@ -1,7 +1,7 @@ import subprocess import sys import time -from _typeshed import ReadableBuffer, _BufferWithLen +from _typeshed import ReadableBuffer, SizedBuffer from builtins import list as _list # conflicts with a method named "list" from collections.abc import Callable from datetime import datetime @@ -9,8 +9,8 @@ from re import Pattern from socket import socket as _socket from ssl import SSLContext, SSLSocket from types import TracebackType -from typing import IO, Any, SupportsAbs, SupportsInt -from typing_extensions import Literal, Self, TypeAlias +from typing import IO, Any, Literal, SupportsAbs, SupportsInt +from typing_extensions import Self, TypeAlias __all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple", "Int2AP", "ParseFlags", "Time2Internaldate", "IMAP4_SSL"] @@ -108,9 +108,14 @@ class IMAP4: def print_log(self) -> None: ... class IMAP4_SSL(IMAP4): - keyfile: str - certfile: str - if sys.version_info >= (3, 9): + if sys.version_info < (3, 12): + keyfile: str + certfile: str + if sys.version_info >= (3, 12): + def __init__( + self, host: str = "", port: int = 993, *, ssl_context: SSLContext | None = None, timeout: float | None = None + ) -> None: ... + elif sys.version_info >= (3, 9): def __init__( self, host: str = "", @@ -155,7 +160,7 @@ class _Authenticator: def __init__(self, mechinst: Callable[[bytes], bytes | bytearray | memoryview | str | None]) -> None: ... def process(self, data: str) -> str: ... def encode(self, inp: bytes | bytearray | memoryview) -> str: ... - def decode(self, inp: str | _BufferWithLen) -> bytes: ... + def decode(self, inp: str | SizedBuffer) -> bytes: ... def Internaldate2tuple(resp: ReadableBuffer) -> time.struct_time | None: ... def Int2AP(num: SupportsAbs[SupportsInt]) -> bytes: ... diff --git a/mypy/typeshed/stdlib/imghdr.pyi b/mypy/typeshed/stdlib/imghdr.pyi index ed3647f20fc5..6e1b858b8f32 100644 --- a/mypy/typeshed/stdlib/imghdr.pyi +++ b/mypy/typeshed/stdlib/imghdr.pyi @@ -6,8 +6,8 @@ __all__ = ["what"] class _ReadableBinary(Protocol): def tell(self) -> int: ... - def read(self, size: int) -> bytes: ... - def seek(self, offset: int) -> Any: ... + def read(self, size: int, /) -> bytes: ... + def seek(self, offset: int, /) -> Any: ... @overload def what(file: StrPath | _ReadableBinary, h: None = None) -> str | None: ... diff --git a/mypy/typeshed/stdlib/imp.pyi b/mypy/typeshed/stdlib/imp.pyi index 3f2920de9c2b..ee5a0cd7bc72 100644 --- a/mypy/typeshed/stdlib/imp.pyi +++ b/mypy/typeshed/stdlib/imp.pyi @@ -45,7 +45,7 @@ class _FileLike(Protocol): def read(self) -> str | bytes: ... def close(self) -> Any: ... def __enter__(self) -> Any: ... - def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> Any: ... + def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, /) -> Any: ... # PathLike doesn't work for the pathname argument here def load_source(name: str, pathname: str, file: _FileLike | None = None) -> types.ModuleType: ... diff --git a/mypy/typeshed/stdlib/importlib/__init__.pyi b/mypy/typeshed/stdlib/importlib/__init__.pyi index 1747b274136e..8506efc01171 100644 --- a/mypy/typeshed/stdlib/importlib/__init__.pyi +++ b/mypy/typeshed/stdlib/importlib/__init__.pyi @@ -1,3 +1,4 @@ +import sys from collections.abc import Mapping, Sequence from importlib.abc import Loader from types import ModuleType @@ -9,12 +10,15 @@ def __import__( name: str, globals: Mapping[str, object] | None = None, locals: Mapping[str, object] | None = None, - fromlist: Sequence[str] = ..., + fromlist: Sequence[str] = (), level: int = 0, ) -> ModuleType: ... # `importlib.import_module` return type should be kept the same as `builtins.__import__` def import_module(name: str, package: str | None = None) -> ModuleType: ... -def find_loader(name: str, path: str | None = None) -> Loader | None: ... + +if sys.version_info < (3, 12): + def find_loader(name: str, path: str | None = None) -> Loader | None: ... + def invalidate_caches() -> None: ... def reload(module: ModuleType) -> ModuleType: ... diff --git a/mypy/typeshed/stdlib/importlib/_abc.pyi b/mypy/typeshed/stdlib/importlib/_abc.pyi new file mode 100644 index 000000000000..1a21b9a72cd8 --- /dev/null +++ b/mypy/typeshed/stdlib/importlib/_abc.pyi @@ -0,0 +1,15 @@ +import sys +import types +from abc import ABCMeta +from importlib.machinery import ModuleSpec + +if sys.version_info >= (3, 10): + class Loader(metaclass=ABCMeta): + def load_module(self, fullname: str) -> types.ModuleType: ... + if sys.version_info < (3, 12): + def module_repr(self, module: types.ModuleType) -> str: ... + + def create_module(self, spec: ModuleSpec) -> types.ModuleType | None: ... + # Not defined on the actual class for backwards-compatibility reasons, + # but expected in new code. + def exec_module(self, module: types.ModuleType) -> None: ... diff --git a/mypy/typeshed/stdlib/importlib/abc.pyi b/mypy/typeshed/stdlib/importlib/abc.pyi index 3d0c2d38c4e9..3937481159dc 100644 --- a/mypy/typeshed/stdlib/importlib/abc.pyi +++ b/mypy/typeshed/stdlib/importlib/abc.pyi @@ -1,24 +1,16 @@ +import _ast import sys import types -from _typeshed import ( - OpenBinaryMode, - OpenBinaryModeReading, - OpenBinaryModeUpdating, - OpenBinaryModeWriting, - OpenTextMode, - ReadableBuffer, -) +from _typeshed import ReadableBuffer, StrPath from abc import ABCMeta, abstractmethod from collections.abc import Iterator, Mapping, Sequence from importlib.machinery import ModuleSpec -from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper -from typing import IO, Any, BinaryIO, NoReturn, Protocol, overload, runtime_checkable -from typing_extensions import Literal +from io import BufferedReader +from typing import IO, Any, Literal, Protocol, overload, runtime_checkable if sys.version_info >= (3, 11): __all__ = [ "Loader", - "Finder", "MetaPathFinder", "PathEntryFinder", "ResourceLoader", @@ -26,20 +18,24 @@ if sys.version_info >= (3, 11): "ExecutionLoader", "FileLoader", "SourceLoader", - "ResourceReader", - "Traversable", - "TraversableResources", ] -class Finder(metaclass=ABCMeta): ... + if sys.version_info < (3, 12): + __all__ += ["Finder", "ResourceReader", "Traversable", "TraversableResources"] -class Loader(metaclass=ABCMeta): - def load_module(self, fullname: str) -> types.ModuleType: ... - def module_repr(self, module: types.ModuleType) -> str: ... - def create_module(self, spec: ModuleSpec) -> types.ModuleType | None: ... - # Not defined on the actual class for backwards-compatibility reasons, - # but expected in new code. - def exec_module(self, module: types.ModuleType) -> None: ... +if sys.version_info >= (3, 10): + from importlib._abc import Loader as Loader +else: + class Loader(metaclass=ABCMeta): + def load_module(self, fullname: str) -> types.ModuleType: ... + def module_repr(self, module: types.ModuleType) -> str: ... + def create_module(self, spec: ModuleSpec) -> types.ModuleType | None: ... + # Not defined on the actual class for backwards-compatibility reasons, + # but expected in new code. + def exec_module(self, module: types.ModuleType) -> None: ... + +if sys.version_info < (3, 12): + class Finder(metaclass=ABCMeta): ... class ResourceLoader(Loader): @abstractmethod @@ -52,7 +48,9 @@ class InspectLoader(Loader): def get_source(self, fullname: str) -> str | None: ... def exec_module(self, module: types.ModuleType) -> None: ... @staticmethod - def source_to_code(data: ReadableBuffer | str, path: str = "") -> types.CodeType: ... + def source_to_code( + data: ReadableBuffer | str | _ast.Module | _ast.Expression | _ast.Interactive, path: ReadableBuffer | StrPath = "" + ) -> types.CodeType: ... class ExecutionLoader(InspectLoader): @abstractmethod @@ -64,21 +62,44 @@ class SourceLoader(ResourceLoader, ExecutionLoader, metaclass=ABCMeta): def get_source(self, fullname: str) -> str | None: ... def path_stats(self, path: str) -> Mapping[str, Any]: ... -# Please keep in sync with sys._MetaPathFinder -class MetaPathFinder(Finder): - def find_module(self, fullname: str, path: Sequence[str] | None) -> Loader | None: ... - def invalidate_caches(self) -> None: ... - # Not defined on the actual class, but expected to exist. - def find_spec( - self, fullname: str, path: Sequence[str] | None, target: types.ModuleType | None = ... - ) -> ModuleSpec | None: ... - -class PathEntryFinder(Finder): - def find_module(self, fullname: str) -> Loader | None: ... - def find_loader(self, fullname: str) -> tuple[Loader | None, Sequence[str]]: ... - def invalidate_caches(self) -> None: ... - # Not defined on the actual class, but expected to exist. - def find_spec(self, fullname: str, target: types.ModuleType | None = ...) -> ModuleSpec | None: ... +# The base classes differ starting in 3.10: +if sys.version_info >= (3, 10): + # Please keep in sync with _typeshed.importlib.MetaPathFinderProtocol + class MetaPathFinder(metaclass=ABCMeta): + if sys.version_info < (3, 12): + def find_module(self, fullname: str, path: Sequence[str] | None) -> Loader | None: ... + + def invalidate_caches(self) -> None: ... + # Not defined on the actual class, but expected to exist. + def find_spec( + self, fullname: str, path: Sequence[str] | None, target: types.ModuleType | None = ..., / + ) -> ModuleSpec | None: ... + + class PathEntryFinder(metaclass=ABCMeta): + if sys.version_info < (3, 12): + def find_module(self, fullname: str) -> Loader | None: ... + def find_loader(self, fullname: str) -> tuple[Loader | None, Sequence[str]]: ... + + def invalidate_caches(self) -> None: ... + # Not defined on the actual class, but expected to exist. + def find_spec(self, fullname: str, target: types.ModuleType | None = ...) -> ModuleSpec | None: ... + +else: + # Please keep in sync with _typeshed.importlib.MetaPathFinderProtocol + class MetaPathFinder(Finder): + def find_module(self, fullname: str, path: Sequence[str] | None) -> Loader | None: ... + def invalidate_caches(self) -> None: ... + # Not defined on the actual class, but expected to exist. + def find_spec( + self, fullname: str, path: Sequence[str] | None, target: types.ModuleType | None = ..., / + ) -> ModuleSpec | None: ... + + class PathEntryFinder(Finder): + def find_module(self, fullname: str) -> Loader | None: ... + def find_loader(self, fullname: str) -> tuple[Loader | None, Sequence[str]]: ... + def invalidate_caches(self) -> None: ... + # Not defined on the actual class, but expected to exist. + def find_spec(self, fullname: str, target: types.ModuleType | None = ...) -> ModuleSpec | None: ... class FileLoader(ResourceLoader, ExecutionLoader, metaclass=ABCMeta): name: str @@ -117,72 +138,26 @@ if sys.version_info >= (3, 9): def joinpath(self, *descendants: str) -> Traversable: ... else: @abstractmethod - def joinpath(self, child: str) -> Traversable: ... - # The .open method comes from pathlib.pyi and should be kept in sync. - @overload - @abstractmethod - def open( - self, - mode: OpenTextMode = "r", - buffering: int = ..., - encoding: str | None = ..., - errors: str | None = ..., - newline: str | None = ..., - ) -> TextIOWrapper: ... - # Unbuffered binary mode: returns a FileIO - @overload - @abstractmethod - def open( - self, mode: OpenBinaryMode, buffering: Literal[0], encoding: None = None, errors: None = None, newline: None = None - ) -> FileIO: ... - # Buffering is on: return BufferedRandom, BufferedReader, or BufferedWriter - @overload - @abstractmethod - def open( - self, - mode: OpenBinaryModeUpdating, - buffering: Literal[-1, 1] = ..., - encoding: None = None, - errors: None = None, - newline: None = None, - ) -> BufferedRandom: ... - @overload - @abstractmethod - def open( - self, - mode: OpenBinaryModeWriting, - buffering: Literal[-1, 1] = ..., - encoding: None = None, - errors: None = None, - newline: None = None, - ) -> BufferedWriter: ... - @overload - @abstractmethod - def open( - self, - mode: OpenBinaryModeReading, - buffering: Literal[-1, 1] = ..., - encoding: None = None, - errors: None = None, - newline: None = None, - ) -> BufferedReader: ... - # Buffering cannot be determined: fall back to BinaryIO + def joinpath(self, child: str, /) -> Traversable: ... + + # The documentation and runtime protocol allows *args, **kwargs arguments, + # but this would mean that all implementers would have to support them, + # which is not the case. @overload @abstractmethod - def open( - self, mode: OpenBinaryMode, buffering: int = ..., encoding: None = None, errors: None = None, newline: None = None - ) -> BinaryIO: ... - # Fallback if mode is not specified + def open(self, mode: Literal["r"] = "r", /, *, encoding: str | None = None, errors: str | None = None) -> IO[str]: ... @overload @abstractmethod - def open( - self, mode: str, buffering: int = ..., encoding: str | None = ..., errors: str | None = ..., newline: str | None = ... - ) -> IO[Any]: ... + def open(self, mode: Literal["rb"], /) -> IO[bytes]: ... @property @abstractmethod def name(self) -> str: ... - @abstractmethod - def __truediv__(self, child: str) -> Traversable: ... + if sys.version_info >= (3, 10): + def __truediv__(self, child: str, /) -> Traversable: ... + else: + @abstractmethod + def __truediv__(self, child: str, /) -> Traversable: ... + @abstractmethod def read_bytes(self) -> bytes: ... @abstractmethod @@ -191,7 +166,7 @@ if sys.version_info >= (3, 9): class TraversableResources(ResourceReader): @abstractmethod def files(self) -> Traversable: ... - def open_resource(self, resource: str) -> BufferedReader: ... # type: ignore[override] - def resource_path(self, resource: Any) -> NoReturn: ... + def open_resource(self, resource: str) -> BufferedReader: ... + def resource_path(self, resource: Any) -> str: ... def is_resource(self, path: str) -> bool: ... def contents(self) -> Iterator[str]: ... diff --git a/mypy/typeshed/stdlib/importlib/machinery.pyi b/mypy/typeshed/stdlib/importlib/machinery.pyi index 5aaefce87e3a..586c2b80ab7b 100644 --- a/mypy/typeshed/stdlib/importlib/machinery.pyi +++ b/mypy/typeshed/stdlib/importlib/machinery.pyi @@ -2,11 +2,10 @@ import importlib.abc import sys import types from _typeshed import ReadableBuffer -from collections.abc import Callable, Iterable, Sequence -from typing import Any - -if sys.version_info >= (3, 8): - from importlib.metadata import DistributionFinder, PathDistribution +from collections.abc import Callable, Iterable, MutableSequence, Sequence +from importlib.metadata import DistributionFinder, PathDistribution +from typing import Any, Literal +from typing_extensions import deprecated class ModuleSpec: def __init__( @@ -31,8 +30,10 @@ class ModuleSpec: class BuiltinImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader): # MetaPathFinder - @classmethod - def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None: ... + if sys.version_info < (3, 12): + @classmethod + def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None: ... + @classmethod def find_spec( cls, fullname: str, path: Sequence[str] | None = None, target: types.ModuleType | None = None @@ -47,8 +48,9 @@ class BuiltinImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader) @classmethod def get_source(cls, fullname: str) -> None: ... # Loader - @staticmethod - def module_repr(module: types.ModuleType) -> str: ... + if sys.version_info < (3, 12): + @staticmethod + def module_repr(module: types.ModuleType) -> str: ... if sys.version_info >= (3, 10): @staticmethod def create_module(spec: ModuleSpec) -> types.ModuleType | None: ... @@ -62,8 +64,10 @@ class BuiltinImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader) class FrozenImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader): # MetaPathFinder - @classmethod - def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None: ... + if sys.version_info < (3, 12): + @classmethod + def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None: ... + @classmethod def find_spec( cls, fullname: str, path: Sequence[str] | None = None, target: types.ModuleType | None = None @@ -78,8 +82,9 @@ class FrozenImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader): @classmethod def get_source(cls, fullname: str) -> None: ... # Loader - @staticmethod - def module_repr(m: types.ModuleType) -> str: ... + if sys.version_info < (3, 12): + @staticmethod + def module_repr(m: types.ModuleType) -> str: ... if sys.version_info >= (3, 10): @staticmethod def create_module(spec: ModuleSpec) -> types.ModuleType | None: ... @@ -91,8 +96,10 @@ class FrozenImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader): def exec_module(module: types.ModuleType) -> None: ... class WindowsRegistryFinder(importlib.abc.MetaPathFinder): - @classmethod - def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None: ... + if sys.version_info < (3, 12): + @classmethod + def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None: ... + @classmethod def find_spec( cls, fullname: str, path: Sequence[str] | None = None, target: types.ModuleType | None = None @@ -108,7 +115,7 @@ class PathFinder: if sys.version_info >= (3, 10): @staticmethod def find_distributions(context: DistributionFinder.Context = ...) -> Iterable[PathDistribution]: ... - elif sys.version_info >= (3, 8): + else: @classmethod def find_distributions(cls, context: DistributionFinder.Context = ...) -> Iterable[PathDistribution]: ... @@ -116,8 +123,9 @@ class PathFinder: def find_spec( cls, fullname: str, path: Sequence[str] | None = None, target: types.ModuleType | None = None ) -> ModuleSpec | None: ... - @classmethod - def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None: ... + if sys.version_info < (3, 12): + @classmethod + def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None: ... SOURCE_SUFFIXES: list[str] DEBUG_BYTECODE_SUFFIXES: list[str] @@ -148,3 +156,24 @@ class ExtensionFileLoader(importlib.abc.ExecutionLoader): def exec_module(self, module: types.ModuleType) -> None: ... def get_code(self, fullname: str) -> None: ... def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + +if sys.version_info >= (3, 11): + import importlib.readers + + class NamespaceLoader(importlib.abc.InspectLoader): + def __init__( + self, name: str, path: MutableSequence[str], path_finder: Callable[[str, tuple[str, ...]], ModuleSpec] + ) -> None: ... + def is_package(self, fullname: str) -> Literal[True]: ... + def get_source(self, fullname: str) -> Literal[""]: ... + def get_code(self, fullname: str) -> types.CodeType: ... + def create_module(self, spec: ModuleSpec) -> None: ... + def exec_module(self, module: types.ModuleType) -> None: ... + @deprecated("load_module() is deprecated; use exec_module() instead") + def load_module(self, fullname: str) -> types.ModuleType: ... + def get_resource_reader(self, module: types.ModuleType) -> importlib.readers.NamespaceReader: ... + if sys.version_info < (3, 12): + @staticmethod + @deprecated("module_repr() is deprecated, and has been removed in Python 3.12") + def module_repr(module: types.ModuleType) -> str: ... diff --git a/mypy/typeshed/stdlib/importlib/metadata/__init__.pyi b/mypy/typeshed/stdlib/importlib/metadata/__init__.pyi index 083453cd3c9a..b2fe14777056 100644 --- a/mypy/typeshed/stdlib/importlib/metadata/__init__.pyi +++ b/mypy/typeshed/stdlib/importlib/metadata/__init__.pyi @@ -1,15 +1,20 @@ import abc import pathlib import sys +from _collections_abc import dict_keys, dict_values from _typeshed import StrPath -from collections.abc import Iterable, Mapping +from collections.abc import Iterable, Iterator, Mapping from email.message import Message from importlib.abc import MetaPathFinder from os import PathLike from pathlib import Path from re import Pattern -from typing import Any, ClassVar, NamedTuple, overload -from typing_extensions import Self +from typing import Any, ClassVar, Generic, NamedTuple, TypeVar, overload +from typing_extensions import Self, TypeAlias + +_T = TypeVar("_T") +_KT = TypeVar("_KT") +_VT = TypeVar("_VT") __all__ = [ "Distribution", @@ -28,21 +33,39 @@ if sys.version_info >= (3, 10): __all__ += ["PackageMetadata", "packages_distributions"] if sys.version_info >= (3, 10): - from importlib.metadata._meta import PackageMetadata as PackageMetadata + from importlib.metadata._meta import PackageMetadata as PackageMetadata, SimplePath def packages_distributions() -> Mapping[str, list[str]]: ... + if sys.version_info >= (3, 12): + # It's generic but shouldn't be + _SimplePath: TypeAlias = SimplePath[Any] + else: + _SimplePath: TypeAlias = SimplePath +else: + _SimplePath: TypeAlias = Path + class PackageNotFoundError(ModuleNotFoundError): @property def name(self) -> str: ... # type: ignore[override] -class _EntryPointBase(NamedTuple): - name: str - value: str - group: str +if sys.version_info >= (3, 11): + class DeprecatedTuple: + def __getitem__(self, item: int) -> str: ... + + _EntryPointBase = DeprecatedTuple +else: + class _EntryPointBase(NamedTuple): + name: str + value: str + group: str class EntryPoint(_EntryPointBase): pattern: ClassVar[Pattern[str]] if sys.version_info >= (3, 11): + name: str + value: str + group: str + def __init__(self, name: str, value: str, group: str) -> None: ... def load(self) -> Any: ... # Callable[[], Any] or an importable module @@ -66,8 +89,35 @@ class EntryPoint(_EntryPointBase): extras: list[str] = ..., ) -> bool: ... # undocumented -if sys.version_info >= (3, 10): - class EntryPoints(list[EntryPoint]): # use as list is deprecated since 3.10 + def __hash__(self) -> int: ... + def __eq__(self, other: object) -> bool: ... + if sys.version_info >= (3, 11): + def __lt__(self, other: object) -> bool: ... + if sys.version_info < (3, 12): + def __iter__(self) -> Iterator[Any]: ... # result of iter((str, Self)), really + +if sys.version_info >= (3, 12): + class EntryPoints(tuple[EntryPoint, ...]): + def __getitem__(self, name: str) -> EntryPoint: ... # type: ignore[override] + def select( + self, + *, + name: str = ..., + value: str = ..., + group: str = ..., + module: str = ..., + attr: str = ..., + extras: list[str] = ..., + ) -> EntryPoints: ... + @property + def names(self) -> set[str]: ... + @property + def groups(self) -> set[str]: ... + +elif sys.version_info >= (3, 10): + class DeprecatedList(list[_T]): ... + + class EntryPoints(DeprecatedList[EntryPoint]): # use as list is deprecated since 3.10 # int argument is deprecated since 3.10 def __getitem__(self, name: int | str) -> EntryPoint: ... # type: ignore[override] def select( @@ -85,7 +135,19 @@ if sys.version_info >= (3, 10): @property def groups(self) -> set[str]: ... - class SelectableGroups(dict[str, EntryPoints]): # use as dict is deprecated since 3.10 +if sys.version_info >= (3, 10) and sys.version_info < (3, 12): + class Deprecated(Generic[_KT, _VT]): + def __getitem__(self, name: _KT) -> _VT: ... + @overload + def get(self, name: _KT) -> _VT | None: ... + @overload + def get(self, name: _KT, default: _T) -> _VT | _T: ... + def __iter__(self) -> Iterator[_KT]: ... + def __contains__(self, *args: object) -> bool: ... + def keys(self) -> dict_keys[_KT, _VT]: ... + def values(self) -> dict_values[_KT, _VT]: ... + + class SelectableGroups(Deprecated[str, EntryPoints], dict[str, EntryPoints]): # use as dict is deprecated since 3.10 @classmethod def load(cls, eps: Iterable[EntryPoint]) -> Self: ... @property @@ -120,11 +182,17 @@ class FileHash: value: str def __init__(self, spec: str) -> None: ... -class Distribution: +if sys.version_info >= (3, 12): + class DeprecatedNonAbstract: ... + _distribution_parent = DeprecatedNonAbstract +else: + _distribution_parent = object + +class Distribution(_distribution_parent): @abc.abstractmethod def read_text(self, filename: str) -> str | None: ... @abc.abstractmethod - def locate_file(self, path: StrPath) -> PathLike[str]: ... + def locate_file(self, path: StrPath) -> _SimplePath: ... @classmethod def from_name(cls, name: str) -> Distribution: ... @overload @@ -173,13 +241,14 @@ class MetadataPathFinder(DistributionFinder): @classmethod def find_distributions(cls, context: DistributionFinder.Context = ...) -> Iterable[PathDistribution]: ... if sys.version_info >= (3, 10): - # Yes, this is an instance method that has argumend named "cls" + # Yes, this is an instance method that has a parameter named "cls" def invalidate_caches(cls) -> None: ... class PathDistribution(Distribution): - def __init__(self, path: Path) -> None: ... - def read_text(self, filename: StrPath) -> str: ... - def locate_file(self, path: StrPath) -> PathLike[str]: ... + _path: _SimplePath + def __init__(self, path: _SimplePath) -> None: ... + def read_text(self, filename: StrPath) -> str | None: ... + def locate_file(self, path: StrPath) -> _SimplePath: ... def distribution(distribution_name: str) -> Distribution: ... @overload @@ -191,15 +260,24 @@ def distributions( if sys.version_info >= (3, 10): def metadata(distribution_name: str) -> PackageMetadata: ... + +else: + def metadata(distribution_name: str) -> Message: ... + +if sys.version_info >= (3, 12): + def entry_points( + *, name: str = ..., value: str = ..., group: str = ..., module: str = ..., attr: str = ..., extras: list[str] = ... + ) -> EntryPoints: ... + +elif sys.version_info >= (3, 10): @overload - def entry_points() -> SelectableGroups: ... # type: ignore[misc] + def entry_points() -> SelectableGroups: ... # type: ignore[overload-overlap] @overload def entry_points( *, name: str = ..., value: str = ..., group: str = ..., module: str = ..., attr: str = ..., extras: list[str] = ... ) -> EntryPoints: ... else: - def metadata(distribution_name: str) -> Message: ... def entry_points() -> dict[str, list[EntryPoint]]: ... def version(distribution_name: str) -> str: ... diff --git a/mypy/typeshed/stdlib/importlib/metadata/_meta.pyi b/mypy/typeshed/stdlib/importlib/metadata/_meta.pyi index e3504fe4036a..3eac226b7065 100644 --- a/mypy/typeshed/stdlib/importlib/metadata/_meta.pyi +++ b/mypy/typeshed/stdlib/importlib/metadata/_meta.pyi @@ -1,22 +1,49 @@ +import sys from collections.abc import Iterator -from typing import Any, Protocol, TypeVar +from typing import Any, Protocol, TypeVar, overload _T = TypeVar("_T") +_T_co = TypeVar("_T_co", covariant=True) class PackageMetadata(Protocol): def __len__(self) -> int: ... def __contains__(self, item: str) -> bool: ... def __getitem__(self, key: str) -> str: ... def __iter__(self) -> Iterator[str]: ... - def get_all(self, name: str, failobj: _T = ...) -> list[Any] | _T: ... @property def json(self) -> dict[str, str | list[str]]: ... + @overload + def get_all(self, name: str, failobj: None = None) -> list[Any] | None: ... + @overload + def get_all(self, name: str, failobj: _T) -> list[Any] | _T: ... + if sys.version_info >= (3, 12): + @overload + def get(self, name: str, failobj: None = None) -> str | None: ... + @overload + def get(self, name: str, failobj: _T) -> _T | str: ... -class SimplePath(Protocol): - def joinpath(self) -> SimplePath: ... - def parent(self) -> SimplePath: ... - def read_text(self) -> str: ... - # There was a bug in `SimplePath` definition in cpython, see #8451 - # Strictly speaking `__div__` was defined in 3.10, not __truediv__, - # but it should have always been `__truediv__`. - def __truediv__(self) -> SimplePath: ... +if sys.version_info >= (3, 12): + class SimplePath(Protocol[_T_co]): + # At runtime this is defined as taking `str | _T`, but that causes trouble. + # See #11436. + def joinpath(self, other: str, /) -> _T_co: ... + @property + def parent(self) -> _T_co: ... + def read_text(self) -> str: ... + # As with joinpath(), this is annotated as taking `str | _T` at runtime. + def __truediv__(self, other: str, /) -> _T_co: ... + +else: + class SimplePath(Protocol): + # Actually takes only self at runtime, but that's clearly wrong + def joinpath(self, other: Any, /) -> SimplePath: ... + # Not defined as a property at runtime, but it should be + @property + def parent(self) -> Any: ... + def read_text(self) -> str: ... + # There was a bug in `SimplePath` definition in cpython, see #8451 + # Strictly speaking `__div__` was defined in 3.10, not __truediv__, + # but it should have always been `__truediv__`. + # Also, the runtime defines this method as taking no arguments, + # which is obviously wrong. + def __truediv__(self, other: Any, /) -> SimplePath: ... diff --git a/mypy/typeshed/stdlib/importlib/readers.pyi b/mypy/typeshed/stdlib/importlib/readers.pyi new file mode 100644 index 000000000000..41d7af966d58 --- /dev/null +++ b/mypy/typeshed/stdlib/importlib/readers.pyi @@ -0,0 +1,68 @@ +# On py311+, things are actually defined in importlib.resources.readers, +# and re-exported here, +# but doing it this way leads to less code duplication for us + +import pathlib +import sys +import zipfile +from _typeshed import Incomplete, StrPath +from collections.abc import Iterable, Iterator +from io import BufferedReader +from typing import Literal, NoReturn, TypeVar +from typing_extensions import Never + +if sys.version_info >= (3, 11): + import importlib.resources.abc as abc +else: + import importlib.abc as abc + +if sys.version_info >= (3, 10): + if sys.version_info >= (3, 11): + __all__ = ["FileReader", "ZipReader", "MultiplexedPath", "NamespaceReader"] + + if sys.version_info < (3, 11): + _T = TypeVar("_T") + + def remove_duplicates(items: Iterable[_T]) -> Iterator[_T]: ... + + class FileReader(abc.TraversableResources): + path: pathlib.Path + def __init__(self, loader) -> None: ... + def resource_path(self, resource: StrPath) -> str: ... + def files(self) -> pathlib.Path: ... + + class ZipReader(abc.TraversableResources): + prefix: str + archive: Incomplete + def __init__(self, loader, module: str) -> None: ... + def open_resource(self, resource: str) -> BufferedReader: ... + def is_resource(self, path: StrPath) -> bool: ... + def files(self) -> zipfile.Path: ... + + class MultiplexedPath(abc.Traversable): + def __init__(self, *paths: abc.Traversable) -> None: ... + def iterdir(self) -> Iterator[abc.Traversable]: ... + def read_bytes(self) -> NoReturn: ... + def read_text(self, *args: Never, **kwargs: Never) -> NoReturn: ... # type: ignore[override] + def is_dir(self) -> Literal[True]: ... + def is_file(self) -> Literal[False]: ... + + if sys.version_info >= (3, 12): + def joinpath(self, *descendants: str) -> abc.Traversable: ... + elif sys.version_info >= (3, 11): + def joinpath(self, child: str) -> abc.Traversable: ... # type: ignore[override] + else: + def joinpath(self, child: str) -> abc.Traversable: ... + + if sys.version_info < (3, 12): + __truediv__ = joinpath + + def open(self, *args: Never, **kwargs: Never) -> NoReturn: ... # type: ignore[override] + @property + def name(self) -> str: ... + + class NamespaceReader(abc.TraversableResources): + path: MultiplexedPath + def __init__(self, namespace_path) -> None: ... + def resource_path(self, resource: str) -> str: ... + def files(self) -> MultiplexedPath: ... diff --git a/mypy/typeshed/stdlib/importlib/resources.pyi b/mypy/typeshed/stdlib/importlib/resources/__init__.pyi similarity index 90% rename from mypy/typeshed/stdlib/importlib/resources.pyi rename to mypy/typeshed/stdlib/importlib/resources/__init__.pyi index ba3d9b087754..8d656563772c 100644 --- a/mypy/typeshed/stdlib/importlib/resources.pyi +++ b/mypy/typeshed/stdlib/importlib/resources/__init__.pyi @@ -7,6 +7,9 @@ from types import ModuleType from typing import Any, BinaryIO, TextIO from typing_extensions import TypeAlias +if sys.version_info >= (3, 9): + from importlib.abc import Traversable + __all__ = ["Package", "Resource", "contents", "is_resource", "open_binary", "open_text", "path", "read_binary", "read_text"] if sys.version_info >= (3, 9): @@ -31,9 +34,13 @@ def is_resource(package: Package, name: str) -> bool: ... def contents(package: Package) -> Iterator[str]: ... if sys.version_info >= (3, 9): - from importlib.abc import Traversable - def files(package: Package) -> Traversable: ... def as_file(path: Traversable) -> AbstractContextManager[Path]: ... +if sys.version_info >= (3, 12): + def files(anchor: Package | None = ...) -> Traversable: ... + +elif sys.version_info >= (3, 9): + def files(package: Package) -> Traversable: ... + if sys.version_info >= (3, 10): from importlib.abc import ResourceReader as ResourceReader diff --git a/mypy/typeshed/stdlib/importlib/resources/abc.pyi b/mypy/typeshed/stdlib/importlib/resources/abc.pyi new file mode 100644 index 000000000000..a36c952d01ac --- /dev/null +++ b/mypy/typeshed/stdlib/importlib/resources/abc.pyi @@ -0,0 +1,12 @@ +import sys + +if sys.version_info >= (3, 11): + # These are all actually defined in this file on 3.11+, + # and re-exported from importlib.abc, + # but it's much less code duplication for typeshed if we pretend that they're still defined + # in importlib.abc on 3.11+, and re-exported from this file + from importlib.abc import ( + ResourceReader as ResourceReader, + Traversable as Traversable, + TraversableResources as TraversableResources, + ) diff --git a/mypy/typeshed/stdlib/importlib/resources/readers.pyi b/mypy/typeshed/stdlib/importlib/resources/readers.pyi new file mode 100644 index 000000000000..0ab21fd29114 --- /dev/null +++ b/mypy/typeshed/stdlib/importlib/resources/readers.pyi @@ -0,0 +1,14 @@ +# On py311+, things are actually defined here +# and re-exported from importlib.readers, +# but doing it this way leads to less code duplication for us + +import sys +from collections.abc import Iterable, Iterator +from typing import TypeVar + +if sys.version_info >= (3, 11): + from importlib.readers import * + + _T = TypeVar("_T") + + def remove_duplicates(items: Iterable[_T]) -> Iterator[_T]: ... diff --git a/mypy/typeshed/stdlib/importlib/resources/simple.pyi b/mypy/typeshed/stdlib/importlib/resources/simple.pyi new file mode 100644 index 000000000000..c4c758111c2d --- /dev/null +++ b/mypy/typeshed/stdlib/importlib/resources/simple.pyi @@ -0,0 +1,56 @@ +import abc +import sys +from collections.abc import Iterator +from io import TextIOWrapper +from typing import IO, Any, BinaryIO, Literal, NoReturn, overload +from typing_extensions import Never + +if sys.version_info >= (3, 11): + from .abc import Traversable, TraversableResources + + class SimpleReader(abc.ABC): + @property + @abc.abstractmethod + def package(self) -> str: ... + @abc.abstractmethod + def children(self) -> list[SimpleReader]: ... + @abc.abstractmethod + def resources(self) -> list[str]: ... + @abc.abstractmethod + def open_binary(self, resource: str) -> BinaryIO: ... + @property + def name(self) -> str: ... + + class ResourceHandle(Traversable, metaclass=abc.ABCMeta): + parent: ResourceContainer + def __init__(self, parent: ResourceContainer, name: str) -> None: ... + def is_file(self) -> Literal[True]: ... + def is_dir(self) -> Literal[False]: ... + @overload + def open( + self, + mode: Literal["r"] = "r", + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + line_buffering: bool = False, + write_through: bool = False, + ) -> TextIOWrapper: ... + @overload + def open(self, mode: Literal["rb"]) -> BinaryIO: ... + @overload + def open(self, mode: str) -> IO[Any]: ... + def joinpath(self, name: Never) -> NoReturn: ... # type: ignore[override] + + class ResourceContainer(Traversable, metaclass=abc.ABCMeta): + reader: SimpleReader + def __init__(self, reader: SimpleReader) -> None: ... + def is_dir(self) -> Literal[True]: ... + def is_file(self) -> Literal[False]: ... + def iterdir(self) -> Iterator[ResourceHandle | ResourceContainer]: ... + def open(self, *args: Never, **kwargs: Never) -> NoReturn: ... # type: ignore[override] + if sys.version_info < (3, 12): + def joinpath(self, *descendants: str) -> Traversable: ... + + class TraversableReader(TraversableResources, SimpleReader, metaclass=abc.ABCMeta): + def files(self) -> ResourceContainer: ... diff --git a/mypy/typeshed/stdlib/importlib/simple.pyi b/mypy/typeshed/stdlib/importlib/simple.pyi new file mode 100644 index 000000000000..58d8c6617082 --- /dev/null +++ b/mypy/typeshed/stdlib/importlib/simple.pyi @@ -0,0 +1,11 @@ +import sys + +if sys.version_info >= (3, 11): + from .resources.simple import ( + ResourceContainer as ResourceContainer, + ResourceHandle as ResourceHandle, + SimpleReader as SimpleReader, + TraversableReader as TraversableReader, + ) + + __all__ = ["SimpleReader", "ResourceHandle", "ResourceContainer", "TraversableReader"] diff --git a/mypy/typeshed/stdlib/importlib/util.pyi b/mypy/typeshed/stdlib/importlib/util.pyi index f988eb270a26..2492c76d5c6c 100644 --- a/mypy/typeshed/stdlib/importlib/util.pyi +++ b/mypy/typeshed/stdlib/importlib/util.pyi @@ -1,16 +1,20 @@ import importlib.abc import importlib.machinery +import sys import types from _typeshed import ReadableBuffer, StrOrBytesPath +from _typeshed.importlib import LoaderProtocol from collections.abc import Callable from typing import Any from typing_extensions import ParamSpec _P = ParamSpec("_P") -def module_for_loader(fxn: Callable[_P, types.ModuleType]) -> Callable[_P, types.ModuleType]: ... -def set_loader(fxn: Callable[_P, types.ModuleType]) -> Callable[_P, types.ModuleType]: ... -def set_package(fxn: Callable[_P, types.ModuleType]) -> Callable[_P, types.ModuleType]: ... +if sys.version_info < (3, 12): + def module_for_loader(fxn: Callable[_P, types.ModuleType]) -> Callable[_P, types.ModuleType]: ... + def set_loader(fxn: Callable[_P, types.ModuleType]) -> Callable[_P, types.ModuleType]: ... + def set_package(fxn: Callable[_P, types.ModuleType]) -> Callable[_P, types.ModuleType]: ... + def resolve_name(name: str, package: str | None) -> str: ... MAGIC_NUMBER: bytes @@ -20,13 +24,13 @@ def source_from_cache(path: str) -> str: ... def decode_source(source_bytes: ReadableBuffer) -> str: ... def find_spec(name: str, package: str | None = None) -> importlib.machinery.ModuleSpec | None: ... def spec_from_loader( - name: str, loader: importlib.abc.Loader | None, *, origin: str | None = None, is_package: bool | None = None + name: str, loader: LoaderProtocol | None, *, origin: str | None = None, is_package: bool | None = None ) -> importlib.machinery.ModuleSpec | None: ... def spec_from_file_location( name: str, location: StrOrBytesPath | None = None, *, - loader: importlib.abc.Loader | None = None, + loader: LoaderProtocol | None = None, submodule_search_locations: list[str] | None = ..., ) -> importlib.machinery.ModuleSpec | None: ... def module_from_spec(spec: importlib.machinery.ModuleSpec) -> types.ModuleType: ... @@ -37,4 +41,4 @@ class LazyLoader(importlib.abc.Loader): def factory(cls, loader: importlib.abc.Loader) -> Callable[..., LazyLoader]: ... def exec_module(self, module: types.ModuleType) -> None: ... -def source_hash(source_bytes: ReadableBuffer) -> int: ... +def source_hash(source_bytes: ReadableBuffer) -> bytes: ... diff --git a/mypy/typeshed/stdlib/inspect.pyi b/mypy/typeshed/stdlib/inspect.pyi index 2525ef4968ec..0abf16d9d0ab 100644 --- a/mypy/typeshed/stdlib/inspect.pyi +++ b/mypy/typeshed/stdlib/inspect.pyi @@ -2,6 +2,7 @@ import dis import enum import sys import types +from _typeshed import StrPath from collections import OrderedDict from collections.abc import AsyncGenerator, Awaitable, Callable, Coroutine, Generator, Mapping, Sequence, Set as AbstractSet from types import ( @@ -24,8 +25,8 @@ from types import ( TracebackType, WrapperDescriptorType, ) -from typing import Any, ClassVar, NamedTuple, Protocol, TypeVar, overload -from typing_extensions import Literal, ParamSpec, Self, TypeAlias, TypeGuard +from typing import Any, ClassVar, Literal, NamedTuple, Protocol, TypeVar, overload +from typing_extensions import ParamSpec, Self, TypeAlias, TypeGuard, TypeIs if sys.version_info >= (3, 11): __all__ = [ @@ -127,8 +128,21 @@ if sys.version_info >= (3, 11): "walktree", ] + if sys.version_info >= (3, 12): + __all__ += [ + "markcoroutinefunction", + "AGEN_CLOSED", + "AGEN_CREATED", + "AGEN_RUNNING", + "AGEN_SUSPENDED", + "getasyncgenlocals", + "getasyncgenstate", + "BufferFlags", + ] + _P = ParamSpec("_P") _T = TypeVar("_T") +_F = TypeVar("_F", bound=Callable[..., Any]) _T_cont = TypeVar("_T_cont", contravariant=True) _V_cont = TypeVar("_V_cont", contravariant=True) @@ -161,90 +175,73 @@ TPFLAGS_IS_ABSTRACT: Literal[1048576] modulesbyfile: dict[str, Any] +_GetMembersPredicateTypeGuard: TypeAlias = Callable[[Any], TypeGuard[_T]] _GetMembersPredicate: TypeAlias = Callable[[Any], bool] +_GetMembersReturnTypeGuard: TypeAlias = list[tuple[str, _T]] _GetMembersReturn: TypeAlias = list[tuple[str, Any]] +@overload +def getmembers(object: object, predicate: _GetMembersPredicateTypeGuard[_T]) -> _GetMembersReturnTypeGuard[_T]: ... +@overload def getmembers(object: object, predicate: _GetMembersPredicate | None = None) -> _GetMembersReturn: ... if sys.version_info >= (3, 11): - def getmembers_static(object: object, predicate: _GetMembersPredicate | None = None) -> _GetMembersReturn: ... - -def getmodulename(path: str) -> str | None: ... -def ismodule(object: object) -> TypeGuard[ModuleType]: ... -def isclass(object: object) -> TypeGuard[type[Any]]: ... -def ismethod(object: object) -> TypeGuard[MethodType]: ... -def isfunction(object: object) -> TypeGuard[FunctionType]: ... - -if sys.version_info >= (3, 8): - @overload - def isgeneratorfunction(obj: Callable[..., Generator[Any, Any, Any]]) -> bool: ... - @overload - def isgeneratorfunction(obj: Callable[_P, Any]) -> TypeGuard[Callable[_P, GeneratorType[Any, Any, Any]]]: ... - @overload - def isgeneratorfunction(obj: object) -> TypeGuard[Callable[..., GeneratorType[Any, Any, Any]]]: ... - @overload - def iscoroutinefunction(obj: Callable[..., Coroutine[Any, Any, Any]]) -> bool: ... - @overload - def iscoroutinefunction(obj: Callable[_P, Awaitable[_T]]) -> TypeGuard[Callable[_P, CoroutineType[Any, Any, _T]]]: ... - @overload - def iscoroutinefunction(obj: Callable[_P, object]) -> TypeGuard[Callable[_P, CoroutineType[Any, Any, Any]]]: ... - @overload - def iscoroutinefunction(obj: object) -> TypeGuard[Callable[..., CoroutineType[Any, Any, Any]]]: ... - -else: - @overload - def isgeneratorfunction(object: Callable[..., Generator[Any, Any, Any]]) -> bool: ... - @overload - def isgeneratorfunction(object: Callable[_P, Any]) -> TypeGuard[Callable[_P, GeneratorType[Any, Any, Any]]]: ... - @overload - def isgeneratorfunction(object: object) -> TypeGuard[Callable[..., GeneratorType[Any, Any, Any]]]: ... - @overload - def iscoroutinefunction(object: Callable[..., Coroutine[Any, Any, Any]]) -> bool: ... @overload - def iscoroutinefunction(object: Callable[_P, Awaitable[_T]]) -> TypeGuard[Callable[_P, CoroutineType[Any, Any, _T]]]: ... + def getmembers_static(object: object, predicate: _GetMembersPredicateTypeGuard[_T]) -> _GetMembersReturnTypeGuard[_T]: ... @overload - def iscoroutinefunction(object: Callable[_P, Any]) -> TypeGuard[Callable[_P, CoroutineType[Any, Any, Any]]]: ... - @overload - def iscoroutinefunction(object: object) -> TypeGuard[Callable[..., CoroutineType[Any, Any, Any]]]: ... + def getmembers_static(object: object, predicate: _GetMembersPredicate | None = None) -> _GetMembersReturn: ... -def isgenerator(object: object) -> TypeGuard[GeneratorType[Any, Any, Any]]: ... -def iscoroutine(object: object) -> TypeGuard[CoroutineType[Any, Any, Any]]: ... -def isawaitable(object: object) -> TypeGuard[Awaitable[Any]]: ... - -if sys.version_info >= (3, 8): - @overload - def isasyncgenfunction(obj: Callable[..., AsyncGenerator[Any, Any]]) -> bool: ... - @overload - def isasyncgenfunction(obj: Callable[_P, Any]) -> TypeGuard[Callable[_P, AsyncGeneratorType[Any, Any]]]: ... - @overload - def isasyncgenfunction(obj: object) -> TypeGuard[Callable[..., AsyncGeneratorType[Any, Any]]]: ... - -else: - @overload - def isasyncgenfunction(object: Callable[..., AsyncGenerator[Any, Any]]) -> bool: ... - @overload - def isasyncgenfunction(object: Callable[_P, Any]) -> TypeGuard[Callable[_P, AsyncGeneratorType[Any, Any]]]: ... - @overload - def isasyncgenfunction(object: object) -> TypeGuard[Callable[..., AsyncGeneratorType[Any, Any]]]: ... +def getmodulename(path: StrPath) -> str | None: ... +def ismodule(object: object) -> TypeIs[ModuleType]: ... +def isclass(object: object) -> TypeIs[type[Any]]: ... +def ismethod(object: object) -> TypeIs[MethodType]: ... +def isfunction(object: object) -> TypeIs[FunctionType]: ... + +if sys.version_info >= (3, 12): + def markcoroutinefunction(func: _F) -> _F: ... + +@overload +def isgeneratorfunction(obj: Callable[..., Generator[Any, Any, Any]]) -> bool: ... +@overload +def isgeneratorfunction(obj: Callable[_P, Any]) -> TypeGuard[Callable[_P, GeneratorType[Any, Any, Any]]]: ... +@overload +def isgeneratorfunction(obj: object) -> TypeGuard[Callable[..., GeneratorType[Any, Any, Any]]]: ... +@overload +def iscoroutinefunction(obj: Callable[..., Coroutine[Any, Any, Any]]) -> bool: ... +@overload +def iscoroutinefunction(obj: Callable[_P, Awaitable[_T]]) -> TypeGuard[Callable[_P, CoroutineType[Any, Any, _T]]]: ... +@overload +def iscoroutinefunction(obj: Callable[_P, object]) -> TypeGuard[Callable[_P, CoroutineType[Any, Any, Any]]]: ... +@overload +def iscoroutinefunction(obj: object) -> TypeGuard[Callable[..., CoroutineType[Any, Any, Any]]]: ... +def isgenerator(object: object) -> TypeIs[GeneratorType[Any, Any, Any]]: ... +def iscoroutine(object: object) -> TypeIs[CoroutineType[Any, Any, Any]]: ... +def isawaitable(object: object) -> TypeIs[Awaitable[Any]]: ... +@overload +def isasyncgenfunction(obj: Callable[..., AsyncGenerator[Any, Any]]) -> bool: ... +@overload +def isasyncgenfunction(obj: Callable[_P, Any]) -> TypeGuard[Callable[_P, AsyncGeneratorType[Any, Any]]]: ... +@overload +def isasyncgenfunction(obj: object) -> TypeGuard[Callable[..., AsyncGeneratorType[Any, Any]]]: ... class _SupportsSet(Protocol[_T_cont, _V_cont]): - def __set__(self, __instance: _T_cont, __value: _V_cont) -> None: ... + def __set__(self, instance: _T_cont, value: _V_cont, /) -> None: ... class _SupportsDelete(Protocol[_T_cont]): - def __delete__(self, __instance: _T_cont) -> None: ... + def __delete__(self, instance: _T_cont, /) -> None: ... -def isasyncgen(object: object) -> TypeGuard[AsyncGeneratorType[Any, Any]]: ... -def istraceback(object: object) -> TypeGuard[TracebackType]: ... -def isframe(object: object) -> TypeGuard[FrameType]: ... -def iscode(object: object) -> TypeGuard[CodeType]: ... -def isbuiltin(object: object) -> TypeGuard[BuiltinFunctionType]: ... +def isasyncgen(object: object) -> TypeIs[AsyncGeneratorType[Any, Any]]: ... +def istraceback(object: object) -> TypeIs[TracebackType]: ... +def isframe(object: object) -> TypeIs[FrameType]: ... +def iscode(object: object) -> TypeIs[CodeType]: ... +def isbuiltin(object: object) -> TypeIs[BuiltinFunctionType]: ... if sys.version_info >= (3, 11): - def ismethodwrapper(object: object) -> TypeGuard[MethodWrapperType]: ... + def ismethodwrapper(object: object) -> TypeIs[MethodWrapperType]: ... def isroutine( object: object, -) -> TypeGuard[ +) -> TypeIs[ FunctionType | LambdaType | MethodType @@ -254,11 +251,11 @@ def isroutine( | MethodDescriptorType | ClassMethodDescriptorType ]: ... -def ismethoddescriptor(object: object) -> TypeGuard[MethodDescriptorType]: ... -def ismemberdescriptor(object: object) -> TypeGuard[MemberDescriptorType]: ... +def ismethoddescriptor(object: object) -> TypeIs[MethodDescriptorType]: ... +def ismemberdescriptor(object: object) -> TypeIs[MemberDescriptorType]: ... def isabstract(object: object) -> bool: ... -def isgetsetdescriptor(object: object) -> TypeGuard[GetSetDescriptorType]: ... -def isdatadescriptor(object: object) -> TypeGuard[_SupportsSet[Any, Any] | _SupportsDelete[Any]]: ... +def isgetsetdescriptor(object: object) -> TypeIs[GetSetDescriptorType]: ... +def isdatadescriptor(object: object) -> TypeIs[_SupportsSet[Any, Any] | _SupportsDelete[Any]]: ... # # Retrieving source code @@ -269,6 +266,14 @@ _SourceObjectType: TypeAlias = ( def findsource(object: _SourceObjectType) -> tuple[list[str], int]: ... def getabsfile(object: _SourceObjectType, _filename: str | None = None) -> str: ... + +# Special-case the two most common input types here +# to avoid the annoyingly vague `Sequence[str]` return type +@overload +def getblock(lines: list[str]) -> list[str]: ... +@overload +def getblock(lines: tuple[str, ...]) -> tuple[str, ...]: ... +@overload def getblock(lines: Sequence[str]) -> Sequence[str]: ... def getdoc(object: object) -> str | None: ... def getcomments(object: object) -> str | None: ... @@ -329,6 +334,7 @@ class Signature: def from_callable(cls, obj: _IntrospectableCallable, *, follow_wrapped: bool = True) -> Self: ... def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... if sys.version_info >= (3, 10): def get_annotations( @@ -341,15 +347,25 @@ if sys.version_info >= (3, 10): # The name is the same as the enum's name in CPython class _ParameterKind(enum.IntEnum): - POSITIONAL_ONLY: int - POSITIONAL_OR_KEYWORD: int - VAR_POSITIONAL: int - KEYWORD_ONLY: int - VAR_KEYWORD: int + POSITIONAL_ONLY = 0 + POSITIONAL_OR_KEYWORD = 1 + VAR_POSITIONAL = 2 + KEYWORD_ONLY = 3 + VAR_KEYWORD = 4 + + @property + def description(self) -> str: ... + +if sys.version_info >= (3, 12): + AGEN_CREATED: Literal["AGEN_CREATED"] + AGEN_RUNNING: Literal["AGEN_RUNNING"] + AGEN_SUSPENDED: Literal["AGEN_SUSPENDED"] + AGEN_CLOSED: Literal["AGEN_CLOSED"] - if sys.version_info >= (3, 8): - @property - def description(self) -> str: ... + def getasyncgenstate( + agen: AsyncGenerator[Any, Any] + ) -> Literal["AGEN_CREATED", "AGEN_RUNNING", "AGEN_SUSPENDED", "AGEN_CLOSED"]: ... + def getasyncgenlocals(agen: AsyncGeneratorType[Any, Any]) -> dict[str, Any]: ... class Parameter: def __init__(self, name: str, kind: _ParameterKind, *, default: Any = ..., annotation: Any = ...) -> None: ... @@ -377,6 +393,7 @@ class Parameter: annotation: Any = ..., ) -> Self: ... def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... class BoundArguments: arguments: OrderedDict[str, Any] @@ -413,6 +430,7 @@ if sys.version_info < (3, 11): varargs: str | None keywords: str | None defaults: tuple[Any, ...] + def getargspec(func: object) -> ArgSpec: ... class FullArgSpec(NamedTuple): @@ -442,9 +460,9 @@ if sys.version_info < (3, 11): varargs: str | None = None, varkw: str | None = None, defaults: tuple[Any, ...] | None = None, - kwonlyargs: Sequence[str] | None = ..., - kwonlydefaults: Mapping[str, Any] | None = ..., - annotations: Mapping[str, Any] = ..., + kwonlyargs: Sequence[str] | None = (), + kwonlydefaults: Mapping[str, Any] | None = {}, + annotations: Mapping[str, Any] = {}, formatarg: Callable[[str], str] = ..., formatvarargs: Callable[[str], str] = ..., formatvarkw: Callable[[str], str] = ..., @@ -464,7 +482,7 @@ def formatargvalues( formatvalue: Callable[[Any], str] | None = ..., ) -> str: ... def getmro(cls: type) -> tuple[type, ...]: ... -def getcallargs(__func: Callable[_P, Any], *args: _P.args, **kwds: _P.kwargs) -> dict[str, Any]: ... +def getcallargs(func: Callable[_P, Any], /, *args: _P.args, **kwds: _P.kwargs) -> dict[str, Any]: ... class ClosureVars(NamedTuple): nonlocals: Mapping[str, Any] @@ -590,3 +608,25 @@ def classify_class_attrs(cls: type) -> list[Attribute]: ... if sys.version_info >= (3, 9): class ClassFoundException(Exception): ... + +if sys.version_info >= (3, 12): + class BufferFlags(enum.IntFlag): + SIMPLE = 0 + WRITABLE = 1 + FORMAT = 4 + ND = 8 + STRIDES = 24 + C_CONTIGUOUS = 56 + F_CONTIGUOUS = 88 + ANY_CONTIGUOUS = 152 + INDIRECT = 280 + CONTIG = 9 + CONTIG_RO = 8 + STRIDED = 25 + STRIDED_RO = 24 + RECORDS = 29 + RECORDS_RO = 28 + FULL = 285 + FULL_RO = 284 + READ = 256 + WRITE = 512 diff --git a/mypy/typeshed/stdlib/io.pyi b/mypy/typeshed/stdlib/io.pyi index c3e07bacbe5a..fdbbc8dddce9 100644 --- a/mypy/typeshed/stdlib/io.pyi +++ b/mypy/typeshed/stdlib/io.pyi @@ -6,12 +6,13 @@ from _typeshed import FileDescriptorOrPath, ReadableBuffer, WriteableBuffer from collections.abc import Callable, Iterable, Iterator from os import _Opener from types import TracebackType -from typing import IO, Any, BinaryIO, TextIO -from typing_extensions import Literal, Self +from typing import IO, Any, BinaryIO, Literal, Protocol, TextIO, TypeVar, overload, type_check_only +from typing_extensions import Self __all__ = [ "BlockingIOError", "open", + "open_code", "IOBase", "RawIOBase", "FileIO", @@ -30,8 +31,10 @@ __all__ = [ "SEEK_END", ] -if sys.version_info >= (3, 8): - __all__ += ["open_code"] +if sys.version_info >= (3, 11): + __all__ += ["DEFAULT_BUFFER_SIZE", "IncrementalNewlineDecoder", "text_encoding"] + +_T = TypeVar("_T") DEFAULT_BUFFER_SIZE: Literal[8192] @@ -41,8 +44,7 @@ SEEK_END: Literal[2] open = builtins.open -if sys.version_info >= (3, 8): - def open_code(path: str) -> IO[bytes]: ... +def open_code(path: str) -> IO[bytes]: ... BlockingIOError = builtins.BlockingIOError @@ -61,15 +63,15 @@ class IOBase(metaclass=abc.ABCMeta): def isatty(self) -> bool: ... def readable(self) -> bool: ... read: Callable[..., Any] - def readlines(self, __hint: int = -1) -> list[bytes]: ... - def seek(self, __offset: int, __whence: int = ...) -> int: ... + def readlines(self, hint: int = -1, /) -> list[bytes]: ... + def seek(self, offset: int, whence: int = ..., /) -> int: ... def seekable(self) -> bool: ... def tell(self) -> int: ... - def truncate(self, __size: int | None = ...) -> int: ... + def truncate(self, size: int | None = ..., /) -> int: ... def writable(self) -> bool: ... write: Callable[..., Any] - def writelines(self, __lines: Iterable[ReadableBuffer]) -> None: ... - def readline(self, __size: int | None = -1) -> bytes: ... + def writelines(self, lines: Iterable[ReadableBuffer], /) -> None: ... + def readline(self, size: int | None = -1, /) -> bytes: ... def __del__(self) -> None: ... @property def closed(self) -> bool: ... @@ -77,32 +79,35 @@ class IOBase(metaclass=abc.ABCMeta): class RawIOBase(IOBase): def readall(self) -> bytes: ... - def readinto(self, __buffer: WriteableBuffer) -> int | None: ... - def write(self, __b: ReadableBuffer) -> int | None: ... - def read(self, __size: int = -1) -> bytes | None: ... + def readinto(self, buffer: WriteableBuffer, /) -> int | None: ... + def write(self, b: ReadableBuffer, /) -> int | None: ... + def read(self, size: int = -1, /) -> bytes | None: ... class BufferedIOBase(IOBase): raw: RawIOBase # This is not part of the BufferedIOBase API and may not exist on some implementations. def detach(self) -> RawIOBase: ... - def readinto(self, __buffer: WriteableBuffer) -> int: ... - def write(self, __buffer: ReadableBuffer) -> int: ... - def readinto1(self, __buffer: WriteableBuffer) -> int: ... - def read(self, __size: int | None = ...) -> bytes: ... - def read1(self, __size: int = ...) -> bytes: ... + def readinto(self, buffer: WriteableBuffer, /) -> int: ... + def write(self, buffer: ReadableBuffer, /) -> int: ... + def readinto1(self, buffer: WriteableBuffer, /) -> int: ... + def read(self, size: int | None = ..., /) -> bytes: ... + def read1(self, size: int = ..., /) -> bytes: ... -class FileIO(RawIOBase, BinaryIO): +class FileIO(RawIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of writelines in the base classes mode: str - name: FileDescriptorOrPath # type: ignore[assignment] + # The type of "name" equals the argument passed in to the constructor, + # but that can make FileIO incompatible with other I/O types that assume + # "name" is a str. In the future, making FileIO generic might help. + name: Any def __init__( self, file: FileDescriptorOrPath, mode: str = ..., closefd: bool = ..., opener: _Opener | None = ... ) -> None: ... @property def closefd(self) -> bool: ... - def write(self, __b: ReadableBuffer) -> int: ... - def read(self, __size: int = -1) -> bytes: ... + def write(self, b: ReadableBuffer, /) -> int: ... + def read(self, size: int = -1, /) -> bytes: ... def __enter__(self) -> Self: ... -class BytesIO(BufferedIOBase, BinaryIO): +class BytesIO(BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of methods in the base classes def __init__(self, initial_bytes: ReadableBuffer = ...) -> None: ... # BytesIO does not contain a "name" field. This workaround is necessary # to allow BytesIO sub-classes to add this field, as it is defined @@ -111,25 +116,25 @@ class BytesIO(BufferedIOBase, BinaryIO): def __enter__(self) -> Self: ... def getvalue(self) -> bytes: ... def getbuffer(self) -> memoryview: ... - def read1(self, __size: int | None = -1) -> bytes: ... + def read1(self, size: int | None = -1, /) -> bytes: ... -class BufferedReader(BufferedIOBase, BinaryIO): +class BufferedReader(BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of methods in the base classes def __enter__(self) -> Self: ... def __init__(self, raw: RawIOBase, buffer_size: int = ...) -> None: ... - def peek(self, __size: int = 0) -> bytes: ... + def peek(self, size: int = 0, /) -> bytes: ... -class BufferedWriter(BufferedIOBase, BinaryIO): +class BufferedWriter(BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of writelines in the base classes def __enter__(self) -> Self: ... def __init__(self, raw: RawIOBase, buffer_size: int = ...) -> None: ... - def write(self, __buffer: ReadableBuffer) -> int: ... + def write(self, buffer: ReadableBuffer, /) -> int: ... -class BufferedRandom(BufferedReader, BufferedWriter): +class BufferedRandom(BufferedReader, BufferedWriter): # type: ignore[misc] # incompatible definitions of methods in the base classes def __enter__(self) -> Self: ... - def seek(self, __target: int, __whence: int = 0) -> int: ... # stubtest needs this + def seek(self, target: int, whence: int = 0, /) -> int: ... # stubtest needs this class BufferedRWPair(BufferedIOBase): def __init__(self, reader: RawIOBase, writer: RawIOBase, buffer_size: int = ...) -> None: ... - def peek(self, __size: int = ...) -> bytes: ... + def peek(self, size: int = ..., /) -> bytes: ... class TextIOBase(IOBase): encoding: str @@ -138,22 +143,49 @@ class TextIOBase(IOBase): def __iter__(self) -> Iterator[str]: ... # type: ignore[override] def __next__(self) -> str: ... # type: ignore[override] def detach(self) -> BinaryIO: ... - def write(self, __s: str) -> int: ... - def writelines(self, __lines: Iterable[str]) -> None: ... # type: ignore[override] - def readline(self, __size: int = ...) -> str: ... # type: ignore[override] - def readlines(self, __hint: int = -1) -> list[str]: ... # type: ignore[override] - def read(self, __size: int | None = ...) -> str: ... - -class TextIOWrapper(TextIOBase, TextIO): + def write(self, s: str, /) -> int: ... + def writelines(self, lines: Iterable[str], /) -> None: ... # type: ignore[override] + def readline(self, size: int = ..., /) -> str: ... # type: ignore[override] + def readlines(self, hint: int = -1, /) -> list[str]: ... # type: ignore[override] + def read(self, size: int | None = ..., /) -> str: ... + +@type_check_only +class _WrappedBuffer(Protocol): + # "name" is wrapped by TextIOWrapper. Its type is inconsistent between + # the various I/O types, see the comments on TextIOWrapper.name and + # TextIO.name. + @property + def name(self) -> Any: ... + @property + def closed(self) -> bool: ... + def read(self, size: int = ..., /) -> ReadableBuffer: ... + # Optional: def read1(self, size: int, /) -> ReadableBuffer: ... + def write(self, b: bytes, /) -> object: ... + def flush(self) -> object: ... + def close(self) -> object: ... + def seekable(self) -> bool: ... + def readable(self) -> bool: ... + def writable(self) -> bool: ... + def truncate(self, size: int, /) -> int: ... + def fileno(self) -> int: ... + def isatty(self) -> int: ... + # Optional: Only needs to be present if seekable() returns True. + # def seek(self, offset: Literal[0], whence: Literal[2]) -> int: ... + # def tell(self) -> int: ... + +# TODO: Should be generic over the buffer type, but needs to wait for +# TypeVar defaults. +class TextIOWrapper(TextIOBase, TextIO): # type: ignore[misc] # incompatible definitions of write in the base classes def __init__( self, - buffer: IO[bytes], - encoding: str | None = ..., - errors: str | None = ..., - newline: str | None = ..., - line_buffering: bool = ..., - write_through: bool = ..., + buffer: _WrappedBuffer, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + line_buffering: bool = False, + write_through: bool = False, ) -> None: ... + # Equals the "buffer" argument passed in to the constructor. @property def buffer(self) -> BinaryIO: ... @property @@ -175,10 +207,14 @@ class TextIOWrapper(TextIOBase, TextIO): def __enter__(self) -> Self: ... def __iter__(self) -> Iterator[str]: ... # type: ignore[override] def __next__(self) -> str: ... # type: ignore[override] - def writelines(self, __lines: Iterable[str]) -> None: ... # type: ignore[override] - def readline(self, __size: int = -1) -> str: ... # type: ignore[override] - def readlines(self, __hint: int = -1) -> list[str]: ... # type: ignore[override] - def seek(self, __cookie: int, __whence: int = 0) -> int: ... # stubtest needs this + def writelines(self, lines: Iterable[str], /) -> None: ... # type: ignore[override] + def readline(self, size: int = -1, /) -> str: ... # type: ignore[override] + def readlines(self, hint: int = -1, /) -> list[str]: ... # type: ignore[override] + # Equals the "buffer" argument passed in to the constructor. + def detach(self) -> BinaryIO: ... + # TextIOWrapper's version of seek only supports a limited subset of + # operations. + def seek(self, cookie: int, whence: int = 0, /) -> int: ... class StringIO(TextIOWrapper): def __init__(self, initial_value: str | None = ..., newline: str | None = ...) -> None: ... @@ -193,4 +229,10 @@ class IncrementalNewlineDecoder(codecs.IncrementalDecoder): def decode(self, input: ReadableBuffer | str, final: bool = False) -> str: ... @property def newlines(self) -> str | tuple[str, ...] | None: ... - def setstate(self, __state: tuple[bytes, int]) -> None: ... + def setstate(self, state: tuple[bytes, int], /) -> None: ... + +if sys.version_info >= (3, 10): + @overload + def text_encoding(encoding: None, stacklevel: int = 2, /) -> Literal["locale", "utf-8"]: ... + @overload + def text_encoding(encoding: _T, stacklevel: int = 2, /) -> _T: ... diff --git a/mypy/typeshed/stdlib/ipaddress.pyi b/mypy/typeshed/stdlib/ipaddress.pyi index 9f9662137765..98b1893d2a8a 100644 --- a/mypy/typeshed/stdlib/ipaddress.pyi +++ b/mypy/typeshed/stdlib/ipaddress.pyi @@ -1,7 +1,7 @@ import sys -from collections.abc import Container, Iterable, Iterator -from typing import Any, Generic, SupportsInt, TypeVar, overload -from typing_extensions import Literal, Self, TypeAlias +from collections.abc import Iterable, Iterator +from typing import Any, Generic, Literal, SupportsInt, TypeVar, overload +from typing_extensions import Self, TypeAlias # Undocumented length constants IPV4LENGTH: Literal[32] @@ -34,9 +34,12 @@ class _IPAddressBase: class _BaseAddress(_IPAddressBase, SupportsInt): def __init__(self, address: object) -> None: ... def __add__(self, other: int) -> Self: ... + def __hash__(self) -> int: ... def __int__(self) -> int: ... def __sub__(self, other: int) -> Self: ... - def __format__(self, fmt: str) -> str: ... + if sys.version_info >= (3, 9): + def __format__(self, fmt: str) -> str: ... + def __eq__(self, other: object) -> bool: ... def __lt__(self, other: Self) -> bool: ... if sys.version_info >= (3, 11): @@ -67,7 +70,7 @@ class _BaseAddress(_IPAddressBase, SupportsInt): @property def packed(self) -> bytes: ... -class _BaseNetwork(_IPAddressBase, Container[_A], Iterable[_A], Generic[_A]): +class _BaseNetwork(_IPAddressBase, Generic[_A]): network_address: _A netmask: _A def __init__(self, address: object, strict: bool = ...) -> None: ... @@ -75,6 +78,7 @@ class _BaseNetwork(_IPAddressBase, Container[_A], Iterable[_A], Generic[_A]): def __getitem__(self, n: int) -> _A: ... def __iter__(self) -> Iterator[_A]: ... def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... def __lt__(self, other: Self) -> bool: ... if sys.version_info >= (3, 11): def __ge__(self, other: Self) -> bool: ... @@ -145,7 +149,10 @@ class _BaseV4: class IPv4Address(_BaseV4, _BaseAddress): ... class IPv4Network(_BaseV4, _BaseNetwork[IPv4Address]): ... -class IPv4Interface(IPv4Address, _BaseInterface[IPv4Address, IPv4Network]): ... + +class IPv4Interface(IPv4Address, _BaseInterface[IPv4Address, IPv4Network]): + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... class _BaseV6: @property @@ -166,11 +173,16 @@ class IPv6Address(_BaseV6, _BaseAddress): @property def scope_id(self) -> str | None: ... + def __hash__(self) -> int: ... + def __eq__(self, other: object) -> bool: ... + class IPv6Network(_BaseV6, _BaseNetwork[IPv6Address]): @property def is_site_local(self) -> bool: ... -class IPv6Interface(IPv6Address, _BaseInterface[IPv6Address, IPv6Network]): ... +class IPv6Interface(IPv6Address, _BaseInterface[IPv6Address, IPv6Network]): + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... def v4_int_to_packed(address: int) -> bytes: ... def v6_int_to_packed(address: int) -> bytes: ... diff --git a/mypy/typeshed/stdlib/itertools.pyi b/mypy/typeshed/stdlib/itertools.pyi index c7b92c3aebb5..264064dcd682 100644 --- a/mypy/typeshed/stdlib/itertools.pyi +++ b/mypy/typeshed/stdlib/itertools.pyi @@ -1,7 +1,7 @@ import sys from collections.abc import Callable, Iterable, Iterator -from typing import Any, Generic, SupportsComplex, SupportsFloat, SupportsInt, TypeVar, overload -from typing_extensions import Literal, Self, SupportsIndex, TypeAlias +from typing import Any, Generic, Literal, SupportsComplex, SupportsFloat, SupportsIndex, SupportsInt, TypeVar, overload +from typing_extensions import Self, TypeAlias if sys.version_info >= (3, 9): from types import GenericAlias @@ -10,6 +10,7 @@ _T = TypeVar("_T") _S = TypeVar("_S") _N = TypeVar("_N", int, float, SupportsFloat, SupportsInt, SupportsIndex, SupportsComplex) _T_co = TypeVar("_T_co", covariant=True) +_S_co = TypeVar("_S_co", covariant=True) _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") _T3 = TypeVar("_T3") @@ -23,7 +24,7 @@ _Predicate: TypeAlias = Callable[[_T], object] # Technically count can take anything that implements a number protocol and has an add method # but we can't enforce the add method -class count(Iterator[_N], Generic[_N]): +class count(Iterator[_N]): @overload def __new__(cls) -> count[int]: ... @overload @@ -33,12 +34,12 @@ class count(Iterator[_N], Generic[_N]): def __next__(self) -> _N: ... def __iter__(self) -> Self: ... -class cycle(Iterator[_T], Generic[_T]): - def __init__(self, __iterable: Iterable[_T]) -> None: ... +class cycle(Iterator[_T]): + def __init__(self, iterable: Iterable[_T], /) -> None: ... def __next__(self) -> _T: ... def __iter__(self) -> Self: ... -class repeat(Iterator[_T], Generic[_T]): +class repeat(Iterator[_T]): @overload def __init__(self, object: _T) -> None: ... @overload @@ -47,121 +48,113 @@ class repeat(Iterator[_T], Generic[_T]): def __iter__(self) -> Self: ... def __length_hint__(self) -> int: ... -class accumulate(Iterator[_T], Generic[_T]): - if sys.version_info >= (3, 8): - @overload - def __init__(self, iterable: Iterable[_T], func: None = None, *, initial: _T | None = ...) -> None: ... - @overload - def __init__(self, iterable: Iterable[_S], func: Callable[[_T, _S], _T], *, initial: _T | None = ...) -> None: ... - else: - def __init__(self, iterable: Iterable[_T], func: Callable[[_T, _T], _T] | None = ...) -> None: ... - +class accumulate(Iterator[_T]): + @overload + def __init__(self, iterable: Iterable[_T], func: None = None, *, initial: _T | None = ...) -> None: ... + @overload + def __init__(self, iterable: Iterable[_S], func: Callable[[_T, _S], _T], *, initial: _T | None = ...) -> None: ... def __iter__(self) -> Self: ... def __next__(self) -> _T: ... -class chain(Iterator[_T], Generic[_T]): +class chain(Iterator[_T]): def __init__(self, *iterables: Iterable[_T]) -> None: ... def __next__(self) -> _T: ... def __iter__(self) -> Self: ... @classmethod # We use type[Any] and not type[_S] to not lose the type inference from __iterable - def from_iterable(cls: type[Any], __iterable: Iterable[Iterable[_S]]) -> chain[_S]: ... + def from_iterable(cls: type[Any], iterable: Iterable[Iterable[_S]], /) -> chain[_S]: ... if sys.version_info >= (3, 9): - def __class_getitem__(cls, __item: Any) -> GenericAlias: ... + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... -class compress(Iterator[_T], Generic[_T]): +class compress(Iterator[_T]): def __init__(self, data: Iterable[_T], selectors: Iterable[Any]) -> None: ... def __iter__(self) -> Self: ... def __next__(self) -> _T: ... -class dropwhile(Iterator[_T], Generic[_T]): - def __init__(self, __predicate: _Predicate[_T], __iterable: Iterable[_T]) -> None: ... +class dropwhile(Iterator[_T]): + def __init__(self, predicate: _Predicate[_T], iterable: Iterable[_T], /) -> None: ... def __iter__(self) -> Self: ... def __next__(self) -> _T: ... -class filterfalse(Iterator[_T], Generic[_T]): - def __init__(self, __predicate: _Predicate[_T] | None, __iterable: Iterable[_T]) -> None: ... +class filterfalse(Iterator[_T]): + def __init__(self, predicate: _Predicate[_T] | None, iterable: Iterable[_T], /) -> None: ... def __iter__(self) -> Self: ... def __next__(self) -> _T: ... -class groupby(Iterator[tuple[_T, Iterator[_S]]], Generic[_T, _S]): +class groupby(Iterator[tuple[_T_co, Iterator[_S_co]]], Generic[_T_co, _S_co]): @overload def __new__(cls, iterable: Iterable[_T1], key: None = None) -> groupby[_T1, _T1]: ... @overload def __new__(cls, iterable: Iterable[_T1], key: Callable[[_T1], _T2]) -> groupby[_T2, _T1]: ... def __iter__(self) -> Self: ... - def __next__(self) -> tuple[_T, Iterator[_S]]: ... + def __next__(self) -> tuple[_T_co, Iterator[_S_co]]: ... -class islice(Iterator[_T], Generic[_T]): +class islice(Iterator[_T]): @overload - def __init__(self, __iterable: Iterable[_T], __stop: int | None) -> None: ... + def __init__(self, iterable: Iterable[_T], stop: int | None, /) -> None: ... @overload - def __init__(self, __iterable: Iterable[_T], __start: int | None, __stop: int | None, __step: int | None = ...) -> None: ... + def __init__(self, iterable: Iterable[_T], start: int | None, stop: int | None, step: int | None = ..., /) -> None: ... def __iter__(self) -> Self: ... def __next__(self) -> _T: ... -class starmap(Iterator[_T], Generic[_T]): - def __init__(self, __function: Callable[..., _T], __iterable: Iterable[Iterable[Any]]) -> None: ... +class starmap(Iterator[_T_co]): + def __new__(cls, function: Callable[..., _T], iterable: Iterable[Iterable[Any]], /) -> starmap[_T]: ... def __iter__(self) -> Self: ... - def __next__(self) -> _T: ... + def __next__(self) -> _T_co: ... -class takewhile(Iterator[_T], Generic[_T]): - def __init__(self, __predicate: _Predicate[_T], __iterable: Iterable[_T]) -> None: ... +class takewhile(Iterator[_T]): + def __init__(self, predicate: _Predicate[_T], iterable: Iterable[_T], /) -> None: ... def __iter__(self) -> Self: ... def __next__(self) -> _T: ... -def tee(__iterable: Iterable[_T], __n: int = 2) -> tuple[Iterator[_T], ...]: ... +def tee(iterable: Iterable[_T], n: int = 2, /) -> tuple[Iterator[_T], ...]: ... -class zip_longest(Iterator[_T_co], Generic[_T_co]): +class zip_longest(Iterator[_T_co]): # one iterable (fillvalue doesn't matter) @overload - def __new__(cls, __iter1: Iterable[_T1], *, fillvalue: object = ...) -> zip_longest[tuple[_T1]]: ... + def __new__(cls, iter1: Iterable[_T1], /, *, fillvalue: object = ...) -> zip_longest[tuple[_T1]]: ... # two iterables @overload # In the overloads without fillvalue, all of the tuple members could theoretically be None, # but we return Any instead to avoid false positives for code where we know one of the iterables # is longer. - def __new__(cls, __iter1: Iterable[_T1], __iter2: Iterable[_T2]) -> zip_longest[tuple[_T1 | Any, _T2 | Any]]: ... + def __new__(cls, iter1: Iterable[_T1], iter2: Iterable[_T2], /) -> zip_longest[tuple[_T1 | Any, _T2 | Any]]: ... @overload def __new__( - cls, __iter1: Iterable[_T1], __iter2: Iterable[_T2], *, fillvalue: _T + cls, iter1: Iterable[_T1], iter2: Iterable[_T2], /, *, fillvalue: _T ) -> zip_longest[tuple[_T1 | _T, _T2 | _T]]: ... # three iterables @overload def __new__( - cls, __iter1: Iterable[_T1], __iter2: Iterable[_T2], __iter3: Iterable[_T3] + cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], / ) -> zip_longest[tuple[_T1 | Any, _T2 | Any, _T3 | Any]]: ... @overload def __new__( - cls, __iter1: Iterable[_T1], __iter2: Iterable[_T2], __iter3: Iterable[_T3], *, fillvalue: _T + cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], /, *, fillvalue: _T ) -> zip_longest[tuple[_T1 | _T, _T2 | _T, _T3 | _T]]: ... # four iterables @overload def __new__( - cls, __iter1: Iterable[_T1], __iter2: Iterable[_T2], __iter3: Iterable[_T3], __iter4: Iterable[_T4] + cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], iter4: Iterable[_T4], / ) -> zip_longest[tuple[_T1 | Any, _T2 | Any, _T3 | Any, _T4 | Any]]: ... @overload def __new__( - cls, __iter1: Iterable[_T1], __iter2: Iterable[_T2], __iter3: Iterable[_T3], __iter4: Iterable[_T4], *, fillvalue: _T + cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], iter4: Iterable[_T4], /, *, fillvalue: _T ) -> zip_longest[tuple[_T1 | _T, _T2 | _T, _T3 | _T, _T4 | _T]]: ... # five iterables @overload def __new__( - cls, - __iter1: Iterable[_T1], - __iter2: Iterable[_T2], - __iter3: Iterable[_T3], - __iter4: Iterable[_T4], - __iter5: Iterable[_T5], + cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], iter4: Iterable[_T4], iter5: Iterable[_T5], / ) -> zip_longest[tuple[_T1 | Any, _T2 | Any, _T3 | Any, _T4 | Any, _T5 | Any]]: ... @overload def __new__( cls, - __iter1: Iterable[_T1], - __iter2: Iterable[_T2], - __iter3: Iterable[_T3], - __iter4: Iterable[_T4], - __iter5: Iterable[_T5], + iter1: Iterable[_T1], + iter2: Iterable[_T2], + iter3: Iterable[_T3], + iter4: Iterable[_T4], + iter5: Iterable[_T5], + /, *, fillvalue: _T, ) -> zip_longest[tuple[_T1 | _T, _T2 | _T, _T3 | _T, _T4 | _T, _T5 | _T]]: ... @@ -169,84 +162,77 @@ class zip_longest(Iterator[_T_co], Generic[_T_co]): @overload def __new__( cls, - __iter1: Iterable[_T], - __iter2: Iterable[_T], - __iter3: Iterable[_T], - __iter4: Iterable[_T], - __iter5: Iterable[_T], - __iter6: Iterable[_T], + iter1: Iterable[_T], + iter2: Iterable[_T], + iter3: Iterable[_T], + iter4: Iterable[_T], + iter5: Iterable[_T], + iter6: Iterable[_T], + /, *iterables: Iterable[_T], ) -> zip_longest[tuple[_T | Any, ...]]: ... @overload def __new__( cls, - __iter1: Iterable[_T], - __iter2: Iterable[_T], - __iter3: Iterable[_T], - __iter4: Iterable[_T], - __iter5: Iterable[_T], - __iter6: Iterable[_T], + iter1: Iterable[_T], + iter2: Iterable[_T], + iter3: Iterable[_T], + iter4: Iterable[_T], + iter5: Iterable[_T], + iter6: Iterable[_T], + /, *iterables: Iterable[_T], fillvalue: _T, ) -> zip_longest[tuple[_T, ...]]: ... def __iter__(self) -> Self: ... def __next__(self) -> _T_co: ... -class product(Iterator[_T_co], Generic[_T_co]): +class product(Iterator[_T_co]): @overload - def __new__(cls, __iter1: Iterable[_T1]) -> product[tuple[_T1]]: ... + def __new__(cls, iter1: Iterable[_T1], /) -> product[tuple[_T1]]: ... @overload - def __new__(cls, __iter1: Iterable[_T1], __iter2: Iterable[_T2]) -> product[tuple[_T1, _T2]]: ... + def __new__(cls, iter1: Iterable[_T1], iter2: Iterable[_T2], /) -> product[tuple[_T1, _T2]]: ... @overload - def __new__(cls, __iter1: Iterable[_T1], __iter2: Iterable[_T2], __iter3: Iterable[_T3]) -> product[tuple[_T1, _T2, _T3]]: ... + def __new__(cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], /) -> product[tuple[_T1, _T2, _T3]]: ... @overload def __new__( - cls, __iter1: Iterable[_T1], __iter2: Iterable[_T2], __iter3: Iterable[_T3], __iter4: Iterable[_T4] + cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], iter4: Iterable[_T4], / ) -> product[tuple[_T1, _T2, _T3, _T4]]: ... @overload def __new__( - cls, - __iter1: Iterable[_T1], - __iter2: Iterable[_T2], - __iter3: Iterable[_T3], - __iter4: Iterable[_T4], - __iter5: Iterable[_T5], + cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], iter4: Iterable[_T4], iter5: Iterable[_T5], / ) -> product[tuple[_T1, _T2, _T3, _T4, _T5]]: ... @overload def __new__( cls, - __iter1: Iterable[_T1], - __iter2: Iterable[_T2], - __iter3: Iterable[_T3], - __iter4: Iterable[_T4], - __iter5: Iterable[_T5], - __iter6: Iterable[_T6], + iter1: Iterable[_T1], + iter2: Iterable[_T2], + iter3: Iterable[_T3], + iter4: Iterable[_T4], + iter5: Iterable[_T5], + iter6: Iterable[_T6], + /, ) -> product[tuple[_T1, _T2, _T3, _T4, _T5, _T6]]: ... @overload - def __new__( - cls, - __iter1: Iterable[Any], - __iter2: Iterable[Any], - __iter3: Iterable[Any], - __iter4: Iterable[Any], - __iter5: Iterable[Any], - __iter6: Iterable[Any], - __iter7: Iterable[Any], - *iterables: Iterable[Any], - ) -> product[tuple[Any, ...]]: ... - @overload - def __new__(cls, *iterables: Iterable[_T1], repeat: int) -> product[tuple[_T1, ...]]: ... - @overload - def __new__(cls, *iterables: Iterable[Any], repeat: int = ...) -> product[tuple[Any, ...]]: ... + def __new__(cls, *iterables: Iterable[_T1], repeat: int = 1) -> product[tuple[_T1, ...]]: ... def __iter__(self) -> Self: ... def __next__(self) -> _T_co: ... -class permutations(Iterator[tuple[_T, ...]], Generic[_T]): - def __init__(self, iterable: Iterable[_T], r: int | None = ...) -> None: ... +class permutations(Iterator[_T_co]): + @overload + def __new__(cls, iterable: Iterable[_T], r: Literal[2]) -> permutations[tuple[_T, _T]]: ... + @overload + def __new__(cls, iterable: Iterable[_T], r: Literal[3]) -> permutations[tuple[_T, _T, _T]]: ... + @overload + def __new__(cls, iterable: Iterable[_T], r: Literal[4]) -> permutations[tuple[_T, _T, _T, _T]]: ... + @overload + def __new__(cls, iterable: Iterable[_T], r: Literal[5]) -> permutations[tuple[_T, _T, _T, _T, _T]]: ... + @overload + def __new__(cls, iterable: Iterable[_T], r: int | None = ...) -> permutations[tuple[_T, ...]]: ... def __iter__(self) -> Self: ... - def __next__(self) -> tuple[_T, ...]: ... + def __next__(self) -> _T_co: ... -class combinations(Iterator[_T_co], Generic[_T_co]): +class combinations(Iterator[_T_co]): @overload def __new__(cls, iterable: Iterable[_T], r: Literal[2]) -> combinations[tuple[_T, _T]]: ... @overload @@ -260,19 +246,28 @@ class combinations(Iterator[_T_co], Generic[_T_co]): def __iter__(self) -> Self: ... def __next__(self) -> _T_co: ... -class combinations_with_replacement(Iterator[tuple[_T, ...]], Generic[_T]): - def __init__(self, iterable: Iterable[_T], r: int) -> None: ... +class combinations_with_replacement(Iterator[_T_co]): + @overload + def __new__(cls, iterable: Iterable[_T], r: Literal[2]) -> combinations_with_replacement[tuple[_T, _T]]: ... + @overload + def __new__(cls, iterable: Iterable[_T], r: Literal[3]) -> combinations_with_replacement[tuple[_T, _T, _T]]: ... + @overload + def __new__(cls, iterable: Iterable[_T], r: Literal[4]) -> combinations_with_replacement[tuple[_T, _T, _T, _T]]: ... + @overload + def __new__(cls, iterable: Iterable[_T], r: Literal[5]) -> combinations_with_replacement[tuple[_T, _T, _T, _T, _T]]: ... + @overload + def __new__(cls, iterable: Iterable[_T], r: int) -> combinations_with_replacement[tuple[_T, ...]]: ... def __iter__(self) -> Self: ... - def __next__(self) -> tuple[_T, ...]: ... + def __next__(self) -> _T_co: ... if sys.version_info >= (3, 10): - class pairwise(Iterator[_T_co], Generic[_T_co]): - def __new__(cls, __iterable: Iterable[_T]) -> pairwise[tuple[_T, _T]]: ... + class pairwise(Iterator[_T_co]): + def __new__(cls, iterable: Iterable[_T], /) -> pairwise[tuple[_T, _T]]: ... def __iter__(self) -> Self: ... def __next__(self) -> _T_co: ... if sys.version_info >= (3, 12): - class batched(Iterator[_T_co], Generic[_T_co]): + class batched(Iterator[tuple[_T_co, ...]], Generic[_T_co]): def __new__(cls, iterable: Iterable[_T_co], n: int) -> Self: ... def __iter__(self) -> Self: ... def __next__(self) -> tuple[_T_co, ...]: ... diff --git a/mypy/typeshed/stdlib/json/encoder.pyi b/mypy/typeshed/stdlib/json/encoder.pyi index 0c0d366eb7a2..c1062688bd93 100644 --- a/mypy/typeshed/stdlib/json/encoder.pyi +++ b/mypy/typeshed/stdlib/json/encoder.pyi @@ -10,6 +10,8 @@ INFINITY: float def py_encode_basestring(s: str) -> str: ... # undocumented def py_encode_basestring_ascii(s: str) -> str: ... # undocumented +def encode_basestring(s: str) -> str: ... # undocumented +def encode_basestring_ascii(s: str) -> str: ... # undocumented class JSONEncoder: item_separator: str diff --git a/mypy/typeshed/stdlib/keyword.pyi b/mypy/typeshed/stdlib/keyword.pyi index 46c386048858..5eb7aab85317 100644 --- a/mypy/typeshed/stdlib/keyword.pyi +++ b/mypy/typeshed/stdlib/keyword.pyi @@ -1,6 +1,6 @@ import sys from collections.abc import Sequence -from typing_extensions import Final +from typing import Final if sys.version_info >= (3, 9): __all__ = ["iskeyword", "issoftkeyword", "kwlist", "softkwlist"] diff --git a/mypy/typeshed/stdlib/lib2to3/btm_matcher.pyi b/mypy/typeshed/stdlib/lib2to3/btm_matcher.pyi new file mode 100644 index 000000000000..4c87b664eb20 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/btm_matcher.pyi @@ -0,0 +1,28 @@ +from _typeshed import Incomplete, SupportsGetItem +from collections import defaultdict +from collections.abc import Iterable + +from .fixer_base import BaseFix +from .pytree import Leaf, Node + +class BMNode: + count: Incomplete + transition_table: Incomplete + fixers: Incomplete + id: Incomplete + content: str + def __init__(self) -> None: ... + +class BottomMatcher: + match: Incomplete + root: Incomplete + nodes: Incomplete + fixers: Incomplete + logger: Incomplete + def __init__(self) -> None: ... + def add_fixer(self, fixer: BaseFix) -> None: ... + def add(self, pattern: SupportsGetItem[int | slice, Incomplete] | None, start: BMNode) -> list[BMNode]: ... + def run(self, leaves: Iterable[Leaf]) -> defaultdict[BaseFix, list[Node | Leaf]]: ... + def print_ac(self) -> None: ... + +def type_repr(type_num: int) -> str | int: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixer_base.pyi b/mypy/typeshed/stdlib/lib2to3/fixer_base.pyi new file mode 100644 index 000000000000..06813c94308a --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixer_base.pyi @@ -0,0 +1,42 @@ +from _typeshed import Incomplete, StrPath +from abc import ABCMeta, abstractmethod +from collections.abc import MutableMapping +from typing import ClassVar, Literal, TypeVar + +from .pytree import Base, Leaf, Node + +_N = TypeVar("_N", bound=Base) + +class BaseFix: + PATTERN: ClassVar[str | None] + pattern: Incomplete | None + pattern_tree: Incomplete | None + options: Incomplete | None + filename: Incomplete | None + numbers: Incomplete + used_names: Incomplete + order: ClassVar[Literal["post", "pre"]] + explicit: ClassVar[bool] + run_order: ClassVar[int] + keep_line_order: ClassVar[bool] + BM_compatible: ClassVar[bool] + syms: Incomplete + log: Incomplete + def __init__(self, options: MutableMapping[str, Incomplete], log: list[str]) -> None: ... + def compile_pattern(self) -> None: ... + def set_filename(self, filename: StrPath) -> None: ... + def match(self, node: _N) -> Literal[False] | dict[str, _N]: ... + @abstractmethod + def transform(self, node: Base, results: dict[str, Base]) -> Node | Leaf | None: ... + def new_name(self, template: str = "xxx_todo_changeme") -> str: ... + first_log: bool + def log_message(self, message: str) -> None: ... + def cannot_convert(self, node: Base, reason: str | None = None) -> None: ... + def warning(self, node: Base, reason: str) -> None: ... + def start_tree(self, tree: Node, filename: StrPath) -> None: ... + def finish_tree(self, tree: Node, filename: StrPath) -> None: ... + +class ConditionalFix(BaseFix, metaclass=ABCMeta): + skip_on: ClassVar[str | None] + def start_tree(self, tree: Node, filename: StrPath, /) -> None: ... + def should_skip(self, node: Base) -> bool: ... diff --git a/test-data/pybind11_mypy_demo/stubgen/pybind11_mypy_demo/__init__.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/__init__.pyi similarity index 100% rename from test-data/pybind11_mypy_demo/stubgen/pybind11_mypy_demo/__init__.pyi rename to mypy/typeshed/stdlib/lib2to3/fixes/__init__.pyi diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_apply.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_apply.pyi new file mode 100644 index 000000000000..e53e3dd86457 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_apply.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixApply(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_asserts.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_asserts.pyi new file mode 100644 index 000000000000..fb0b472aa12a --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_asserts.pyi @@ -0,0 +1,10 @@ +from typing import ClassVar, Literal + +from ..fixer_base import BaseFix + +NAMES: dict[str, str] + +class FixAsserts(BaseFix): + BM_compatible: ClassVar[Literal[False]] + PATTERN: ClassVar[str] + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_basestring.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_basestring.pyi new file mode 100644 index 000000000000..8ed5ccaa7fd3 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_basestring.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixBasestring(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[Literal["'basestring'"]] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_buffer.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_buffer.pyi new file mode 100644 index 000000000000..1efca6228ea2 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_buffer.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixBuffer(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_dict.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_dict.pyi new file mode 100644 index 000000000000..08c54c3bc376 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_dict.pyi @@ -0,0 +1,16 @@ +from _typeshed import Incomplete +from typing import ClassVar, Literal + +from .. import fixer_base + +iter_exempt: set[str] + +class FixDict(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... + P1: ClassVar[str] + p1: ClassVar[Incomplete] + P2: ClassVar[str] + p2: ClassVar[Incomplete] + def in_special_context(self, node, isiter): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_except.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_except.pyi new file mode 100644 index 000000000000..30930a2c381e --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_except.pyi @@ -0,0 +1,14 @@ +from collections.abc import Generator, Iterable +from typing import ClassVar, Literal, TypeVar + +from .. import fixer_base +from ..pytree import Base + +_N = TypeVar("_N", bound=Base) + +def find_excepts(nodes: Iterable[_N]) -> Generator[tuple[_N, _N], None, None]: ... + +class FixExcept(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_exec.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_exec.pyi new file mode 100644 index 000000000000..71e2a820a564 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_exec.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixExec(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_execfile.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_execfile.pyi new file mode 100644 index 000000000000..8122a6389b12 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_execfile.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixExecfile(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_exitfunc.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_exitfunc.pyi new file mode 100644 index 000000000000..7fc910c0a1bc --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_exitfunc.pyi @@ -0,0 +1,13 @@ +from _typeshed import Incomplete, StrPath +from lib2to3 import fixer_base +from typing import ClassVar, Literal + +from ..pytree import Node + +class FixExitfunc(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def __init__(self, *args) -> None: ... + sys_import: Incomplete | None + def start_tree(self, tree: Node, filename: StrPath) -> None: ... + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_filter.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_filter.pyi new file mode 100644 index 000000000000..638889be8b65 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_filter.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixFilter(fixer_base.ConditionalFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + skip_on: ClassVar[Literal["future_builtins.filter"]] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_funcattrs.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_funcattrs.pyi new file mode 100644 index 000000000000..60487bb1f2a6 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_funcattrs.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixFuncattrs(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_future.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_future.pyi new file mode 100644 index 000000000000..12ed93f21223 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_future.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixFuture(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_getcwdu.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_getcwdu.pyi new file mode 100644 index 000000000000..aa3ccf50be9e --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_getcwdu.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixGetcwdu(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_has_key.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_has_key.pyi new file mode 100644 index 000000000000..f6f5a072e21b --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_has_key.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixHasKey(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_idioms.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_idioms.pyi new file mode 100644 index 000000000000..4595c57c7eb9 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_idioms.pyi @@ -0,0 +1,15 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +CMP: str +TYPE: str + +class FixIdioms(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[False]] + PATTERN: ClassVar[str] + def match(self, node): ... + def transform(self, node, results): ... + def transform_isinstance(self, node, results): ... + def transform_while(self, node, results) -> None: ... + def transform_sort(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_import.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_import.pyi new file mode 100644 index 000000000000..bf4b2d00925e --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_import.pyi @@ -0,0 +1,16 @@ +from _typeshed import StrPath +from collections.abc import Generator +from typing import ClassVar, Literal + +from .. import fixer_base +from ..pytree import Node + +def traverse_imports(names) -> Generator[str, None, None]: ... + +class FixImport(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + skip: bool + def start_tree(self, tree: Node, name: StrPath) -> None: ... + def transform(self, node, results): ... + def probably_a_local_import(self, imp_name): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_imports.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_imports.pyi new file mode 100644 index 000000000000..dd6f72dd88ac --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_imports.pyi @@ -0,0 +1,21 @@ +from _typeshed import StrPath +from collections.abc import Generator +from typing import ClassVar, Literal + +from .. import fixer_base +from ..pytree import Node + +MAPPING: dict[str, str] + +def alternates(members): ... +def build_pattern(mapping=...) -> Generator[str, None, None]: ... + +class FixImports(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + mapping = MAPPING + def build_pattern(self): ... + def compile_pattern(self) -> None: ... + def match(self, node): ... + replace: dict[str, str] + def start_tree(self, tree: Node, filename: StrPath) -> None: ... + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_imports2.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_imports2.pyi new file mode 100644 index 000000000000..8d55433085dd --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_imports2.pyi @@ -0,0 +1,6 @@ +from . import fix_imports + +MAPPING: dict[str, str] + +class FixImports2(fix_imports.FixImports): + mapping = MAPPING diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_input.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_input.pyi new file mode 100644 index 000000000000..fc1279535bed --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_input.pyi @@ -0,0 +1,11 @@ +from _typeshed import Incomplete +from typing import ClassVar, Literal + +from .. import fixer_base + +context: Incomplete + +class FixInput(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_intern.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_intern.pyi new file mode 100644 index 000000000000..804b7b2517a5 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_intern.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixIntern(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + order: ClassVar[Literal["pre"]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_isinstance.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_isinstance.pyi new file mode 100644 index 000000000000..31eefd625317 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_isinstance.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixIsinstance(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_itertools.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_itertools.pyi new file mode 100644 index 000000000000..229d86ee71bb --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_itertools.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixItertools(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + it_funcs: str + PATTERN: ClassVar[str] + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_itertools_imports.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_itertools_imports.pyi new file mode 100644 index 000000000000..39a4da506867 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_itertools_imports.pyi @@ -0,0 +1,7 @@ +from lib2to3 import fixer_base +from typing import ClassVar, Literal + +class FixItertoolsImports(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_long.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_long.pyi new file mode 100644 index 000000000000..9ccf2711d7d1 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_long.pyi @@ -0,0 +1,7 @@ +from lib2to3 import fixer_base +from typing import ClassVar, Literal + +class FixLong(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[Literal["'long'"]] + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_map.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_map.pyi new file mode 100644 index 000000000000..6e60282cf0be --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_map.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixMap(fixer_base.ConditionalFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + skip_on: ClassVar[Literal["future_builtins.map"]] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_metaclass.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_metaclass.pyi new file mode 100644 index 000000000000..1b1ec82032b4 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_metaclass.pyi @@ -0,0 +1,17 @@ +from collections.abc import Generator +from typing import ClassVar, Literal + +from .. import fixer_base +from ..pytree import Base + +def has_metaclass(parent): ... +def fixup_parse_tree(cls_node) -> None: ... +def fixup_simple_stmt(parent, i, stmt_node) -> None: ... +def remove_trailing_newline(node) -> None: ... +def find_metas(cls_node) -> Generator[tuple[Base, int, Base], None, None]: ... +def fixup_indent(suite) -> None: ... + +class FixMetaclass(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_methodattrs.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_methodattrs.pyi new file mode 100644 index 000000000000..594b5e2c95c9 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_methodattrs.pyi @@ -0,0 +1,10 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +MAP: dict[str, str] + +class FixMethodattrs(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_ne.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_ne.pyi new file mode 100644 index 000000000000..6ff1220b0472 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_ne.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixNe(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[False]] + def match(self, node): ... + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_next.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_next.pyi new file mode 100644 index 000000000000..b13914ae8c01 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_next.pyi @@ -0,0 +1,19 @@ +from _typeshed import StrPath +from typing import ClassVar, Literal + +from .. import fixer_base +from ..pytree import Node + +bind_warning: str + +class FixNext(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + order: ClassVar[Literal["pre"]] + shadowed_next: bool + def start_tree(self, tree: Node, filename: StrPath) -> None: ... + def transform(self, node, results) -> None: ... + +def is_assign_target(node): ... +def find_assign(node): ... +def is_subtree(root, node): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_nonzero.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_nonzero.pyi new file mode 100644 index 000000000000..5c37fc12ef08 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_nonzero.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixNonzero(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_numliterals.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_numliterals.pyi new file mode 100644 index 000000000000..113145e395f6 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_numliterals.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixNumliterals(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[False]] + def match(self, node): ... + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_operator.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_operator.pyi new file mode 100644 index 000000000000..b9863d38347b --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_operator.pyi @@ -0,0 +1,12 @@ +from lib2to3 import fixer_base +from typing import ClassVar, Literal + +def invocation(s): ... + +class FixOperator(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + order: ClassVar[Literal["pre"]] + methods: str + obj: str + PATTERN: ClassVar[str] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_paren.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_paren.pyi new file mode 100644 index 000000000000..237df6c5ff2c --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_paren.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixParen(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_print.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_print.pyi new file mode 100644 index 000000000000..e9564b04ac75 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_print.pyi @@ -0,0 +1,12 @@ +from _typeshed import Incomplete +from typing import ClassVar, Literal + +from .. import fixer_base + +parend_expr: Incomplete + +class FixPrint(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... + def add_kwarg(self, l_nodes, s_kwd, n_expr) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_raise.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_raise.pyi new file mode 100644 index 000000000000..e02c3080f409 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_raise.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixRaise(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_raw_input.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_raw_input.pyi new file mode 100644 index 000000000000..d1a0eb0e0a7e --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_raw_input.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixRawInput(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_reduce.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_reduce.pyi new file mode 100644 index 000000000000..f8ad876c21a6 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_reduce.pyi @@ -0,0 +1,8 @@ +from lib2to3 import fixer_base +from typing import ClassVar, Literal + +class FixReduce(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + order: ClassVar[Literal["pre"]] + PATTERN: ClassVar[str] + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_reload.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_reload.pyi new file mode 100644 index 000000000000..820075438eca --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_reload.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixReload(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + order: ClassVar[Literal["pre"]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_renames.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_renames.pyi new file mode 100644 index 000000000000..6283f1ab7ce2 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_renames.pyi @@ -0,0 +1,17 @@ +from collections.abc import Generator +from typing import ClassVar, Literal + +from .. import fixer_base + +MAPPING: dict[str, dict[str, str]] +LOOKUP: dict[tuple[str, str], str] + +def alternates(members): ... +def build_pattern() -> Generator[str, None, None]: ... + +class FixRenames(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + order: ClassVar[Literal["pre"]] + PATTERN: ClassVar[str] + def match(self, node): ... + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_repr.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_repr.pyi new file mode 100644 index 000000000000..3b192d396dd6 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_repr.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixRepr(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_set_literal.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_set_literal.pyi new file mode 100644 index 000000000000..6962ff326f56 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_set_literal.pyi @@ -0,0 +1,7 @@ +from lib2to3 import fixer_base +from typing import ClassVar, Literal + +class FixSetLiteral(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_standarderror.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_standarderror.pyi new file mode 100644 index 000000000000..ba914bcab5d6 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_standarderror.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixStandarderror(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_sys_exc.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_sys_exc.pyi new file mode 100644 index 000000000000..0fa1a4787087 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_sys_exc.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixSysExc(fixer_base.BaseFix): + exc_info: ClassVar[list[str]] + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_throw.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_throw.pyi new file mode 100644 index 000000000000..4c99855e5c37 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_throw.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixThrow(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_tuple_params.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_tuple_params.pyi new file mode 100644 index 000000000000..bfaa9970c996 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_tuple_params.pyi @@ -0,0 +1,17 @@ +from _typeshed import Incomplete +from typing import ClassVar, Literal + +from .. import fixer_base + +def is_docstring(stmt): ... + +class FixTupleParams(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... + def transform_lambda(self, node, results) -> None: ... + +def simplify_args(node): ... +def find_params(node): ... +def map_to_index(param_list, prefix=..., d: Incomplete | None = ...): ... +def tuple_name(param_list): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_types.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_types.pyi new file mode 100644 index 000000000000..e26dbec71a97 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_types.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixTypes(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_unicode.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_unicode.pyi new file mode 100644 index 000000000000..80d9d8b6e656 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_unicode.pyi @@ -0,0 +1,12 @@ +from _typeshed import StrPath +from typing import ClassVar, Literal + +from .. import fixer_base +from ..pytree import Node + +class FixUnicode(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[Literal["STRING | 'unicode' | 'unichr'"]] # type: ignore[name-defined] # Name "STRING" is not defined + unicode_literals: bool + def start_tree(self, tree: Node, filename: StrPath) -> None: ... + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_urllib.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_urllib.pyi new file mode 100644 index 000000000000..625472f609ab --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_urllib.pyi @@ -0,0 +1,15 @@ +from collections.abc import Generator +from typing import Literal + +from .fix_imports import FixImports + +MAPPING: dict[str, list[tuple[Literal["urllib.request", "urllib.parse", "urllib.error"], list[str]]]] + +def build_pattern() -> Generator[str, None, None]: ... + +class FixUrllib(FixImports): + def build_pattern(self): ... + def transform_import(self, node, results) -> None: ... + def transform_member(self, node, results): ... + def transform_dot(self, node, results) -> None: ... + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_ws_comma.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_ws_comma.pyi new file mode 100644 index 000000000000..4ce5cb2c4ac1 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_ws_comma.pyi @@ -0,0 +1,12 @@ +from typing import ClassVar, Literal + +from .. import fixer_base +from ..pytree import Leaf + +class FixWsComma(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[False]] + PATTERN: ClassVar[str] + COMMA: Leaf + COLON: Leaf + SEPS: tuple[Leaf, Leaf] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_xrange.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_xrange.pyi new file mode 100644 index 000000000000..71318b7660b6 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_xrange.pyi @@ -0,0 +1,20 @@ +from _typeshed import Incomplete, StrPath +from typing import ClassVar, Literal + +from .. import fixer_base +from ..pytree import Node + +class FixXrange(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + transformed_xranges: set[Incomplete] | None + def start_tree(self, tree: Node, filename: StrPath) -> None: ... + def finish_tree(self, tree: Node, filename: StrPath) -> None: ... + def transform(self, node, results): ... + def transform_xrange(self, node, results) -> None: ... + def transform_range(self, node, results): ... + P1: ClassVar[str] + p1: ClassVar[Incomplete] + P2: ClassVar[str] + p2: ClassVar[Incomplete] + def in_special_context(self, node): ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_xreadlines.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_xreadlines.pyi new file mode 100644 index 000000000000..b4794143a003 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_xreadlines.pyi @@ -0,0 +1,8 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixXreadlines(fixer_base.BaseFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + def transform(self, node, results) -> None: ... diff --git a/mypy/typeshed/stdlib/lib2to3/fixes/fix_zip.pyi b/mypy/typeshed/stdlib/lib2to3/fixes/fix_zip.pyi new file mode 100644 index 000000000000..805886ee3180 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/fixes/fix_zip.pyi @@ -0,0 +1,9 @@ +from typing import ClassVar, Literal + +from .. import fixer_base + +class FixZip(fixer_base.ConditionalFix): + BM_compatible: ClassVar[Literal[True]] + PATTERN: ClassVar[str] + skip_on: ClassVar[Literal["future_builtins.zip"]] + def transform(self, node, results): ... diff --git a/mypy/typeshed/stdlib/lib2to3/main.pyi b/mypy/typeshed/stdlib/lib2to3/main.pyi new file mode 100644 index 000000000000..5b7fdfca5d65 --- /dev/null +++ b/mypy/typeshed/stdlib/lib2to3/main.pyi @@ -0,0 +1,42 @@ +from _typeshed import FileDescriptorOrPath +from collections.abc import Container, Iterable, Iterator, Mapping, Sequence +from logging import _ExcInfoType +from typing import AnyStr, Literal + +from . import refactor as refactor + +def diff_texts(a: str, b: str, filename: str) -> Iterator[str]: ... + +class StdoutRefactoringTool(refactor.MultiprocessRefactoringTool): + nobackups: bool + show_diffs: bool + def __init__( + self, + fixers: Iterable[str], + options: Mapping[str, object] | None, + explicit: Container[str] | None, + nobackups: bool, + show_diffs: bool, + input_base_dir: str = "", + output_dir: str = "", + append_suffix: str = "", + ) -> None: ... + # Same as super.log_error and Logger.error + def log_error( # type: ignore[override] + self, + msg: str, + *args: Iterable[str], + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, + ) -> None: ... + # Same as super.write_file but without default values + def write_file( # type: ignore[override] + self, new_text: str, filename: FileDescriptorOrPath, old_text: str, encoding: str | None + ) -> None: ... + # filename has to be str + def print_output(self, old: str, new: str, filename: str, equal: bool) -> None: ... # type: ignore[override] + +def warn(msg: object) -> None: ... +def main(fixer_pkg: str, args: Sequence[AnyStr] | None = None) -> Literal[0, 1, 2]: ... diff --git a/mypy/typeshed/stdlib/lib2to3/pgen2/__init__.pyi b/mypy/typeshed/stdlib/lib2to3/pgen2/__init__.pyi index acc1cc429be9..de8a874f434d 100644 --- a/mypy/typeshed/stdlib/lib2to3/pgen2/__init__.pyi +++ b/mypy/typeshed/stdlib/lib2to3/pgen2/__init__.pyi @@ -1,8 +1,9 @@ from collections.abc import Callable -from lib2to3.pgen2.grammar import Grammar -from lib2to3.pytree import _RawNode from typing import Any from typing_extensions import TypeAlias +from ..pytree import _RawNode +from .grammar import Grammar + # This is imported in several lib2to3/pgen2 submodules _Convert: TypeAlias = Callable[[Grammar, _RawNode], Any] # noqa: Y047 diff --git a/mypy/typeshed/stdlib/lib2to3/pgen2/driver.pyi b/mypy/typeshed/stdlib/lib2to3/pgen2/driver.pyi index 9f6e4d6774ad..dea13fb9d0f8 100644 --- a/mypy/typeshed/stdlib/lib2to3/pgen2/driver.pyi +++ b/mypy/typeshed/stdlib/lib2to3/pgen2/driver.pyi @@ -1,10 +1,11 @@ from _typeshed import StrPath from collections.abc import Iterable -from lib2to3.pgen2 import _Convert -from lib2to3.pgen2.grammar import Grammar -from lib2to3.pytree import _NL from logging import Logger -from typing import IO, Any +from typing import IO + +from ..pytree import _NL +from . import _Convert +from .grammar import Grammar __all__ = ["Driver", "load_grammar"] @@ -13,7 +14,9 @@ class Driver: logger: Logger convert: _Convert def __init__(self, grammar: Grammar, convert: _Convert | None = None, logger: Logger | None = None) -> None: ... - def parse_tokens(self, tokens: Iterable[Any], debug: bool = False) -> _NL: ... + def parse_tokens( + self, tokens: Iterable[tuple[int, str, tuple[int, int], tuple[int, int], str]], debug: bool = False + ) -> _NL: ... def parse_stream_raw(self, stream: IO[str], debug: bool = False) -> _NL: ... def parse_stream(self, stream: IO[str], debug: bool = False) -> _NL: ... def parse_file(self, filename: StrPath, encoding: str | None = None, debug: bool = False) -> _NL: ... diff --git a/mypy/typeshed/stdlib/lib2to3/pgen2/parse.pyi b/mypy/typeshed/stdlib/lib2to3/pgen2/parse.pyi index 51eb671f4236..320c5f018d43 100644 --- a/mypy/typeshed/stdlib/lib2to3/pgen2/parse.pyi +++ b/mypy/typeshed/stdlib/lib2to3/pgen2/parse.pyi @@ -1,11 +1,12 @@ +from _typeshed import Incomplete from collections.abc import Sequence -from lib2to3.pgen2 import _Convert -from lib2to3.pgen2.grammar import _DFAS, Grammar -from lib2to3.pytree import _NL, _RawNode -from typing import Any from typing_extensions import TypeAlias -_Context: TypeAlias = Sequence[Any] +from ..pytree import _NL, _RawNode +from . import _Convert +from .grammar import _DFAS, Grammar + +_Context: TypeAlias = Sequence[Incomplete] class ParseError(Exception): msg: str diff --git a/mypy/typeshed/stdlib/lib2to3/pgen2/pgen.pyi b/mypy/typeshed/stdlib/lib2to3/pgen2/pgen.pyi index d346739d4d58..6d9f776c61ae 100644 --- a/mypy/typeshed/stdlib/lib2to3/pgen2/pgen.pyi +++ b/mypy/typeshed/stdlib/lib2to3/pgen2/pgen.pyi @@ -1,8 +1,9 @@ -from _typeshed import StrPath +from _typeshed import Incomplete, StrPath from collections.abc import Iterable, Iterator -from lib2to3.pgen2 import grammar -from lib2to3.pgen2.tokenize import _TokenInfo -from typing import IO, Any, NoReturn +from typing import IO, NoReturn, overload + +from . import grammar +from .tokenize import _TokenInfo class PgenGrammar(grammar.Grammar): ... @@ -26,19 +27,22 @@ class ParserGenerator: def parse_alt(self) -> tuple[NFAState, NFAState]: ... def parse_item(self) -> tuple[NFAState, NFAState]: ... def parse_atom(self) -> tuple[NFAState, NFAState]: ... - def expect(self, type: int, value: Any | None = None) -> str: ... + def expect(self, type: int, value: str | None = None) -> str: ... def gettoken(self) -> None: ... - def raise_error(self, msg: str, *args: Any) -> NoReturn: ... + @overload + def raise_error(self, msg: object) -> NoReturn: ... + @overload + def raise_error(self, msg: str, *args: object) -> NoReturn: ... class NFAState: arcs: list[tuple[str | None, NFAState]] def addarc(self, next: NFAState, label: str | None = None) -> None: ... class DFAState: - nfaset: dict[NFAState, Any] + nfaset: dict[NFAState, Incomplete] isfinal: bool arcs: dict[str, DFAState] - def __init__(self, nfaset: dict[NFAState, Any], final: NFAState) -> None: ... + def __init__(self, nfaset: dict[NFAState, Incomplete], final: NFAState) -> None: ... def addarc(self, next: DFAState, label: str) -> None: ... def unifystate(self, old: DFAState, new: DFAState) -> None: ... def __eq__(self, other: DFAState) -> bool: ... # type: ignore[override] diff --git a/mypy/typeshed/stdlib/lib2to3/pgen2/tokenize.pyi b/mypy/typeshed/stdlib/lib2to3/pgen2/tokenize.pyi index 2a9c3fbba821..af54de1b51d3 100644 --- a/mypy/typeshed/stdlib/lib2to3/pgen2/tokenize.pyi +++ b/mypy/typeshed/stdlib/lib2to3/pgen2/tokenize.pyi @@ -1,7 +1,8 @@ from collections.abc import Callable, Iterable, Iterator -from lib2to3.pgen2.token import * from typing_extensions import TypeAlias +from .token import * + __all__ = [ "AMPER", "AMPEREQUAL", diff --git a/mypy/typeshed/stdlib/lib2to3/pygram.pyi b/mypy/typeshed/stdlib/lib2to3/pygram.pyi index 00fdbd1a124e..86c74b54888a 100644 --- a/mypy/typeshed/stdlib/lib2to3/pygram.pyi +++ b/mypy/typeshed/stdlib/lib2to3/pygram.pyi @@ -1,5 +1,4 @@ -import sys -from lib2to3.pgen2.grammar import Grammar +from .pgen2.grammar import Grammar class Symbols: def __init__(self, grammar: Grammar) -> None: ... @@ -111,6 +110,5 @@ class pattern_symbols(Symbols): python_grammar: Grammar python_grammar_no_print_statement: Grammar -if sys.version_info >= (3, 8): - python_grammar_no_print_and_exec_statement: Grammar +python_grammar_no_print_and_exec_statement: Grammar pattern_grammar: Grammar diff --git a/mypy/typeshed/stdlib/lib2to3/pytree.pyi b/mypy/typeshed/stdlib/lib2to3/pytree.pyi index 4f756c9768db..138333bd58af 100644 --- a/mypy/typeshed/stdlib/lib2to3/pytree.pyi +++ b/mypy/typeshed/stdlib/lib2to3/pytree.pyi @@ -1,16 +1,20 @@ -from collections.abc import Iterator -from lib2to3.pgen2.grammar import Grammar -from typing import Any +from _typeshed import Incomplete, SupportsGetItem, SupportsLenAndGetItem, Unused +from abc import abstractmethod +from collections.abc import Iterable, Iterator, MutableSequence +from typing import Final from typing_extensions import Self, TypeAlias +from .fixer_base import BaseFix +from .pgen2.grammar import Grammar + _NL: TypeAlias = Node | Leaf _Context: TypeAlias = tuple[str, int, int] _Results: TypeAlias = dict[str, _NL] _RawNode: TypeAlias = tuple[int, str, _Context, list[_NL] | None] -HUGE: int +HUGE: Final = 0x7FFFFFFF -def type_repr(type_num: int) -> str: ... +def type_repr(type_num: int) -> str | int: ... class Base: type: int @@ -20,10 +24,14 @@ class Base: was_changed: bool was_checked: bool def __eq__(self, other: object) -> bool: ... - def _eq(self, other: Self) -> bool: ... + @abstractmethod + def _eq(self, other: Base) -> bool: ... + @abstractmethod def clone(self) -> Self: ... - def post_order(self) -> Iterator[_NL]: ... - def pre_order(self) -> Iterator[_NL]: ... + @abstractmethod + def post_order(self) -> Iterator[Self]: ... + @abstractmethod + def pre_order(self) -> Iterator[Self]: ... def replace(self, new: _NL | list[_NL]) -> None: ... def get_lineno(self) -> int: ... def changed(self) -> None: ... @@ -37,15 +45,23 @@ class Base: def get_suffix(self) -> str: ... class Node(Base): - fixers_applied: list[Any] + fixers_applied: MutableSequence[BaseFix] | None + # Is Unbound until set in refactor.RefactoringTool + future_features: frozenset[Incomplete] + # Is Unbound until set in pgen2.parse.Parser.pop + used_names: set[str] def __init__( self, type: int, - children: list[_NL], - context: Any | None = None, + children: Iterable[_NL], + context: Unused = None, prefix: str | None = None, - fixers_applied: list[Any] | None = None, + fixers_applied: MutableSequence[BaseFix] | None = None, ) -> None: ... + def _eq(self, other: Base) -> bool: ... + def clone(self) -> Node: ... + def post_order(self) -> Iterator[Self]: ... + def pre_order(self) -> Iterator[Self]: ... def set_child(self, i: int, child: _NL) -> None: ... def insert_child(self, i: int, child: _NL) -> None: ... def append_child(self, child: _NL) -> None: ... @@ -55,10 +71,19 @@ class Leaf(Base): lineno: int column: int value: str - fixers_applied: list[Any] + fixers_applied: MutableSequence[BaseFix] def __init__( - self, type: int, value: str, context: _Context | None = None, prefix: str | None = None, fixers_applied: list[Any] = ... + self, + type: int, + value: str, + context: _Context | None = None, + prefix: str | None = None, + fixers_applied: MutableSequence[BaseFix] = [], ) -> None: ... + def _eq(self, other: Base) -> bool: ... + def clone(self) -> Leaf: ... + def post_order(self) -> Iterator[Self]: ... + def pre_order(self) -> Iterator[Self]: ... def __unicode__(self) -> str: ... def convert(gr: Grammar, raw_node: _RawNode) -> _NL: ... @@ -69,8 +94,8 @@ class BasePattern: name: str | None def optimize(self) -> BasePattern: ... # sic, subclasses are free to optimize themselves into different patterns def match(self, node: _NL, results: _Results | None = None) -> bool: ... - def match_seq(self, nodes: list[_NL], results: _Results | None = None) -> bool: ... - def generate_matches(self, nodes: list[_NL]) -> Iterator[tuple[int, _Results]]: ... + def match_seq(self, nodes: SupportsLenAndGetItem[_NL], results: _Results | None = None) -> bool: ... + def generate_matches(self, nodes: SupportsGetItem[int, _NL]) -> Iterator[tuple[int, _Results]]: ... class LeafPattern(BasePattern): def __init__(self, type: int | None = None, content: str | None = None, name: str | None = None) -> None: ... @@ -87,4 +112,6 @@ class WildcardPattern(BasePattern): class NegatedPattern(BasePattern): def __init__(self, content: str | None = None) -> None: ... -def generate_matches(patterns: list[BasePattern], nodes: list[_NL]) -> Iterator[tuple[int, _Results]]: ... +def generate_matches( + patterns: SupportsGetItem[int | slice, BasePattern] | None, nodes: SupportsGetItem[int | slice, _NL] +) -> Iterator[tuple[int, _Results]]: ... diff --git a/mypy/typeshed/stdlib/lib2to3/refactor.pyi b/mypy/typeshed/stdlib/lib2to3/refactor.pyi index f1d89679aee7..a7f382540648 100644 --- a/mypy/typeshed/stdlib/lib2to3/refactor.pyi +++ b/mypy/typeshed/stdlib/lib2to3/refactor.pyi @@ -1,12 +1,15 @@ +from _typeshed import FileDescriptorOrPath, StrPath, SupportsGetItem from collections.abc import Container, Generator, Iterable, Mapping -from logging import Logger -from typing import Any, ClassVar, NoReturn -from typing_extensions import TypeAlias +from logging import Logger, _ExcInfoType +from multiprocessing import JoinableQueue +from multiprocessing.synchronize import Lock +from typing import Any, ClassVar, Final, NoReturn, overload +from .btm_matcher import BottomMatcher +from .fixer_base import BaseFix +from .pgen2.driver import Driver from .pgen2.grammar import Grammar - -_Driver: TypeAlias = Any # really lib2to3.driver.Driver -_BottomMatcher: TypeAlias = Any # really lib2to3.btm_matcher.BottomMatcher +from .pytree import Node def get_all_fix_names(fixer_pkg: str, remove_prefix: bool = True) -> list[str]: ... def get_fixers_from_package(pkg_name: str) -> list[str]: ... @@ -21,53 +24,59 @@ class RefactoringTool: options: dict[str, Any] grammar: Grammar write_unchanged_files: bool - errors: list[Any] + errors: list[tuple[str, Iterable[str], dict[str, _ExcInfoType]]] logger: Logger - fixer_log: list[Any] + fixer_log: list[str] wrote: bool - driver: _Driver - pre_order: Any - post_order: Any - files: list[Any] - BM: _BottomMatcher - bmi_pre_order: list[Any] - bmi_post_order: list[Any] + driver: Driver + pre_order: list[BaseFix] + post_order: list[BaseFix] + files: list[StrPath] + BM: BottomMatcher + bmi_pre_order: list[BaseFix] + bmi_post_order: list[BaseFix] def __init__( - self, fixer_names: Iterable[str], options: Mapping[str, Any] | None = None, explicit: Container[str] | None = None + self, fixer_names: Iterable[str], options: Mapping[str, object] | None = None, explicit: Container[str] | None = None ) -> None: ... - def get_fixers(self) -> tuple[list[Any], list[Any]]: ... - def log_error(self, msg: str, *args: Any, **kwds: Any) -> NoReturn: ... - def log_message(self, msg: str, *args: Any) -> None: ... - def log_debug(self, msg: str, *args: Any) -> None: ... - def print_output(self, old_text: str, new_text: str, filename: str, equal): ... + def get_fixers(self) -> tuple[list[BaseFix], list[BaseFix]]: ... + def log_error(self, msg: str, *args: Iterable[str], **kwargs: _ExcInfoType) -> NoReturn: ... + @overload + def log_message(self, msg: object) -> None: ... + @overload + def log_message(self, msg: str, *args: object) -> None: ... + @overload + def log_debug(self, msg: object) -> None: ... + @overload + def log_debug(self, msg: str, *args: object) -> None: ... + def print_output(self, old_text: str, new_text: str, filename: StrPath, equal: bool) -> None: ... def refactor(self, items: Iterable[str], write: bool = False, doctests_only: bool = False) -> None: ... def refactor_dir(self, dir_name: str, write: bool = False, doctests_only: bool = False) -> None: ... - def _read_python_source(self, filename: str) -> tuple[str, str]: ... - def refactor_file(self, filename: str, write: bool = False, doctests_only: bool = False) -> None: ... - def refactor_string(self, data: str, name: str): ... + def _read_python_source(self, filename: FileDescriptorOrPath) -> tuple[str, str]: ... + def refactor_file(self, filename: StrPath, write: bool = False, doctests_only: bool = False) -> None: ... + def refactor_string(self, data: str, name: str) -> Node | None: ... def refactor_stdin(self, doctests_only: bool = False) -> None: ... - def refactor_tree(self, tree, name: str) -> bool: ... - def traverse_by(self, fixers, traversal) -> None: ... + def refactor_tree(self, tree: Node, name: str) -> bool: ... + def traverse_by(self, fixers: SupportsGetItem[int, Iterable[BaseFix]] | None, traversal: Iterable[Node]) -> None: ... def processed_file( - self, new_text: str, filename: str, old_text: str | None = None, write: bool = False, encoding: str | None = None + self, new_text: str, filename: StrPath, old_text: str | None = None, write: bool = False, encoding: str | None = None ) -> None: ... - def write_file(self, new_text: str, filename: str, old_text: str, encoding: str | None = None) -> None: ... - PS1: ClassVar[str] - PS2: ClassVar[str] - def refactor_docstring(self, input: str, filename: str) -> str: ... - def refactor_doctest(self, block: list[str], lineno: int, indent: int, filename: str) -> list[str]: ... + def write_file(self, new_text: str, filename: FileDescriptorOrPath, old_text: str, encoding: str | None = None) -> None: ... + PS1: Final = ">>> " + PS2: Final = "... " + def refactor_docstring(self, input: str, filename: StrPath) -> str: ... + def refactor_doctest(self, block: list[str], lineno: int, indent: int, filename: StrPath) -> list[str]: ... def summarize(self) -> None: ... - def parse_block(self, block: Iterable[str], lineno: int, indent: int): ... + def parse_block(self, block: Iterable[str], lineno: int, indent: int) -> Node: ... def wrap_toks( self, block: Iterable[str], lineno: int, indent: int - ) -> Generator[tuple[Any, Any, tuple[int, int], tuple[int, int], str], None, None]: ... + ) -> Generator[tuple[int, str, tuple[int, int], tuple[int, int], str], None, None]: ... def gen_lines(self, block: Iterable[str], indent: int) -> Generator[str, None, None]: ... class MultiprocessingUnsupported(Exception): ... class MultiprocessRefactoringTool(RefactoringTool): - queue: Any | None - output_lock: Any | None + queue: JoinableQueue[None | tuple[Iterable[str], bool | int]] | None + output_lock: Lock | None def refactor( self, items: Iterable[str], write: bool = False, doctests_only: bool = False, num_processes: int = 1 ) -> None: ... diff --git a/mypy/typeshed/stdlib/linecache.pyi b/mypy/typeshed/stdlib/linecache.pyi index 8e317dd38990..2e050e13b621 100644 --- a/mypy/typeshed/stdlib/linecache.pyi +++ b/mypy/typeshed/stdlib/linecache.pyi @@ -1,5 +1,6 @@ import sys -from typing import Any, Protocol +from collections.abc import Callable +from typing import Any from typing_extensions import TypeAlias if sys.version_info >= (3, 9): @@ -10,8 +11,7 @@ else: _ModuleGlobals: TypeAlias = dict[str, Any] _ModuleMetadata: TypeAlias = tuple[int, float | None, list[str], str] -class _SourceLoader(Protocol): - def __call__(self) -> str | None: ... +_SourceLoader: TypeAlias = tuple[Callable[[], str | None]] cache: dict[str, _SourceLoader | _ModuleMetadata] # undocumented diff --git a/mypy/typeshed/stdlib/locale.pyi b/mypy/typeshed/stdlib/locale.pyi index 0b0dd9456e52..c18523e04361 100644 --- a/mypy/typeshed/stdlib/locale.pyi +++ b/mypy/typeshed/stdlib/locale.pyi @@ -1,6 +1,94 @@ import sys -from _typeshed import StrPath -from collections.abc import Callable, Iterable, Mapping +from _locale import ( + CHAR_MAX as CHAR_MAX, + LC_ALL as LC_ALL, + LC_COLLATE as LC_COLLATE, + LC_CTYPE as LC_CTYPE, + LC_MONETARY as LC_MONETARY, + LC_NUMERIC as LC_NUMERIC, + LC_TIME as LC_TIME, + localeconv as localeconv, + strcoll as strcoll, + strxfrm as strxfrm, +) + +# This module defines a function "str()", which is why "str" can't be used +# as a type annotation or type alias. +from builtins import str as _str +from collections.abc import Callable, Iterable +from decimal import Decimal +from typing import Any + +if sys.version_info >= (3, 11): + from _locale import getencoding as getencoding + +# Some parts of the `_locale` module are platform-specific: +if sys.platform != "win32": + from _locale import ( + ABDAY_1 as ABDAY_1, + ABDAY_2 as ABDAY_2, + ABDAY_3 as ABDAY_3, + ABDAY_4 as ABDAY_4, + ABDAY_5 as ABDAY_5, + ABDAY_6 as ABDAY_6, + ABDAY_7 as ABDAY_7, + ABMON_1 as ABMON_1, + ABMON_2 as ABMON_2, + ABMON_3 as ABMON_3, + ABMON_4 as ABMON_4, + ABMON_5 as ABMON_5, + ABMON_6 as ABMON_6, + ABMON_7 as ABMON_7, + ABMON_8 as ABMON_8, + ABMON_9 as ABMON_9, + ABMON_10 as ABMON_10, + ABMON_11 as ABMON_11, + ABMON_12 as ABMON_12, + ALT_DIGITS as ALT_DIGITS, + AM_STR as AM_STR, + CODESET as CODESET, + CRNCYSTR as CRNCYSTR, + D_FMT as D_FMT, + D_T_FMT as D_T_FMT, + DAY_1 as DAY_1, + DAY_2 as DAY_2, + DAY_3 as DAY_3, + DAY_4 as DAY_4, + DAY_5 as DAY_5, + DAY_6 as DAY_6, + DAY_7 as DAY_7, + ERA as ERA, + ERA_D_FMT as ERA_D_FMT, + ERA_D_T_FMT as ERA_D_T_FMT, + ERA_T_FMT as ERA_T_FMT, + LC_MESSAGES as LC_MESSAGES, + MON_1 as MON_1, + MON_2 as MON_2, + MON_3 as MON_3, + MON_4 as MON_4, + MON_5 as MON_5, + MON_6 as MON_6, + MON_7 as MON_7, + MON_8 as MON_8, + MON_9 as MON_9, + MON_10 as MON_10, + MON_11 as MON_11, + MON_12 as MON_12, + NOEXPR as NOEXPR, + PM_STR as PM_STR, + RADIXCHAR as RADIXCHAR, + T_FMT as T_FMT, + T_FMT_AMPM as T_FMT_AMPM, + THOUSEP as THOUSEP, + YESEXPR as YESEXPR, + bind_textdomain_codeset as bind_textdomain_codeset, + bindtextdomain as bindtextdomain, + dcgettext as dcgettext, + dgettext as dgettext, + gettext as gettext, + nl_langinfo as nl_langinfo, + textdomain as textdomain, + ) __all__ = [ "getlocale", @@ -15,13 +103,11 @@ __all__ = [ "str", "atof", "atoi", - "format", "format_string", "currency", "normalize", "LC_CTYPE", "LC_COLLATE", - "LC_MESSAGES", "LC_TIME", "LC_MONETARY", "LC_NUMERIC", @@ -32,96 +118,28 @@ __all__ = [ if sys.version_info >= (3, 11): __all__ += ["getencoding"] -# This module defines a function "str()", which is why "str" can't be used -# as a type annotation or type alias. -from builtins import str as _str -from decimal import Decimal -from typing import Any - -CODESET: int -D_T_FMT: int -D_FMT: int -T_FMT: int -T_FMT_AMPM: int -AM_STR: int -PM_STR: int +if sys.version_info < (3, 12): + __all__ += ["format"] -DAY_1: int -DAY_2: int -DAY_3: int -DAY_4: int -DAY_5: int -DAY_6: int -DAY_7: int -ABDAY_1: int -ABDAY_2: int -ABDAY_3: int -ABDAY_4: int -ABDAY_5: int -ABDAY_6: int -ABDAY_7: int - -MON_1: int -MON_2: int -MON_3: int -MON_4: int -MON_5: int -MON_6: int -MON_7: int -MON_8: int -MON_9: int -MON_10: int -MON_11: int -MON_12: int -ABMON_1: int -ABMON_2: int -ABMON_3: int -ABMON_4: int -ABMON_5: int -ABMON_6: int -ABMON_7: int -ABMON_8: int -ABMON_9: int -ABMON_10: int -ABMON_11: int -ABMON_12: int - -RADIXCHAR: int -THOUSEP: int -YESEXPR: int -NOEXPR: int -CRNCYSTR: int - -ERA: int -ERA_D_T_FMT: int -ERA_D_FMT: int -ERA_T_FMT: int - -ALT_DIGITS: int - -LC_CTYPE: int -LC_COLLATE: int -LC_TIME: int -LC_MONETARY: int -LC_MESSAGES: int -LC_NUMERIC: int -LC_ALL: int - -CHAR_MAX: int +if sys.platform != "win32": + __all__ += ["LC_MESSAGES"] class Error(Exception): ... -def setlocale(category: int, locale: _str | Iterable[_str | None] | None = None) -> _str: ... -def localeconv() -> Mapping[_str, int | _str | list[int]]: ... -def nl_langinfo(__key: int) -> _str: ... -def getdefaultlocale(envvars: tuple[_str, ...] = ...) -> tuple[_str | None, _str | None]: ... +def getdefaultlocale( + envvars: tuple[_str, ...] = ("LC_ALL", "LC_CTYPE", "LANG", "LANGUAGE") +) -> tuple[_str | None, _str | None]: ... def getlocale(category: int = ...) -> tuple[_str | None, _str | None]: ... +def setlocale(category: int, locale: _str | Iterable[_str | None] | None = None) -> _str: ... def getpreferredencoding(do_setlocale: bool = True) -> _str: ... def normalize(localename: _str) -> _str: ... def resetlocale(category: int = ...) -> None: ... -def strcoll(__os1: _str, __os2: _str) -> int: ... -def strxfrm(__string: _str) -> _str: ... -def format(percent: _str, value: float | Decimal, grouping: bool = False, monetary: bool = False, *additional: Any) -> _str: ... + +if sys.version_info < (3, 12): + def format( + percent: _str, value: float | Decimal, grouping: bool = False, monetary: bool = False, *additional: Any + ) -> _str: ... + def format_string(f: _str, val: Any, grouping: bool = False, monetary: bool = False) -> _str: ... def currency(val: float | Decimal, symbol: bool = True, grouping: bool = False, international: bool = False) -> _str: ... def delocalize(string: _str) -> _str: ... @@ -129,20 +147,6 @@ def atof(string: _str, func: Callable[[_str], float] = ...) -> float: ... def atoi(string: _str) -> int: ... def str(val: float) -> _str: ... -# native gettext functions -# https://docs.python.org/3/library/locale.html#access-to-message-catalogs -# https://github.com/python/cpython/blob/f4c03484da59049eb62a9bf7777b963e2267d187/Modules/_localemodule.c#L626 -if sys.platform == "linux" or sys.platform == "darwin": - def gettext(__msg: _str) -> _str: ... - def dgettext(__domain: _str | None, __msg: _str) -> _str: ... - def dcgettext(__domain: _str | None, __msg: _str, __category: int) -> _str: ... - def textdomain(__domain: _str | None) -> _str: ... - def bindtextdomain(__domain: _str, __dir: StrPath | None) -> _str: ... - def bind_textdomain_codeset(__domain: _str, __codeset: _str | None) -> _str | None: ... - -if sys.version_info >= (3, 11): - def getencoding() -> _str: ... - locale_alias: dict[_str, _str] # undocumented locale_encoding_alias: dict[_str, _str] # undocumented windows_locale: dict[int, _str] # undocumented diff --git a/mypy/typeshed/stdlib/logging/__init__.pyi b/mypy/typeshed/stdlib/logging/__init__.pyi index c74afa45ded1..7ceddfa7ff28 100644 --- a/mypy/typeshed/stdlib/logging/__init__.pyi +++ b/mypy/typeshed/stdlib/logging/__init__.pyi @@ -7,8 +7,8 @@ from re import Pattern from string import Template from time import struct_time from types import FrameType, TracebackType -from typing import Any, ClassVar, Generic, TextIO, TypeVar, overload -from typing_extensions import Literal, Self, TypeAlias +from typing import Any, ClassVar, Generic, Literal, Protocol, TextIO, TypeVar, overload +from typing_extensions import Self, TypeAlias if sys.version_info >= (3, 11): from types import GenericAlias @@ -60,14 +60,26 @@ __all__ = [ if sys.version_info >= (3, 11): __all__ += ["getLevelNamesMapping"] +if sys.version_info >= (3, 12): + __all__ += ["getHandlerByName", "getHandlerNames"] _SysExcInfoType: TypeAlias = tuple[type[BaseException], BaseException, TracebackType | None] | tuple[None, None, None] _ExcInfoType: TypeAlias = None | bool | _SysExcInfoType | BaseException _ArgsType: TypeAlias = tuple[object, ...] | Mapping[str, object] -_FilterType: TypeAlias = Filter | Callable[[LogRecord], bool] _Level: TypeAlias = int | str _FormatStyle: TypeAlias = Literal["%", "{", "$"] +if sys.version_info >= (3, 12): + class _SupportsFilter(Protocol): + def filter(self, record: LogRecord, /) -> bool | LogRecord: ... + + _FilterType: TypeAlias = Filter | Callable[[LogRecord], bool | LogRecord] | _SupportsFilter +else: + class _SupportsFilter(Protocol): + def filter(self, record: LogRecord, /) -> bool: ... + + _FilterType: TypeAlias = Filter | Callable[[LogRecord], bool] | _SupportsFilter + raiseExceptions: bool logThreads: bool logMultiprocessing: bool @@ -80,10 +92,13 @@ _levelToName: dict[int, str] _nameToLevel: dict[str, int] class Filterer: - filters: list[Filter] + filters: list[_FilterType] def addFilter(self, filter: _FilterType) -> None: ... def removeFilter(self, filter: _FilterType) -> None: ... - def filter(self, record: LogRecord) -> bool: ... + if sys.version_info >= (3, 12): + def filter(self, record: LogRecord) -> bool | LogRecord: ... + else: + def filter(self, record: LogRecord) -> bool: ... class Manager: # undocumented root: RootLogger @@ -111,173 +126,96 @@ class Logger(Filterer): def isEnabledFor(self, level: int) -> bool: ... def getEffectiveLevel(self) -> int: ... def getChild(self, suffix: str) -> Self: ... # see python/typing#980 - if sys.version_info >= (3, 8): - def debug( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - def info( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - def warning( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - def warn( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - def error( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - def exception( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = True, - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - def critical( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - def log( - self, - level: int, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - def _log( - self, - level: int, - msg: object, - args: _ArgsType, - exc_info: _ExcInfoType | None = None, - extra: Mapping[str, object] | None = None, - stack_info: bool = False, - stacklevel: int = 1, - ) -> None: ... # undocumented - else: - def debug( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - def info( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - def warning( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - def warn( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - def error( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - def critical( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - def log( - self, - level: int, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - def exception( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = True, - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - def _log( - self, - level: int, - msg: object, - args: _ArgsType, - exc_info: _ExcInfoType | None = None, - extra: Mapping[str, object] | None = None, - stack_info: bool = False, - ) -> None: ... # undocumented + if sys.version_info >= (3, 12): + def getChildren(self) -> set[Logger]: ... + + def debug( + self, + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, + ) -> None: ... + def info( + self, + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, + ) -> None: ... + def warning( + self, + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, + ) -> None: ... + def warn( + self, + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, + ) -> None: ... + def error( + self, + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, + ) -> None: ... + def exception( + self, + msg: object, + *args: object, + exc_info: _ExcInfoType = True, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, + ) -> None: ... + def critical( + self, + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, + ) -> None: ... + def log( + self, + level: int, + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, + ) -> None: ... + def _log( + self, + level: int, + msg: object, + args: _ArgsType, + exc_info: _ExcInfoType | None = None, + extra: Mapping[str, object] | None = None, + stack_info: bool = False, + stacklevel: int = 1, + ) -> None: ... # undocumented fatal = critical def addHandler(self, hdlr: Handler) -> None: ... def removeHandler(self, hdlr: Handler) -> None: ... - if sys.version_info >= (3, 8): - def findCaller(self, stack_info: bool = False, stacklevel: int = 1) -> tuple[str, int, str, str | None]: ... - else: - def findCaller(self, stack_info: bool = False) -> tuple[str, int, str, str | None]: ... - + def findCaller(self, stack_info: bool = False, stacklevel: int = 1) -> tuple[str, int, str, str | None]: ... def handle(self, record: LogRecord) -> None: ... def makeRecord( self, @@ -324,6 +262,10 @@ class Handler(Filterer): def format(self, record: LogRecord) -> str: ... def emit(self, record: LogRecord) -> None: ... +if sys.version_info >= (3, 12): + def getHandlerByName(name: str) -> Handler | None: ... + def getHandlerNames() -> frozenset[str]: ... + class Formatter: converter: Callable[[float | None], struct_time] _fmt: str | None # undocumented @@ -345,12 +287,10 @@ class Formatter: *, defaults: Mapping[str, Any] | None = None, ) -> None: ... - elif sys.version_info >= (3, 8): + else: def __init__( self, fmt: str | None = None, datefmt: str | None = None, style: _FormatStyle = "%", validate: bool = True ) -> None: ... - else: - def __init__(self, fmt: str | None = None, datefmt: str | None = None, style: _FormatStyle = "%") -> None: ... def format(self, record: LogRecord) -> str: ... def formatTime(self, record: LogRecord, datefmt: str | None = None) -> str: ... @@ -370,7 +310,10 @@ class Filter: name: str # undocumented nlen: int # undocumented def __init__(self, name: str = "") -> None: ... - def filter(self, record: LogRecord) -> bool: ... + if sys.version_info >= (3, 12): + def filter(self, record: LogRecord) -> bool | LogRecord: ... + else: + def filter(self, record: LogRecord) -> bool: ... class LogRecord: # args can be set to None by logging.handlers.QueueHandler @@ -389,7 +332,7 @@ class LogRecord: msecs: float # Only created when logging.Formatter.format is called. See #6132. message: str - msg: str + msg: str | Any # The runtime accepts any object, but will be a str in 99% of cases name: str pathname: str process: int | None @@ -398,6 +341,9 @@ class LogRecord: stack_info: str | None thread: int | None threadName: str | None + if sys.version_info >= (3, 12): + taskName: str | None + def __init__( self, name: str, @@ -412,7 +358,7 @@ class LogRecord: ) -> None: ... def getMessage(self) -> str: ... # Allows setting contextual information on LogRecord objects as per the docs, see #7833 - def __setattr__(self, __name: str, __value: Any) -> None: ... + def __setattr__(self, name: str, value: Any, /) -> None: ... _L = TypeVar("_L", bound=Logger | LoggerAdapter[Any]) @@ -427,286 +373,173 @@ class LoggerAdapter(Generic[_L]): def __init__(self, logger: _L, extra: Mapping[str, object]) -> None: ... def process(self, msg: Any, kwargs: MutableMapping[str, Any]) -> tuple[Any, MutableMapping[str, Any]]: ... - if sys.version_info >= (3, 8): - def debug( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - **kwargs: object, - ) -> None: ... - def info( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - **kwargs: object, - ) -> None: ... - def warning( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - **kwargs: object, - ) -> None: ... - def warn( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - **kwargs: object, - ) -> None: ... - def error( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - **kwargs: object, - ) -> None: ... - def exception( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = True, - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - **kwargs: object, - ) -> None: ... - def critical( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - **kwargs: object, - ) -> None: ... - def log( - self, - level: int, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - **kwargs: object, - ) -> None: ... - else: - def debug( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., - **kwargs: object, - ) -> None: ... - def info( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., - **kwargs: object, - ) -> None: ... - def warning( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., - **kwargs: object, - ) -> None: ... - def warn( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., - **kwargs: object, - ) -> None: ... - def error( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., - **kwargs: object, - ) -> None: ... - def exception( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = True, - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., - **kwargs: object, - ) -> None: ... - def critical( - self, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., - **kwargs: object, - ) -> None: ... - def log( - self, - level: int, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., - **kwargs: object, - ) -> None: ... - - def isEnabledFor(self, level: int) -> bool: ... - def getEffectiveLevel(self) -> int: ... - def setLevel(self, level: _Level) -> None: ... - def hasHandlers(self) -> bool: ... - def _log( - self, - level: int, - msg: object, - args: _ArgsType, - exc_info: _ExcInfoType | None = None, - extra: Mapping[str, object] | None = None, - stack_info: bool = False, - ) -> None: ... # undocumented - @property - def name(self) -> str: ... # undocumented - if sys.version_info >= (3, 11): - def __class_getitem__(cls, item: Any) -> GenericAlias: ... - -def getLogger(name: str | None = None) -> Logger: ... -def getLoggerClass() -> type[Logger]: ... -def getLogRecordFactory() -> Callable[..., LogRecord]: ... - -if sys.version_info >= (3, 8): def debug( + self, msg: object, *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, + **kwargs: object, ) -> None: ... def info( + self, msg: object, *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, + **kwargs: object, ) -> None: ... def warning( + self, msg: object, *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, + **kwargs: object, ) -> None: ... def warn( + self, msg: object, *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, + **kwargs: object, ) -> None: ... def error( + self, msg: object, *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - def critical( - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, + **kwargs: object, ) -> None: ... def exception( + self, msg: object, *args: object, exc_info: _ExcInfoType = True, - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - def log( - level: int, - msg: object, - *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - stacklevel: int = ..., - extra: Mapping[str, object] | None = ..., - ) -> None: ... - -else: - def debug( - msg: object, *args: object, exc_info: _ExcInfoType = ..., stack_info: bool = ..., extra: Mapping[str, object] | None = ... - ) -> None: ... - def info( - msg: object, *args: object, exc_info: _ExcInfoType = ..., stack_info: bool = ..., extra: Mapping[str, object] | None = ... - ) -> None: ... - def warning( - msg: object, *args: object, exc_info: _ExcInfoType = ..., stack_info: bool = ..., extra: Mapping[str, object] | None = ... - ) -> None: ... - def warn( - msg: object, *args: object, exc_info: _ExcInfoType = ..., stack_info: bool = ..., extra: Mapping[str, object] | None = ... - ) -> None: ... - def error( - msg: object, *args: object, exc_info: _ExcInfoType = ..., stack_info: bool = ..., extra: Mapping[str, object] | None = ... + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, + **kwargs: object, ) -> None: ... def critical( - msg: object, *args: object, exc_info: _ExcInfoType = ..., stack_info: bool = ..., extra: Mapping[str, object] | None = ... - ) -> None: ... - def exception( + self, msg: object, *args: object, - exc_info: _ExcInfoType = True, - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, + **kwargs: object, ) -> None: ... def log( + self, level: int, msg: object, *args: object, - exc_info: _ExcInfoType = ..., - stack_info: bool = ..., - extra: Mapping[str, object] | None = ..., + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, + **kwargs: object, ) -> None: ... + def isEnabledFor(self, level: int) -> bool: ... + def getEffectiveLevel(self) -> int: ... + def setLevel(self, level: _Level) -> None: ... + def hasHandlers(self) -> bool: ... + def _log( + self, + level: int, + msg: object, + args: _ArgsType, + exc_info: _ExcInfoType | None = None, + extra: Mapping[str, object] | None = None, + stack_info: bool = False, + ) -> None: ... # undocumented + @property + def name(self) -> str: ... # undocumented + if sys.version_info >= (3, 11): + def __class_getitem__(cls, item: Any) -> GenericAlias: ... + +def getLogger(name: str | None = None) -> Logger: ... +def getLoggerClass() -> type[Logger]: ... +def getLogRecordFactory() -> Callable[..., LogRecord]: ... +def debug( + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, +) -> None: ... +def info( + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, +) -> None: ... +def warning( + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, +) -> None: ... +def warn( + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, +) -> None: ... +def error( + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, +) -> None: ... +def critical( + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, +) -> None: ... +def exception( + msg: object, + *args: object, + exc_info: _ExcInfoType = True, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, +) -> None: ... +def log( + level: int, + msg: object, + *args: object, + exc_info: _ExcInfoType = None, + stack_info: bool = False, + stacklevel: int = 1, + extra: Mapping[str, object] | None = None, +) -> None: ... fatal = critical @@ -735,20 +568,6 @@ if sys.version_info >= (3, 9): errors: str | None = ..., ) -> None: ... -elif sys.version_info >= (3, 8): - def basicConfig( - *, - filename: StrPath | None = ..., - filemode: str = ..., - format: str = ..., - datefmt: str | None = ..., - style: _FormatStyle = ..., - level: _Level | None = ..., - stream: SupportsWrite[str] | None = ..., - handlers: Iterable[Handler] | None = ..., - force: bool = ..., - ) -> None: ... - else: def basicConfig( *, @@ -760,6 +579,7 @@ else: level: _Level | None = ..., stream: SupportsWrite[str] | None = ..., handlers: Iterable[Handler] | None = ..., + force: bool = ..., ) -> None: ... def shutdown(handlerList: Sequence[Any] = ...) -> None: ... # handlerList is undocumented @@ -767,7 +587,7 @@ def setLoggerClass(klass: type[Logger]) -> None: ... def captureWarnings(capture: bool) -> None: ... def setLogRecordFactory(factory: Callable[..., LogRecord]) -> None: ... -lastResort: StreamHandler[Any] | None +lastResort: Handler | None _StreamT = TypeVar("_StreamT", bound=SupportsWrite[str]) @@ -777,7 +597,7 @@ class StreamHandler(Handler, Generic[_StreamT]): @overload def __init__(self: StreamHandler[TextIO], stream: None = None) -> None: ... @overload - def __init__(self: StreamHandler[_StreamT], stream: _StreamT) -> None: ... + def __init__(self: StreamHandler[_StreamT], stream: _StreamT) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] #11780 def setStream(self, stream: _StreamT) -> _StreamT | None: ... if sys.version_info >= (3, 11): def __class_getitem__(cls, item: Any) -> GenericAlias: ... @@ -815,8 +635,7 @@ class PercentStyle: # undocumented default_format: str asctime_format: str asctime_search: str - if sys.version_info >= (3, 8): - validation_pattern: Pattern[str] + validation_pattern: Pattern[str] _fmt: str if sys.version_info >= (3, 10): def __init__(self, fmt: str, *, defaults: Mapping[str, Any] | None = None) -> None: ... @@ -824,9 +643,7 @@ class PercentStyle: # undocumented def __init__(self, fmt: str) -> None: ... def usesTime(self) -> bool: ... - if sys.version_info >= (3, 8): - def validate(self) -> None: ... - + def validate(self) -> None: ... def format(self, record: Any) -> str: ... class StrFormatStyle(PercentStyle): # undocumented diff --git a/mypy/typeshed/stdlib/logging/config.pyi b/mypy/typeshed/stdlib/logging/config.pyi index f76f655a6196..7a26846addbb 100644 --- a/mypy/typeshed/stdlib/logging/config.pyi +++ b/mypy/typeshed/stdlib/logging/config.pyi @@ -1,44 +1,58 @@ import sys from _typeshed import StrOrBytesPath -from collections.abc import Callable, Sequence +from collections.abc import Callable, Hashable, Iterable, Sequence from configparser import RawConfigParser from re import Pattern from threading import Thread -from typing import IO, Any +from typing import IO, Any, Literal, SupportsIndex, TypedDict, overload +from typing_extensions import Required, TypeAlias -from . import _Level - -if sys.version_info >= (3, 8): - from typing import Literal, TypedDict -else: - from typing_extensions import Literal, TypedDict +from . import Filter, Filterer, Formatter, Handler, Logger, _FilterType, _FormatStyle, _Level DEFAULT_LOGGING_CONFIG_PORT: int RESET_ERROR: int # undocumented IDENTIFIER: Pattern[str] # undocumented -class _RootLoggerConfiguration(TypedDict, total=False): - level: _Level - filters: Sequence[str] - handlers: Sequence[str] +if sys.version_info >= (3, 11): + class _RootLoggerConfiguration(TypedDict, total=False): + level: _Level + filters: Sequence[str | _FilterType] + handlers: Sequence[str] + +else: + class _RootLoggerConfiguration(TypedDict, total=False): + level: _Level + filters: Sequence[str] + handlers: Sequence[str] class _LoggerConfiguration(_RootLoggerConfiguration, TypedDict, total=False): propagate: bool -class _OptionalDictConfigArgs(TypedDict, total=False): - # these two can have custom factories (key: `()`) which can have extra keys - formatters: dict[str, dict[str, Any]] - filters: dict[str, dict[str, Any]] - # type checkers would warn about extra keys if this was a TypedDict - handlers: dict[str, dict[str, Any]] +_FormatterConfigurationTypedDict = TypedDict( + "_FormatterConfigurationTypedDict", {"class": str, "format": str, "datefmt": str, "style": _FormatStyle}, total=False +) + +class _FilterConfigurationTypedDict(TypedDict): + name: str + +# Formatter and filter configs can specify custom factories via the special `()` key. +# If that is the case, the dictionary can contain any additional keys +# https://docs.python.org/3/library/logging.config.html#user-defined-objects +_FormatterConfiguration: TypeAlias = _FormatterConfigurationTypedDict | dict[str, Any] +_FilterConfiguration: TypeAlias = _FilterConfigurationTypedDict | dict[str, Any] +# Handler config can have additional keys even when not providing a custom factory so we just use `dict`. +_HandlerConfiguration: TypeAlias = dict[str, Any] + +class _DictConfigArgs(TypedDict, total=False): + version: Required[Literal[1]] + formatters: dict[str, _FormatterConfiguration] + filters: dict[str, _FilterConfiguration] + handlers: dict[str, _HandlerConfiguration] loggers: dict[str, _LoggerConfiguration] - root: _RootLoggerConfiguration | None + root: _RootLoggerConfiguration incremental: bool disable_existing_loggers: bool -class _DictConfigArgs(_OptionalDictConfigArgs, TypedDict): - version: Literal[1] - # Accept dict[str, Any] to avoid false positives if called with a dict # type, since dict types are not compatible with TypedDicts. # @@ -64,3 +78,57 @@ else: def valid_ident(s: str) -> Literal[True]: ... # undocumented def listen(port: int = 9030, verify: Callable[[bytes], bytes | None] | None = None) -> Thread: ... def stopListening() -> None: ... + +class ConvertingMixin: # undocumented + def convert_with_key(self, key: Any, value: Any, replace: bool = True) -> Any: ... + def convert(self, value: Any) -> Any: ... + +class ConvertingDict(dict[Hashable, Any], ConvertingMixin): # undocumented + def __getitem__(self, key: Hashable) -> Any: ... + def get(self, key: Hashable, default: Any = None) -> Any: ... + def pop(self, key: Hashable, default: Any = None) -> Any: ... + +class ConvertingList(list[Any], ConvertingMixin): # undocumented + @overload + def __getitem__(self, key: SupportsIndex) -> Any: ... + @overload + def __getitem__(self, key: slice) -> Any: ... + def pop(self, idx: SupportsIndex = -1) -> Any: ... + +class ConvertingTuple(tuple[Any, ...], ConvertingMixin): # undocumented + @overload + def __getitem__(self, key: SupportsIndex) -> Any: ... + @overload + def __getitem__(self, key: slice) -> Any: ... + +class BaseConfigurator: # undocumented + CONVERT_PATTERN: Pattern[str] + WORD_PATTERN: Pattern[str] + DOT_PATTERN: Pattern[str] + INDEX_PATTERN: Pattern[str] + DIGIT_PATTERN: Pattern[str] + value_converters: dict[str, str] + importer: Callable[..., Any] + + def __init__(self, config: _DictConfigArgs | dict[str, Any]) -> None: ... + def resolve(self, s: str) -> Any: ... + def ext_convert(self, value: str) -> Any: ... + def cfg_convert(self, value: str) -> Any: ... + def convert(self, value: Any) -> Any: ... + def configure_custom(self, config: dict[str, Any]) -> Any: ... + def as_tuple(self, value: list[Any] | tuple[Any]) -> tuple[Any]: ... + +class DictConfigurator(BaseConfigurator): + def configure(self) -> None: ... # undocumented + def configure_formatter(self, config: _FormatterConfiguration) -> Formatter | Any: ... # undocumented + def configure_filter(self, config: _FilterConfiguration) -> Filter | Any: ... # undocumented + def add_filters(self, filterer: Filterer, filters: Iterable[_FilterType]) -> None: ... # undocumented + def configure_handler(self, config: _HandlerConfiguration) -> Handler | Any: ... # undocumented + def add_handlers(self, logger: Logger, handlers: Iterable[str]) -> None: ... # undocumented + def common_logger_config( + self, logger: Logger, config: _LoggerConfiguration, incremental: bool = False + ) -> None: ... # undocumented + def configure_logger(self, name: str, config: _LoggerConfiguration, incremental: bool = False) -> None: ... # undocumented + def configure_root(self, config: _LoggerConfiguration, incremental: bool = False) -> None: ... # undocumented + +dictConfigClass = DictConfigurator diff --git a/mypy/typeshed/stdlib/logging/handlers.pyi b/mypy/typeshed/stdlib/logging/handlers.pyi index 7e0bfd705895..4c3dc913308c 100644 --- a/mypy/typeshed/stdlib/logging/handlers.pyi +++ b/mypy/typeshed/stdlib/logging/handlers.pyi @@ -5,10 +5,12 @@ import sys from _typeshed import ReadableBuffer, StrPath from collections.abc import Callable from logging import FileHandler, Handler, LogRecord -from queue import Queue, SimpleQueue from re import Pattern from socket import SocketKind, socket -from typing import Any, ClassVar +from threading import Thread +from typing import Any, ClassVar, Protocol, TypeVar + +_T = TypeVar("_T") DEFAULT_TCP_LOGGING_PORT: int DEFAULT_UDP_LOGGING_PORT: int @@ -177,7 +179,9 @@ class SysLogHandler(Handler): priority_names: ClassVar[dict[str, int]] # undocumented facility_names: ClassVar[dict[str, int]] # undocumented priority_map: ClassVar[dict[str, str]] # undocumented - def __init__(self, address: tuple[str, int] | str = ..., facility: int = 1, socktype: SocketKind | None = None) -> None: ... + def __init__( + self, address: tuple[str, int] | str = ("localhost", 514), facility: str | int = 1, socktype: SocketKind | None = None + ) -> None: ... if sys.version_info >= (3, 11): def createSocket(self) -> None: ... @@ -247,17 +251,22 @@ class HTTPHandler(Handler): if sys.version_info >= (3, 9): def getConnection(self, host: str, secure: bool) -> http.client.HTTPConnection: ... # undocumented +class _QueueLike(Protocol[_T]): + def get(self) -> _T: ... + def put_nowait(self, item: _T, /) -> None: ... + class QueueHandler(Handler): - queue: SimpleQueue[Any] | Queue[Any] # undocumented - def __init__(self, queue: SimpleQueue[Any] | Queue[Any]) -> None: ... + queue: _QueueLike[Any] + def __init__(self, queue: _QueueLike[Any]) -> None: ... def prepare(self, record: LogRecord) -> Any: ... def enqueue(self, record: LogRecord) -> None: ... class QueueListener: handlers: tuple[Handler, ...] # undocumented respect_handler_level: bool # undocumented - queue: SimpleQueue[Any] | Queue[Any] # undocumented - def __init__(self, queue: SimpleQueue[Any] | Queue[Any], *handlers: Handler, respect_handler_level: bool = False) -> None: ... + queue: _QueueLike[Any] # undocumented + _thread: Thread | None # undocumented + def __init__(self, queue: _QueueLike[Any], *handlers: Handler, respect_handler_level: bool = False) -> None: ... def dequeue(self, block: bool) -> LogRecord: ... def prepare(self, record: LogRecord) -> Any: ... def start(self) -> None: ... diff --git a/mypy/typeshed/stdlib/lzma.pyi b/mypy/typeshed/stdlib/lzma.pyi index 34bd6f3f8db1..c05e46a02aeb 100644 --- a/mypy/typeshed/stdlib/lzma.pyi +++ b/mypy/typeshed/stdlib/lzma.pyi @@ -1,8 +1,8 @@ -import io +from _compression import BaseStream from _typeshed import ReadableBuffer, StrOrBytesPath from collections.abc import Mapping, Sequence -from typing import IO, Any, TextIO, overload -from typing_extensions import Literal, Self, TypeAlias, final +from typing import IO, Any, Literal, TextIO, final, overload +from typing_extensions import Self, TypeAlias __all__ = [ "CHECK_NONE", @@ -99,12 +99,12 @@ class LZMACompressor: def __init__( self, format: int | None = ..., check: int = ..., preset: int | None = ..., filters: _FilterChain | None = ... ) -> None: ... - def compress(self, __data: ReadableBuffer) -> bytes: ... + def compress(self, data: ReadableBuffer, /) -> bytes: ... def flush(self) -> bytes: ... class LZMAError(Exception): ... -class LZMAFile(io.BufferedIOBase, IO[bytes]): +class LZMAFile(BaseStream, IO[bytes]): # type: ignore[misc] # incompatible definitions of writelines in the base classes def __init__( self, filename: _PathOrFile | None = None, @@ -194,4 +194,4 @@ def compress( def decompress( data: ReadableBuffer, format: int = 0, memlimit: int | None = None, filters: _FilterChain | None = None ) -> bytes: ... -def is_check_supported(__check_id: int) -> bool: ... +def is_check_supported(check_id: int, /) -> bool: ... diff --git a/mypy/typeshed/stdlib/macpath.pyi b/mypy/typeshed/stdlib/macpath.pyi deleted file mode 100644 index 37821f44b200..000000000000 --- a/mypy/typeshed/stdlib/macpath.pyi +++ /dev/null @@ -1,104 +0,0 @@ -from _typeshed import BytesPath, StrOrBytesPath, StrPath -from genericpath import ( - commonprefix as commonprefix, - exists as exists, - getatime as getatime, - getctime as getctime, - getmtime as getmtime, - getsize as getsize, - isdir as isdir, - isfile as isfile, - samefile as samefile, - sameopenfile as sameopenfile, - samestat as samestat, -) -from os import PathLike - -# Re-export common definitions from posixpath to reduce duplication -from posixpath import ( - abspath as abspath, - curdir as curdir, - defpath as defpath, - devnull as devnull, - expanduser as expanduser, - expandvars as expandvars, - extsep as extsep, - isabs as isabs, - lexists as lexists, - pardir as pardir, - pathsep as pathsep, - sep as sep, - splitdrive as splitdrive, - splitext as splitext, - supports_unicode_filenames as supports_unicode_filenames, -) -from typing import AnyStr, overload - -__all__ = [ - "normcase", - "isabs", - "join", - "splitdrive", - "split", - "splitext", - "basename", - "dirname", - "commonprefix", - "getsize", - "getmtime", - "getatime", - "getctime", - "islink", - "exists", - "lexists", - "isdir", - "isfile", - "expanduser", - "expandvars", - "normpath", - "abspath", - "curdir", - "pardir", - "sep", - "pathsep", - "defpath", - "altsep", - "extsep", - "devnull", - "realpath", - "supports_unicode_filenames", -] - -altsep: str | None - -@overload -def basename(s: PathLike[AnyStr]) -> AnyStr: ... -@overload -def basename(s: AnyStr) -> AnyStr: ... -@overload -def dirname(s: PathLike[AnyStr]) -> AnyStr: ... -@overload -def dirname(s: AnyStr) -> AnyStr: ... -@overload -def normcase(path: PathLike[AnyStr]) -> AnyStr: ... -@overload -def normcase(path: AnyStr) -> AnyStr: ... -@overload -def normpath(s: PathLike[AnyStr]) -> AnyStr: ... -@overload -def normpath(s: AnyStr) -> AnyStr: ... -@overload -def realpath(path: PathLike[AnyStr]) -> AnyStr: ... -@overload -def realpath(path: AnyStr) -> AnyStr: ... -def islink(s: StrOrBytesPath) -> bool: ... - -# Mypy complains that the signatures overlap, but things seem to behave correctly anyway. -@overload -def join(s: StrPath, *paths: StrPath) -> str: ... -@overload -def join(s: BytesPath, *paths: BytesPath) -> bytes: ... -@overload -def split(s: PathLike[AnyStr]) -> tuple[AnyStr, AnyStr]: ... -@overload -def split(s: AnyStr) -> tuple[AnyStr, AnyStr]: ... diff --git a/mypy/typeshed/stdlib/mailbox.pyi b/mypy/typeshed/stdlib/mailbox.pyi index 8053fad88ea5..1059bfe917e8 100644 --- a/mypy/typeshed/stdlib/mailbox.pyi +++ b/mypy/typeshed/stdlib/mailbox.pyi @@ -5,8 +5,8 @@ from _typeshed import StrPath, SupportsNoArgReadline, SupportsRead from abc import ABCMeta, abstractmethod from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence from types import TracebackType -from typing import IO, Any, AnyStr, Generic, Protocol, TypeVar, overload -from typing_extensions import Literal, Self, TypeAlias +from typing import IO, Any, AnyStr, Generic, Literal, Protocol, TypeVar, overload +from typing_extensions import Self, TypeAlias if sys.version_info >= (3, 9): from types import GenericAlias diff --git a/mypy/typeshed/stdlib/mailcap.pyi b/mypy/typeshed/stdlib/mailcap.pyi index 5905f5826bf7..ce549e01f528 100644 --- a/mypy/typeshed/stdlib/mailcap.pyi +++ b/mypy/typeshed/stdlib/mailcap.pyi @@ -6,6 +6,6 @@ _Cap: TypeAlias = dict[str, str | int] __all__ = ["getcaps", "findmatch"] def findmatch( - caps: Mapping[str, list[_Cap]], MIMEtype: str, key: str = "view", filename: str = "/dev/null", plist: Sequence[str] = ... + caps: Mapping[str, list[_Cap]], MIMEtype: str, key: str = "view", filename: str = "/dev/null", plist: Sequence[str] = [] ) -> tuple[str | None, _Cap | None]: ... def getcaps() -> dict[str, list[_Cap]]: ... diff --git a/mypy/typeshed/stdlib/marshal.pyi b/mypy/typeshed/stdlib/marshal.pyi index 21f05c908479..69546344f5bf 100644 --- a/mypy/typeshed/stdlib/marshal.pyi +++ b/mypy/typeshed/stdlib/marshal.pyi @@ -27,7 +27,7 @@ _Marshallable: TypeAlias = ( | ReadableBuffer ) -def dump(__value: _Marshallable, __file: SupportsWrite[bytes], __version: int = 4) -> None: ... -def load(__file: SupportsRead[bytes]) -> Any: ... -def dumps(__value: _Marshallable, __version: int = 4) -> bytes: ... -def loads(__bytes: ReadableBuffer) -> Any: ... +def dump(value: _Marshallable, file: SupportsWrite[bytes], version: int = 4, /) -> None: ... +def load(file: SupportsRead[bytes], /) -> Any: ... +def dumps(value: _Marshallable, version: int = 4, /) -> bytes: ... +def loads(bytes: ReadableBuffer, /) -> Any: ... diff --git a/mypy/typeshed/stdlib/math.pyi b/mypy/typeshed/stdlib/math.pyi index 231964f397db..0c2fd4aba719 100644 --- a/mypy/typeshed/stdlib/math.pyi +++ b/mypy/typeshed/stdlib/math.pyi @@ -1,15 +1,12 @@ import sys from collections.abc import Iterable -from typing import Protocol, SupportsFloat, TypeVar, overload -from typing_extensions import SupportsIndex, TypeAlias +from typing import Protocol, SupportsFloat, SupportsIndex, TypeVar, overload +from typing_extensions import TypeAlias _T = TypeVar("_T") _T_co = TypeVar("_T_co", covariant=True) -if sys.version_info >= (3, 8): - _SupportsFloatOrIndex: TypeAlias = SupportsFloat | SupportsIndex -else: - _SupportsFloatOrIndex: TypeAlias = SupportsFloat +_SupportsFloatOrIndex: TypeAlias = SupportsFloat | SupportsIndex e: float pi: float @@ -17,76 +14,60 @@ inf: float nan: float tau: float -def acos(__x: _SupportsFloatOrIndex) -> float: ... -def acosh(__x: _SupportsFloatOrIndex) -> float: ... -def asin(__x: _SupportsFloatOrIndex) -> float: ... -def asinh(__x: _SupportsFloatOrIndex) -> float: ... -def atan(__x: _SupportsFloatOrIndex) -> float: ... -def atan2(__y: _SupportsFloatOrIndex, __x: _SupportsFloatOrIndex) -> float: ... -def atanh(__x: _SupportsFloatOrIndex) -> float: ... +def acos(x: _SupportsFloatOrIndex, /) -> float: ... +def acosh(x: _SupportsFloatOrIndex, /) -> float: ... +def asin(x: _SupportsFloatOrIndex, /) -> float: ... +def asinh(x: _SupportsFloatOrIndex, /) -> float: ... +def atan(x: _SupportsFloatOrIndex, /) -> float: ... +def atan2(y: _SupportsFloatOrIndex, x: _SupportsFloatOrIndex, /) -> float: ... +def atanh(x: _SupportsFloatOrIndex, /) -> float: ... if sys.version_info >= (3, 11): - def cbrt(__x: _SupportsFloatOrIndex) -> float: ... + def cbrt(x: _SupportsFloatOrIndex, /) -> float: ... class _SupportsCeil(Protocol[_T_co]): def __ceil__(self) -> _T_co: ... @overload -def ceil(__x: _SupportsCeil[_T]) -> _T: ... +def ceil(x: _SupportsCeil[_T], /) -> _T: ... @overload -def ceil(__x: _SupportsFloatOrIndex) -> int: ... - -if sys.version_info >= (3, 8): - def comb(__n: SupportsIndex, __k: SupportsIndex) -> int: ... - -def copysign(__x: _SupportsFloatOrIndex, __y: _SupportsFloatOrIndex) -> float: ... -def cos(__x: _SupportsFloatOrIndex) -> float: ... -def cosh(__x: _SupportsFloatOrIndex) -> float: ... -def degrees(__x: _SupportsFloatOrIndex) -> float: ... - -if sys.version_info >= (3, 8): - def dist(__p: Iterable[_SupportsFloatOrIndex], __q: Iterable[_SupportsFloatOrIndex]) -> float: ... - -def erf(__x: _SupportsFloatOrIndex) -> float: ... -def erfc(__x: _SupportsFloatOrIndex) -> float: ... -def exp(__x: _SupportsFloatOrIndex) -> float: ... +def ceil(x: _SupportsFloatOrIndex, /) -> int: ... +def comb(n: SupportsIndex, k: SupportsIndex, /) -> int: ... +def copysign(x: _SupportsFloatOrIndex, y: _SupportsFloatOrIndex, /) -> float: ... +def cos(x: _SupportsFloatOrIndex, /) -> float: ... +def cosh(x: _SupportsFloatOrIndex, /) -> float: ... +def degrees(x: _SupportsFloatOrIndex, /) -> float: ... +def dist(p: Iterable[_SupportsFloatOrIndex], q: Iterable[_SupportsFloatOrIndex], /) -> float: ... +def erf(x: _SupportsFloatOrIndex, /) -> float: ... +def erfc(x: _SupportsFloatOrIndex, /) -> float: ... +def exp(x: _SupportsFloatOrIndex, /) -> float: ... if sys.version_info >= (3, 11): - def exp2(__x: _SupportsFloatOrIndex) -> float: ... - -def expm1(__x: _SupportsFloatOrIndex) -> float: ... -def fabs(__x: _SupportsFloatOrIndex) -> float: ... + def exp2(x: _SupportsFloatOrIndex, /) -> float: ... -if sys.version_info >= (3, 8): - def factorial(__x: SupportsIndex) -> int: ... - -else: - def factorial(__x: int) -> int: ... +def expm1(x: _SupportsFloatOrIndex, /) -> float: ... +def fabs(x: _SupportsFloatOrIndex, /) -> float: ... +def factorial(x: SupportsIndex, /) -> int: ... class _SupportsFloor(Protocol[_T_co]): def __floor__(self) -> _T_co: ... @overload -def floor(__x: _SupportsFloor[_T]) -> _T: ... +def floor(x: _SupportsFloor[_T], /) -> _T: ... @overload -def floor(__x: _SupportsFloatOrIndex) -> int: ... -def fmod(__x: _SupportsFloatOrIndex, __y: _SupportsFloatOrIndex) -> float: ... -def frexp(__x: _SupportsFloatOrIndex) -> tuple[float, int]: ... -def fsum(__seq: Iterable[_SupportsFloatOrIndex]) -> float: ... -def gamma(__x: _SupportsFloatOrIndex) -> float: ... +def floor(x: _SupportsFloatOrIndex, /) -> int: ... +def fmod(x: _SupportsFloatOrIndex, y: _SupportsFloatOrIndex, /) -> float: ... +def frexp(x: _SupportsFloatOrIndex, /) -> tuple[float, int]: ... +def fsum(seq: Iterable[_SupportsFloatOrIndex], /) -> float: ... +def gamma(x: _SupportsFloatOrIndex, /) -> float: ... if sys.version_info >= (3, 9): def gcd(*integers: SupportsIndex) -> int: ... else: - def gcd(__x: SupportsIndex, __y: SupportsIndex) -> int: ... - -if sys.version_info >= (3, 8): - def hypot(*coordinates: _SupportsFloatOrIndex) -> float: ... - -else: - def hypot(__x: _SupportsFloatOrIndex, __y: _SupportsFloatOrIndex) -> float: ... + def gcd(x: SupportsIndex, y: SupportsIndex, /) -> int: ... +def hypot(*coordinates: _SupportsFloatOrIndex) -> float: ... def isclose( a: _SupportsFloatOrIndex, b: _SupportsFloatOrIndex, @@ -94,51 +75,51 @@ def isclose( rel_tol: _SupportsFloatOrIndex = 1e-09, abs_tol: _SupportsFloatOrIndex = 0.0, ) -> bool: ... -def isinf(__x: _SupportsFloatOrIndex) -> bool: ... -def isfinite(__x: _SupportsFloatOrIndex) -> bool: ... -def isnan(__x: _SupportsFloatOrIndex) -> bool: ... - -if sys.version_info >= (3, 8): - def isqrt(__n: SupportsIndex) -> int: ... +def isinf(x: _SupportsFloatOrIndex, /) -> bool: ... +def isfinite(x: _SupportsFloatOrIndex, /) -> bool: ... +def isnan(x: _SupportsFloatOrIndex, /) -> bool: ... +def isqrt(n: SupportsIndex, /) -> int: ... if sys.version_info >= (3, 9): def lcm(*integers: SupportsIndex) -> int: ... -def ldexp(__x: _SupportsFloatOrIndex, __i: int) -> float: ... -def lgamma(__x: _SupportsFloatOrIndex) -> float: ... +def ldexp(x: _SupportsFloatOrIndex, i: int, /) -> float: ... +def lgamma(x: _SupportsFloatOrIndex, /) -> float: ... def log(x: _SupportsFloatOrIndex, base: _SupportsFloatOrIndex = ...) -> float: ... -def log10(__x: _SupportsFloatOrIndex) -> float: ... -def log1p(__x: _SupportsFloatOrIndex) -> float: ... -def log2(__x: _SupportsFloatOrIndex) -> float: ... -def modf(__x: _SupportsFloatOrIndex) -> tuple[float, float]: ... +def log10(x: _SupportsFloatOrIndex, /) -> float: ... +def log1p(x: _SupportsFloatOrIndex, /) -> float: ... +def log2(x: _SupportsFloatOrIndex, /) -> float: ... +def modf(x: _SupportsFloatOrIndex, /) -> tuple[float, float]: ... -if sys.version_info >= (3, 9): - def nextafter(__x: _SupportsFloatOrIndex, __y: _SupportsFloatOrIndex) -> float: ... +if sys.version_info >= (3, 12): + def nextafter(x: _SupportsFloatOrIndex, y: _SupportsFloatOrIndex, /, *, steps: SupportsIndex | None = None) -> float: ... -if sys.version_info >= (3, 8): - def perm(__n: SupportsIndex, __k: SupportsIndex | None = None) -> int: ... +elif sys.version_info >= (3, 9): + def nextafter(x: _SupportsFloatOrIndex, y: _SupportsFloatOrIndex, /) -> float: ... -def pow(__x: _SupportsFloatOrIndex, __y: _SupportsFloatOrIndex) -> float: ... +def perm(n: SupportsIndex, k: SupportsIndex | None = None, /) -> int: ... +def pow(x: _SupportsFloatOrIndex, y: _SupportsFloatOrIndex, /) -> float: ... +@overload +def prod(iterable: Iterable[SupportsIndex], /, *, start: SupportsIndex = 1) -> int: ... # type: ignore[overload-overlap] +@overload +def prod(iterable: Iterable[_SupportsFloatOrIndex], /, *, start: _SupportsFloatOrIndex = 1) -> float: ... +def radians(x: _SupportsFloatOrIndex, /) -> float: ... +def remainder(x: _SupportsFloatOrIndex, y: _SupportsFloatOrIndex, /) -> float: ... +def sin(x: _SupportsFloatOrIndex, /) -> float: ... +def sinh(x: _SupportsFloatOrIndex, /) -> float: ... -if sys.version_info >= (3, 8): - @overload - def prod(__iterable: Iterable[SupportsIndex], *, start: SupportsIndex = 1) -> int: ... # type: ignore[misc] - @overload - def prod(__iterable: Iterable[_SupportsFloatOrIndex], *, start: _SupportsFloatOrIndex = 1) -> float: ... +if sys.version_info >= (3, 12): + def sumprod(p: Iterable[float], q: Iterable[float], /) -> float: ... -def radians(__x: _SupportsFloatOrIndex) -> float: ... -def remainder(__x: _SupportsFloatOrIndex, __y: _SupportsFloatOrIndex) -> float: ... -def sin(__x: _SupportsFloatOrIndex) -> float: ... -def sinh(__x: _SupportsFloatOrIndex) -> float: ... -def sqrt(__x: _SupportsFloatOrIndex) -> float: ... -def tan(__x: _SupportsFloatOrIndex) -> float: ... -def tanh(__x: _SupportsFloatOrIndex) -> float: ... +def sqrt(x: _SupportsFloatOrIndex, /) -> float: ... +def tan(x: _SupportsFloatOrIndex, /) -> float: ... +def tanh(x: _SupportsFloatOrIndex, /) -> float: ... # Is different from `_typeshed.SupportsTrunc`, which is not generic class _SupportsTrunc(Protocol[_T_co]): def __trunc__(self) -> _T_co: ... -def trunc(__x: _SupportsTrunc[_T]) -> _T: ... +def trunc(x: _SupportsTrunc[_T], /) -> _T: ... if sys.version_info >= (3, 9): - def ulp(__x: _SupportsFloatOrIndex) -> float: ... + def ulp(x: _SupportsFloatOrIndex, /) -> float: ... diff --git a/mypy/typeshed/stdlib/mimetypes.pyi b/mypy/typeshed/stdlib/mimetypes.pyi index fd3908680009..e74b214d3ff1 100644 --- a/mypy/typeshed/stdlib/mimetypes.pyi +++ b/mypy/typeshed/stdlib/mimetypes.pyi @@ -1,4 +1,3 @@ -import sys from _typeshed import StrPath from collections.abc import Sequence from typing import IO @@ -19,12 +18,7 @@ __all__ = [ "common_types", ] -if sys.version_info >= (3, 8): - def guess_type(url: StrPath, strict: bool = True) -> tuple[str | None, str | None]: ... - -else: - def guess_type(url: str, strict: bool = True) -> tuple[str | None, str | None]: ... - +def guess_type(url: StrPath, strict: bool = True) -> tuple[str | None, str | None]: ... def guess_all_extensions(type: str, strict: bool = True) -> list[str]: ... def guess_extension(type: str, strict: bool = True) -> str | None: ... def init(files: Sequence[str] | None = None) -> None: ... @@ -43,15 +37,10 @@ class MimeTypes: encodings_map: dict[str, str] types_map: tuple[dict[str, str], dict[str, str]] types_map_inv: tuple[dict[str, str], dict[str, str]] - def __init__(self, filenames: tuple[str, ...] = ..., strict: bool = True) -> None: ... + def __init__(self, filenames: tuple[str, ...] = (), strict: bool = True) -> None: ... def guess_extension(self, type: str, strict: bool = True) -> str | None: ... - if sys.version_info >= (3, 8): - def guess_type(self, url: StrPath, strict: bool = True) -> tuple[str | None, str | None]: ... - else: - def guess_type(self, url: str, strict: bool = True) -> tuple[str | None, str | None]: ... - + def guess_type(self, url: StrPath, strict: bool = True) -> tuple[str | None, str | None]: ... def guess_all_extensions(self, type: str, strict: bool = True) -> list[str]: ... def read(self, filename: str, strict: bool = True) -> None: ... def readfp(self, fp: IO[str], strict: bool = True) -> None: ... - if sys.platform == "win32": - def read_windows_registry(self, strict: bool = True) -> None: ... + def read_windows_registry(self, strict: bool = True) -> None: ... diff --git a/mypy/typeshed/stdlib/mmap.pyi b/mypy/typeshed/stdlib/mmap.pyi index c74ad3cda6db..93c4f408e5b6 100644 --- a/mypy/typeshed/stdlib/mmap.pyi +++ b/mypy/typeshed/stdlib/mmap.pyi @@ -16,6 +16,8 @@ if sys.platform == "linux": MAP_EXECUTABLE: int if sys.version_info >= (3, 10): MAP_POPULATE: int +if sys.version_info >= (3, 11) and sys.platform != "win32" and sys.platform != "darwin": + MAP_STACK: int if sys.platform != "win32": MAP_ANON: int @@ -26,7 +28,7 @@ if sys.platform != "win32": PROT_READ: int PROT_WRITE: int - PAGESIZE: int +PAGESIZE: int class mmap(Iterable[int], Sized): if sys.platform == "win32": @@ -37,11 +39,7 @@ class mmap(Iterable[int], Sized): ) -> None: ... def close(self) -> None: ... - if sys.version_info >= (3, 8): - def flush(self, offset: int = ..., size: int = ...) -> None: ... - else: - def flush(self, offset: int = ..., size: int = ...) -> int: ... - + def flush(self, offset: int = ..., size: int = ...) -> None: ... def move(self, dest: int, src: int, count: int) -> None: ... def read_byte(self) -> int: ... def readline(self) -> bytes: ... @@ -52,7 +50,7 @@ class mmap(Iterable[int], Sized): def write_byte(self, byte: int) -> None: ... def __len__(self) -> int: ... closed: bool - if sys.version_info >= (3, 8) and sys.platform != "win32": + if sys.platform != "win32": def madvise(self, option: int, start: int = ..., length: int = ...) -> None: ... def find(self, sub: ReadableBuffer, start: int = ..., stop: int = ...) -> int: ... @@ -60,24 +58,26 @@ class mmap(Iterable[int], Sized): def read(self, n: int | None = ...) -> bytes: ... def write(self, bytes: ReadableBuffer) -> int: ... @overload - def __getitem__(self, __index: int) -> int: ... + def __getitem__(self, key: int, /) -> int: ... @overload - def __getitem__(self, __index: slice) -> bytes: ... - def __delitem__(self, __index: int | slice) -> NoReturn: ... + def __getitem__(self, key: slice, /) -> bytes: ... + def __delitem__(self, key: int | slice, /) -> NoReturn: ... @overload - def __setitem__(self, __index: int, __object: int) -> None: ... + def __setitem__(self, key: int, value: int, /) -> None: ... @overload - def __setitem__(self, __index: slice, __object: ReadableBuffer) -> None: ... + def __setitem__(self, key: slice, value: ReadableBuffer, /) -> None: ... # Doesn't actually exist, but the object actually supports "in" because it has __getitem__, # so we claim that there is also a __contains__ to help type checkers. - def __contains__(self, __o: object) -> bool: ... + def __contains__(self, o: object, /) -> bool: ... # Doesn't actually exist, but the object is actually iterable because it has __getitem__ and __len__, # so we claim that there is also an __iter__ to help type checkers. def __iter__(self) -> Iterator[int]: ... def __enter__(self) -> Self: ... def __exit__(self, *args: Unused) -> None: ... + def __buffer__(self, flags: int, /) -> memoryview: ... + def __release_buffer__(self, buffer: memoryview, /) -> None: ... -if sys.version_info >= (3, 8) and sys.platform != "win32": +if sys.platform != "win32": MADV_NORMAL: int MADV_RANDOM: int MADV_SEQUENTIAL: int @@ -85,28 +85,28 @@ if sys.version_info >= (3, 8) and sys.platform != "win32": MADV_DONTNEED: int MADV_FREE: int - if sys.platform == "linux": - MADV_REMOVE: int - MADV_DONTFORK: int - MADV_DOFORK: int - MADV_HWPOISON: int - MADV_MERGEABLE: int - MADV_UNMERGEABLE: int - # Seems like this constant is not defined in glibc. - # See https://github.com/python/typeshed/pull/5360 for details - # MADV_SOFT_OFFLINE: int - MADV_HUGEPAGE: int - MADV_NOHUGEPAGE: int - MADV_DONTDUMP: int - MADV_DODUMP: int +if sys.platform == "linux": + MADV_REMOVE: int + MADV_DONTFORK: int + MADV_DOFORK: int + MADV_HWPOISON: int + MADV_MERGEABLE: int + MADV_UNMERGEABLE: int + # Seems like this constant is not defined in glibc. + # See https://github.com/python/typeshed/pull/5360 for details + # MADV_SOFT_OFFLINE: int + MADV_HUGEPAGE: int + MADV_NOHUGEPAGE: int + MADV_DONTDUMP: int + MADV_DODUMP: int - # This Values are defined for FreeBSD but type checkers do not support conditions for these - if sys.platform != "linux" and sys.platform != "darwin": - MADV_NOSYNC: int - MADV_AUTOSYNC: int - MADV_NOCORE: int - MADV_CORE: int - MADV_PROTECT: int +# This Values are defined for FreeBSD but type checkers do not support conditions for these +if sys.platform != "linux" and sys.platform != "darwin" and sys.platform != "win32": + MADV_NOSYNC: int + MADV_AUTOSYNC: int + MADV_NOCORE: int + MADV_CORE: int + MADV_PROTECT: int if sys.version_info >= (3, 10) and sys.platform == "darwin": MADV_FREE_REUSABLE: int diff --git a/mypy/typeshed/stdlib/modulefinder.pyi b/mypy/typeshed/stdlib/modulefinder.pyi index 6f1917644b06..132cac5f1878 100644 --- a/mypy/typeshed/stdlib/modulefinder.pyi +++ b/mypy/typeshed/stdlib/modulefinder.pyi @@ -31,23 +31,13 @@ class ModuleFinder: excludes: Container[str] # undocumented replace_paths: Sequence[tuple[str, str]] # undocumented - if sys.version_info >= (3, 8): - def __init__( - self, - path: list[str] | None = None, - debug: int = 0, - excludes: Container[str] | None = None, - replace_paths: Sequence[tuple[str, str]] | None = None, - ) -> None: ... - else: - def __init__( - self, - path: list[str] | None = None, - debug: int = 0, - excludes: Container[str] = ..., - replace_paths: Sequence[tuple[str, str]] = ..., - ) -> None: ... - + def __init__( + self, + path: list[str] | None = None, + debug: int = 0, + excludes: Container[str] | None = None, + replace_paths: Sequence[tuple[str, str]] | None = None, + ) -> None: ... def msg(self, level: int, str: str, *args: Any) -> None: ... # undocumented def msgin(self, *args: Any) -> None: ... # undocumented def msgout(self, *args: Any) -> None: ... # undocumented diff --git a/mypy/typeshed/stdlib/msilib/__init__.pyi b/mypy/typeshed/stdlib/msilib/__init__.pyi index 9f7367d152ba..3e43cbc44f52 100644 --- a/mypy/typeshed/stdlib/msilib/__init__.pyi +++ b/mypy/typeshed/stdlib/msilib/__init__.pyi @@ -1,8 +1,7 @@ import sys from collections.abc import Container, Iterable, Sequence from types import ModuleType -from typing import Any -from typing_extensions import Literal +from typing import Any, Literal if sys.platform == "win32": from _msi import * @@ -57,6 +56,7 @@ if sys.platform == "win32": def gen_id(self, file: str) -> str: ... def append(self, full: str, file: str, logical: str) -> tuple[int, str]: ... def commit(self, db: _Database) -> None: ... + _directories: set[str] class Directory: diff --git a/mypy/typeshed/stdlib/msilib/text.pyi b/mypy/typeshed/stdlib/msilib/text.pyi index 1353cf8a2392..441c843ca6cf 100644 --- a/mypy/typeshed/stdlib/msilib/text.pyi +++ b/mypy/typeshed/stdlib/msilib/text.pyi @@ -3,5 +3,5 @@ import sys if sys.platform == "win32": ActionText: list[tuple[str, str, str | None]] UIText: list[tuple[str, str | None]] - + dirname: str tables: list[str] diff --git a/mypy/typeshed/stdlib/msvcrt.pyi b/mypy/typeshed/stdlib/msvcrt.pyi index 5849b9b00ca0..54b3674a3a46 100644 --- a/mypy/typeshed/stdlib/msvcrt.pyi +++ b/mypy/typeshed/stdlib/msvcrt.pyi @@ -1,8 +1,9 @@ import sys -from typing_extensions import Literal +from typing import Final, Literal # This module is only available on Windows if sys.platform == "win32": + CRT_ASSEMBLY_VERSION: Final[str] LK_UNLCK: Literal[0] LK_LOCK: Literal[1] LK_NBLCK: Literal[2] @@ -12,17 +13,20 @@ if sys.platform == "win32": SEM_NOALIGNMENTFAULTEXCEPT: int SEM_NOGPFAULTERRORBOX: int SEM_NOOPENFILEERRORBOX: int - def locking(__fd: int, __mode: int, __nbytes: int) -> None: ... - def setmode(__fd: int, __mode: int) -> int: ... - def open_osfhandle(__handle: int, __flags: int) -> int: ... - def get_osfhandle(__fd: int) -> int: ... + def locking(fd: int, mode: int, nbytes: int, /) -> None: ... + def setmode(fd: int, mode: int, /) -> int: ... + def open_osfhandle(handle: int, flags: int, /) -> int: ... + def get_osfhandle(fd: int, /) -> int: ... def kbhit() -> bool: ... def getch() -> bytes: ... def getwch() -> str: ... def getche() -> bytes: ... def getwche() -> str: ... - def putch(__char: bytes | bytearray) -> None: ... - def putwch(__unicode_char: str) -> None: ... - def ungetch(__char: bytes | bytearray) -> None: ... - def ungetwch(__unicode_char: str) -> None: ... + def putch(char: bytes | bytearray, /) -> None: ... + def putwch(unicode_char: str, /) -> None: ... + def ungetch(char: bytes | bytearray, /) -> None: ... + def ungetwch(unicode_char: str, /) -> None: ... def heapmin() -> None: ... + def SetErrorMode(mode: int, /) -> int: ... + if sys.version_info >= (3, 10): + def GetErrorMode() -> int: ... # undocumented diff --git a/mypy/typeshed/stdlib/multiprocessing/__init__.pyi b/mypy/typeshed/stdlib/multiprocessing/__init__.pyi index 186bd54a021d..2bd6e2883ddb 100644 --- a/mypy/typeshed/stdlib/multiprocessing/__init__.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/__init__.pyi @@ -1,4 +1,3 @@ -import sys from multiprocessing import context, reduction as reducer from multiprocessing.context import ( AuthenticationError as AuthenticationError, @@ -7,7 +6,11 @@ from multiprocessing.context import ( ProcessError as ProcessError, TimeoutError as TimeoutError, ) -from multiprocessing.process import active_children as active_children, current_process as current_process +from multiprocessing.process import ( + active_children as active_children, + current_process as current_process, + parent_process as parent_process, +) # These are technically functions that return instances of these Queue classes. # The stub here doesn't reflect reality exactly -- @@ -19,9 +22,6 @@ from multiprocessing.process import active_children as active_children, current_ from multiprocessing.queues import JoinableQueue as JoinableQueue, Queue as Queue, SimpleQueue as SimpleQueue from multiprocessing.spawn import freeze_support as freeze_support -if sys.version_info >= (3, 8): - from multiprocessing.process import parent_process as parent_process - __all__ = [ "Array", "AuthenticationError", @@ -55,15 +55,13 @@ __all__ = [ "get_logger", "get_start_method", "log_to_stderr", + "parent_process", "reducer", "set_executable", "set_forkserver_preload", "set_start_method", ] -if sys.version_info >= (3, 8): - __all__ += ["parent_process"] - # These functions (really bound methods) # are all autogenerated at runtime here: https://github.com/python/cpython/blob/600c65c094b0b48704d8ec2416930648052ba715/Lib/multiprocessing/__init__.py#L23 RawValue = context._default_context.RawValue diff --git a/mypy/typeshed/stdlib/multiprocessing/connection.pyi b/mypy/typeshed/stdlib/multiprocessing/connection.pyi index d034373712e0..7045a81b85be 100644 --- a/mypy/typeshed/stdlib/multiprocessing/connection.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/connection.pyi @@ -3,8 +3,8 @@ import sys import types from _typeshed import ReadableBuffer from collections.abc import Iterable -from typing import Any -from typing_extensions import Self, SupportsIndex, TypeAlias +from typing import Any, SupportsIndex +from typing_extensions import Self, TypeAlias __all__ = ["Client", "Listener", "Pipe", "wait"] @@ -31,6 +31,7 @@ class _ConnectionBase: def __exit__( self, exc_type: type[BaseException] | None, exc_value: BaseException | None, exc_tb: types.TracebackType | None ) -> None: ... + def __del__(self) -> None: ... class Connection(_ConnectionBase): ... @@ -52,7 +53,12 @@ class Listener: self, exc_type: type[BaseException] | None, exc_value: BaseException | None, exc_tb: types.TracebackType | None ) -> None: ... -def deliver_challenge(connection: Connection, authkey: bytes) -> None: ... +if sys.version_info >= (3, 12): + def deliver_challenge(connection: Connection, authkey: bytes, digest_name: str = "sha256") -> None: ... + +else: + def deliver_challenge(connection: Connection, authkey: bytes) -> None: ... + def answer_challenge(connection: Connection, authkey: bytes) -> None: ... def wait( object_list: Iterable[Connection | socket.socket | int], timeout: float | None = None diff --git a/mypy/typeshed/stdlib/multiprocessing/context.pyi b/mypy/typeshed/stdlib/multiprocessing/context.pyi index c498649a7b61..9a45a81559c0 100644 --- a/mypy/typeshed/stdlib/multiprocessing/context.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/context.pyi @@ -1,27 +1,25 @@ import ctypes import sys from collections.abc import Callable, Iterable, Sequence -from ctypes import _CData +from ctypes import _CData, _SimpleCData, c_char from logging import Logger, _Level as _LoggingLevel from multiprocessing import popen_fork, popen_forkserver, popen_spawn_posix, popen_spawn_win32, queues, synchronize from multiprocessing.managers import SyncManager from multiprocessing.pool import Pool as _Pool from multiprocessing.process import BaseProcess -from multiprocessing.sharedctypes import SynchronizedArray, SynchronizedBase -from typing import Any, ClassVar, TypeVar, overload -from typing_extensions import Literal, TypeAlias +from multiprocessing.sharedctypes import Synchronized, SynchronizedArray, SynchronizedString +from typing import Any, ClassVar, Literal, TypeVar, overload +from typing_extensions import TypeAlias if sys.platform != "win32": from multiprocessing.connection import Connection else: from multiprocessing.connection import PipeConnection -if sys.version_info >= (3, 8): - __all__ = () -else: - __all__: list[str] = [] +__all__ = () _LockLike: TypeAlias = synchronize.Lock | synchronize.RLock +_T = TypeVar("_T") _CT = TypeVar("_CT", bound=_CData) class ProcessError(Exception): ... @@ -39,10 +37,8 @@ class BaseContext: # multiprocessing.*, so the signatures should be identical (modulo self). @staticmethod def current_process() -> BaseProcess: ... - if sys.version_info >= (3, 8): - @staticmethod - def parent_process() -> BaseProcess | None: ... - + @staticmethod + def parent_process() -> BaseProcess | None: ... @staticmethod def active_children() -> list[BaseProcess]: ... def cpu_count(self) -> int: ... @@ -72,7 +68,7 @@ class BaseContext: self, processes: int | None = None, initializer: Callable[..., object] | None = None, - initargs: Iterable[Any] = ..., + initargs: Iterable[Any] = (), maxtasksperchild: int | None = None, ) -> _Pool: ... @overload @@ -84,15 +80,25 @@ class BaseContext: @overload def RawArray(self, typecode_or_type: str, size_or_initializer: int | Sequence[Any]) -> Any: ... @overload - def Value(self, typecode_or_type: type[_CT], *args: Any, lock: Literal[False]) -> _CT: ... + def Value( + self, typecode_or_type: type[_SimpleCData[_T]], *args: Any, lock: Literal[True] | _LockLike = True + ) -> Synchronized[_T]: ... + @overload + def Value(self, typecode_or_type: type[_CT], *args: Any, lock: Literal[False]) -> Synchronized[_CT]: ... @overload - def Value(self, typecode_or_type: type[_CT], *args: Any, lock: Literal[True] | _LockLike = True) -> SynchronizedBase[_CT]: ... + def Value(self, typecode_or_type: type[_CT], *args: Any, lock: Literal[True] | _LockLike = True) -> Synchronized[_CT]: ... @overload - def Value(self, typecode_or_type: str, *args: Any, lock: Literal[True] | _LockLike = True) -> SynchronizedBase[Any]: ... + def Value(self, typecode_or_type: str, *args: Any, lock: Literal[True] | _LockLike = True) -> Synchronized[Any]: ... @overload def Value(self, typecode_or_type: str | type[_CData], *args: Any, lock: bool | _LockLike = True) -> Any: ... @overload - def Array(self, typecode_or_type: type[_CT], size_or_initializer: int | Sequence[Any], *, lock: Literal[False]) -> _CT: ... + def Array( + self, typecode_or_type: type[c_char], size_or_initializer: int | Sequence[Any], *, lock: Literal[True] | _LockLike = True + ) -> SynchronizedString: ... + @overload + def Array( + self, typecode_or_type: type[_CT], size_or_initializer: int | Sequence[Any], *, lock: Literal[False] + ) -> SynchronizedArray[_CT]: ... @overload def Array( self, typecode_or_type: type[_CT], size_or_initializer: int | Sequence[Any], *, lock: Literal[True] | _LockLike = True @@ -151,8 +157,6 @@ class DefaultContext(BaseContext): def __init__(self, context: BaseContext) -> None: ... def get_start_method(self, allow_none: bool = False) -> str: ... def get_all_start_methods(self) -> list[str]: ... - if sys.version_info < (3, 8): - __all__: ClassVar[list[str]] _default_context: DefaultContext diff --git a/mypy/typeshed/stdlib/multiprocessing/dummy/__init__.pyi b/mypy/typeshed/stdlib/multiprocessing/dummy/__init__.pyi index 5b2a33772de6..3cbeeb057791 100644 --- a/mypy/typeshed/stdlib/multiprocessing/dummy/__init__.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/dummy/__init__.pyi @@ -12,8 +12,7 @@ from threading import ( RLock as RLock, Semaphore as Semaphore, ) -from typing import Any -from typing_extensions import Literal +from typing import Any, Literal from .connection import Pipe as Pipe @@ -50,16 +49,16 @@ class DummyProcess(threading.Thread): group: Any = None, target: Callable[..., object] | None = None, name: str | None = None, - args: Iterable[Any] = ..., - kwargs: Mapping[str, Any] = ..., + args: Iterable[Any] = (), + kwargs: Mapping[str, Any] = {}, ) -> None: ... Process = DummyProcess class Namespace: def __init__(self, **kwds: Any) -> None: ... - def __getattr__(self, __name: str) -> Any: ... - def __setattr__(self, __name: str, __value: Any) -> None: ... + def __getattr__(self, name: str, /) -> Any: ... + def __setattr__(self, name: str, value: Any, /) -> None: ... class Value: _typecode: Any @@ -69,9 +68,7 @@ class Value: def Array(typecode: Any, sequence: Sequence[Any], lock: Any = True) -> array.array[Any]: ... def Manager() -> Any: ... -def Pool( - processes: int | None = None, initializer: Callable[..., object] | None = None, initargs: Iterable[Any] = ... -) -> Any: ... +def Pool(processes: int | None = None, initializer: Callable[..., object] | None = None, initargs: Iterable[Any] = ()) -> Any: ... def active_children() -> list[Any]: ... current_process = threading.current_thread diff --git a/mypy/typeshed/stdlib/multiprocessing/dummy/connection.pyi b/mypy/typeshed/stdlib/multiprocessing/dummy/connection.pyi index fcd03a657319..d7e982129466 100644 --- a/mypy/typeshed/stdlib/multiprocessing/dummy/connection.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/dummy/connection.pyi @@ -1,14 +1,13 @@ +from multiprocessing.connection import _Address from queue import Queue from types import TracebackType from typing import Any -from typing_extensions import Self, TypeAlias +from typing_extensions import Self __all__ = ["Client", "Listener", "Pipe"] families: list[None] -_Address: TypeAlias = str | tuple[str, int] - class Connection: _in: Any _out: Any diff --git a/mypy/typeshed/stdlib/multiprocessing/forkserver.pyi b/mypy/typeshed/stdlib/multiprocessing/forkserver.pyi index df435f00ebe7..9a15f2683b7d 100644 --- a/mypy/typeshed/stdlib/multiprocessing/forkserver.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/forkserver.pyi @@ -24,7 +24,7 @@ def main( def read_signed(fd: int) -> Any: ... def write_signed(fd: int, n: int) -> None: ... -_forkserver: ForkServer = ... +_forkserver: ForkServer ensure_running = _forkserver.ensure_running get_inherited_fds = _forkserver.get_inherited_fds connect_to_new_process = _forkserver.connect_to_new_process diff --git a/mypy/typeshed/stdlib/multiprocessing/managers.pyi b/mypy/typeshed/stdlib/multiprocessing/managers.pyi index e035a1875650..02b5c4bc8c67 100644 --- a/mypy/typeshed/stdlib/multiprocessing/managers.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/managers.pyi @@ -4,19 +4,14 @@ import threading from _typeshed import SupportsKeysAndGetItem, SupportsRichComparison, SupportsRichComparisonT from collections.abc import Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Sequence from types import TracebackType -from typing import Any, AnyStr, ClassVar, Generic, TypeVar, overload -from typing_extensions import Self, SupportsIndex, TypeAlias +from typing import Any, AnyStr, ClassVar, Generic, SupportsIndex, TypeVar, overload +from typing_extensions import Self, TypeAlias from .connection import Connection from .context import BaseContext +from .shared_memory import _SLT, ShareableList as _ShareableList, SharedMemory as _SharedMemory -if sys.version_info >= (3, 8): - from .shared_memory import _SLT, ShareableList as _ShareableList, SharedMemory as _SharedMemory - - __all__ = ["BaseManager", "SyncManager", "BaseProxy", "Token", "SharedMemoryManager"] - -else: - __all__ = ["BaseManager", "SyncManager", "BaseProxy", "Token"] +__all__ = ["BaseManager", "SyncManager", "BaseProxy", "Token", "SharedMemoryManager"] if sys.version_info >= (3, 9): from types import GenericAlias @@ -27,8 +22,8 @@ _VT = TypeVar("_VT") class Namespace: def __init__(self, **kwds: Any) -> None: ... - def __getattr__(self, __name: str) -> Any: ... - def __setattr__(self, __name: str, __value: Any) -> None: ... + def __getattr__(self, name: str, /) -> Any: ... + def __setattr__(self, name: str, value: Any, /) -> None: ... _Namespace: TypeAlias = Namespace @@ -54,7 +49,7 @@ class BaseProxy: manager_owned: bool = False, ) -> None: ... def __deepcopy__(self, memo: Any | None) -> Any: ... - def _callmethod(self, methodname: str, args: tuple[Any, ...] = ..., kwds: dict[Any, Any] = ...) -> None: ... + def _callmethod(self, methodname: str, args: tuple[Any, ...] = (), kwds: dict[Any, Any] = {}) -> None: ... def _getvalue(self) -> Any: ... def __reduce__(self) -> tuple[Any, tuple[Any, Any, str, dict[Any, Any]]]: ... @@ -68,19 +63,23 @@ class ValueProxy(BaseProxy, Generic[_T]): class DictProxy(BaseProxy, MutableMapping[_KT, _VT]): __builtins__: ClassVar[dict[str, Any]] def __len__(self) -> int: ... - def __getitem__(self, __key: _KT) -> _VT: ... - def __setitem__(self, __key: _KT, __value: _VT) -> None: ... - def __delitem__(self, __key: _KT) -> None: ... + def __getitem__(self, key: _KT, /) -> _VT: ... + def __setitem__(self, key: _KT, value: _VT, /) -> None: ... + def __delitem__(self, key: _KT, /) -> None: ... def __iter__(self) -> Iterator[_KT]: ... def copy(self) -> dict[_KT, _VT]: ... + @overload # type: ignore[override] + def get(self, key: _KT, /) -> _VT | None: ... + @overload + def get(self, key: _KT, default: _VT, /) -> _VT: ... @overload - def get(self, __key: _KT) -> _VT | None: ... + def get(self, key: _KT, default: _T, /) -> _VT | _T: ... @overload - def get(self, __key: _KT, __default: _VT | _T) -> _VT | _T: ... + def pop(self, key: _KT, /) -> _VT: ... @overload - def pop(self, __key: _KT) -> _VT: ... + def pop(self, key: _KT, default: _VT, /) -> _VT: ... @overload - def pop(self, __key: _KT, __default: _VT | _T) -> _VT | _T: ... + def pop(self, key: _KT, default: _T, /) -> _VT | _T: ... def keys(self) -> list[_KT]: ... # type: ignore[override] def items(self) -> list[tuple[_KT, _VT]]: ... # type: ignore[override] def values(self) -> list[_VT]: ... # type: ignore[override] @@ -88,26 +87,26 @@ class DictProxy(BaseProxy, MutableMapping[_KT, _VT]): class BaseListProxy(BaseProxy, MutableSequence[_T]): __builtins__: ClassVar[dict[str, Any]] def __len__(self) -> int: ... - def __add__(self, __x: list[_T]) -> list[_T]: ... - def __delitem__(self, __i: SupportsIndex | slice) -> None: ... + def __add__(self, x: list[_T], /) -> list[_T]: ... + def __delitem__(self, i: SupportsIndex | slice, /) -> None: ... @overload - def __getitem__(self, __i: SupportsIndex) -> _T: ... + def __getitem__(self, i: SupportsIndex, /) -> _T: ... @overload - def __getitem__(self, __s: slice) -> list[_T]: ... + def __getitem__(self, s: slice, /) -> list[_T]: ... @overload - def __setitem__(self, __i: SupportsIndex, __o: _T) -> None: ... + def __setitem__(self, i: SupportsIndex, o: _T, /) -> None: ... @overload - def __setitem__(self, __s: slice, __o: Iterable[_T]) -> None: ... - def __mul__(self, __n: SupportsIndex) -> list[_T]: ... - def __rmul__(self, __n: SupportsIndex) -> list[_T]: ... + def __setitem__(self, s: slice, o: Iterable[_T], /) -> None: ... + def __mul__(self, n: SupportsIndex, /) -> list[_T]: ... + def __rmul__(self, n: SupportsIndex, /) -> list[_T]: ... def __reversed__(self) -> Iterator[_T]: ... - def append(self, __object: _T) -> None: ... - def extend(self, __iterable: Iterable[_T]) -> None: ... - def pop(self, __index: SupportsIndex = ...) -> _T: ... - def index(self, __value: _T, __start: SupportsIndex = ..., __stop: SupportsIndex = ...) -> int: ... - def count(self, __value: _T) -> int: ... - def insert(self, __index: SupportsIndex, __object: _T) -> None: ... - def remove(self, __value: _T) -> None: ... + def append(self, object: _T, /) -> None: ... + def extend(self, iterable: Iterable[_T], /) -> None: ... + def pop(self, index: SupportsIndex = ..., /) -> _T: ... + def index(self, value: _T, start: SupportsIndex = ..., stop: SupportsIndex = ..., /) -> int: ... + def count(self, value: _T, /) -> int: ... + def insert(self, index: SupportsIndex, object: _T, /) -> None: ... + def remove(self, value: _T, /) -> None: ... # Use BaseListProxy[SupportsRichComparisonT] for the first overload rather than [SupportsRichComparison] # to work around invariance @overload @@ -116,8 +115,8 @@ class BaseListProxy(BaseProxy, MutableSequence[_T]): def sort(self, *, key: Callable[[_T], SupportsRichComparison], reverse: bool = ...) -> None: ... class ListProxy(BaseListProxy[_T]): - def __iadd__(self, __x: Iterable[_T]) -> Self: ... # type: ignore[override] - def __imul__(self, __n: SupportsIndex) -> Self: ... # type: ignore[override] + def __iadd__(self, value: Iterable[_T], /) -> Self: ... # type: ignore[override] + def __imul__(self, value: SupportsIndex, /) -> Self: ... # type: ignore[override] # Returned by BaseManager.get_server() class Server: @@ -150,7 +149,7 @@ class BaseManager: def get_server(self) -> Server: ... def connect(self) -> None: ... - def start(self, initializer: Callable[..., object] | None = None, initargs: Iterable[Any] = ...) -> None: ... + def start(self, initializer: Callable[..., object] | None = None, initargs: Iterable[Any] = ()) -> None: ... def shutdown(self) -> None: ... # only available after start() was called def join(self, timeout: float | None = None) -> None: ... # undocumented @property @@ -187,26 +186,27 @@ class SyncManager(BaseManager): @overload def dict(self, **kwargs: _VT) -> DictProxy[str, _VT]: ... @overload - def dict(self, __map: SupportsKeysAndGetItem[_KT, _VT]) -> DictProxy[_KT, _VT]: ... + def dict(self, map: SupportsKeysAndGetItem[_KT, _VT], /) -> DictProxy[_KT, _VT]: ... @overload - def dict(self, __map: SupportsKeysAndGetItem[str, _VT], **kwargs: _VT) -> DictProxy[str, _VT]: ... + def dict(self, map: SupportsKeysAndGetItem[str, _VT], /, **kwargs: _VT) -> DictProxy[str, _VT]: ... @overload - def dict(self, __iterable: Iterable[tuple[_KT, _VT]]) -> DictProxy[_KT, _VT]: ... + def dict(self, iterable: Iterable[tuple[_KT, _VT]], /) -> DictProxy[_KT, _VT]: ... @overload - def dict(self, __iterable: Iterable[tuple[str, _VT]], **kwargs: _VT) -> DictProxy[str, _VT]: ... + def dict(self, iterable: Iterable[tuple[str, _VT]], /, **kwargs: _VT) -> DictProxy[str, _VT]: ... @overload - def dict(self, __iterable: Iterable[list[str]]) -> DictProxy[str, str]: ... + def dict(self, iterable: Iterable[list[str]], /) -> DictProxy[str, str]: ... @overload - def list(self, __sequence: Sequence[_T]) -> ListProxy[_T]: ... + def dict(self, iterable: Iterable[list[bytes]], /) -> DictProxy[bytes, bytes]: ... + @overload + def list(self, sequence: Sequence[_T], /) -> ListProxy[_T]: ... @overload def list(self) -> ListProxy[Any]: ... class RemoteError(Exception): ... +class SharedMemoryServer(Server): ... -if sys.version_info >= (3, 8): - class SharedMemoryServer(Server): ... - - class SharedMemoryManager(BaseManager): - def get_server(self) -> SharedMemoryServer: ... - def SharedMemory(self, size: int) -> _SharedMemory: ... - def ShareableList(self, sequence: Iterable[_SLT] | None) -> _ShareableList[_SLT]: ... +class SharedMemoryManager(BaseManager): + def get_server(self) -> SharedMemoryServer: ... + def SharedMemory(self, size: int) -> _SharedMemory: ... + def ShareableList(self, sequence: Iterable[_SLT] | None) -> _ShareableList[_SLT]: ... + def __del__(self) -> None: ... diff --git a/mypy/typeshed/stdlib/multiprocessing/pool.pyi b/mypy/typeshed/stdlib/multiprocessing/pool.pyi index a19dd555e254..465c8e08c134 100644 --- a/mypy/typeshed/stdlib/multiprocessing/pool.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/pool.pyi @@ -1,8 +1,8 @@ import sys from collections.abc import Callable, Iterable, Iterator, Mapping from types import TracebackType -from typing import Any, Generic, TypeVar -from typing_extensions import Literal, Self +from typing import Any, Generic, Literal, TypeVar +from typing_extensions import Self if sys.version_info >= (3, 9): from types import GenericAlias @@ -13,18 +13,9 @@ _S = TypeVar("_S") _T = TypeVar("_T") class ApplyResult(Generic[_T]): - if sys.version_info >= (3, 8): - def __init__( - self, pool: Pool, callback: Callable[[_T], object] | None, error_callback: Callable[[BaseException], object] | None - ) -> None: ... - else: - def __init__( - self, - cache: dict[int, ApplyResult[Any]], - callback: Callable[[_T], object] | None, - error_callback: Callable[[BaseException], object] | None, - ) -> None: ... - + def __init__( + self, pool: Pool, callback: Callable[[_T], object] | None, error_callback: Callable[[BaseException], object] | None + ) -> None: ... def get(self, timeout: float | None = None) -> _T: ... def wait(self, timeout: float | None = None) -> None: ... def ready(self) -> bool: ... @@ -36,31 +27,17 @@ class ApplyResult(Generic[_T]): AsyncResult = ApplyResult class MapResult(ApplyResult[list[_T]]): - if sys.version_info >= (3, 8): - def __init__( - self, - pool: Pool, - chunksize: int, - length: int, - callback: Callable[[list[_T]], object] | None, - error_callback: Callable[[BaseException], object] | None, - ) -> None: ... - else: - def __init__( - self, - cache: dict[int, ApplyResult[Any]], - chunksize: int, - length: int, - callback: Callable[[list[_T]], object] | None, - error_callback: Callable[[BaseException], object] | None, - ) -> None: ... + def __init__( + self, + pool: Pool, + chunksize: int, + length: int, + callback: Callable[[list[_T]], object] | None, + error_callback: Callable[[BaseException], object] | None, + ) -> None: ... class IMapIterator(Iterator[_T]): - if sys.version_info >= (3, 8): - def __init__(self, pool: Pool) -> None: ... - else: - def __init__(self, cache: dict[int, IMapIterator[Any]]) -> None: ... - + def __init__(self, pool: Pool) -> None: ... def __iter__(self) -> Self: ... def next(self, timeout: float | None = None) -> _T: ... def __next__(self, timeout: float | None = None) -> _T: ... @@ -72,16 +49,16 @@ class Pool: self, processes: int | None = None, initializer: Callable[..., object] | None = None, - initargs: Iterable[Any] = ..., + initargs: Iterable[Any] = (), maxtasksperchild: int | None = None, context: Any | None = None, ) -> None: ... - def apply(self, func: Callable[..., _T], args: Iterable[Any] = ..., kwds: Mapping[str, Any] = ...) -> _T: ... + def apply(self, func: Callable[..., _T], args: Iterable[Any] = (), kwds: Mapping[str, Any] = {}) -> _T: ... def apply_async( self, func: Callable[..., _T], - args: Iterable[Any] = ..., - kwds: Mapping[str, Any] = ..., + args: Iterable[Any] = (), + kwds: Mapping[str, Any] = {}, callback: Callable[[_T], object] | None = None, error_callback: Callable[[BaseException], object] | None = None, ) -> AsyncResult[_T]: ... @@ -91,7 +68,7 @@ class Pool: func: Callable[[_S], _T], iterable: Iterable[_S], chunksize: int | None = None, - callback: Callable[[_T], object] | None = None, + callback: Callable[[list[_T]], object] | None = None, error_callback: Callable[[BaseException], object] | None = None, ) -> MapResult[_T]: ... def imap(self, func: Callable[[_S], _T], iterable: Iterable[_S], chunksize: int | None = 1) -> IMapIterator[_T]: ... @@ -102,7 +79,7 @@ class Pool: func: Callable[..., _T], iterable: Iterable[Iterable[Any]], chunksize: int | None = None, - callback: Callable[[_T], object] | None = None, + callback: Callable[[list[_T]], object] | None = None, error_callback: Callable[[BaseException], object] | None = None, ) -> AsyncResult[list[_T]]: ... def close(self) -> None: ... @@ -112,19 +89,15 @@ class Pool: def __exit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None ) -> None: ... + def __del__(self) -> None: ... class ThreadPool(Pool): def __init__( - self, processes: int | None = None, initializer: Callable[..., object] | None = None, initargs: Iterable[Any] = ... + self, processes: int | None = None, initializer: Callable[..., object] | None = None, initargs: Iterable[Any] = () ) -> None: ... # undocumented -if sys.version_info >= (3, 8): - INIT: Literal["INIT"] - RUN: Literal["RUN"] - CLOSE: Literal["CLOSE"] - TERMINATE: Literal["TERMINATE"] -else: - RUN: Literal[0] - CLOSE: Literal[1] - TERMINATE: Literal[2] +INIT: Literal["INIT"] +RUN: Literal["RUN"] +CLOSE: Literal["CLOSE"] +TERMINATE: Literal["TERMINATE"] diff --git a/mypy/typeshed/stdlib/multiprocessing/process.pyi b/mypy/typeshed/stdlib/multiprocessing/process.pyi index ef1b4b596d33..4d129b27b0e8 100644 --- a/mypy/typeshed/stdlib/multiprocessing/process.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/process.pyi @@ -1,11 +1,7 @@ -import sys from collections.abc import Callable, Iterable, Mapping from typing import Any -if sys.version_info >= (3, 8): - __all__ = ["BaseProcess", "current_process", "active_children", "parent_process"] -else: - __all__ = ["BaseProcess", "current_process", "active_children"] +__all__ = ["BaseProcess", "current_process", "active_children", "parent_process"] class BaseProcess: name: str @@ -17,8 +13,8 @@ class BaseProcess: group: None = None, target: Callable[..., object] | None = None, name: str | None = None, - args: Iterable[Any] = ..., - kwargs: Mapping[str, Any] = ..., + args: Iterable[Any] = (), + kwargs: Mapping[str, Any] = {}, *, daemon: bool | None = None, ) -> None: ... @@ -40,6 +36,4 @@ class BaseProcess: def current_process() -> BaseProcess: ... def active_children() -> list[BaseProcess]: ... - -if sys.version_info >= (3, 8): - def parent_process() -> BaseProcess | None: ... +def parent_process() -> BaseProcess | None: ... diff --git a/mypy/typeshed/stdlib/multiprocessing/queues.pyi b/mypy/typeshed/stdlib/multiprocessing/queues.pyi index 7ba17dcfbe05..4cedd665552a 100644 --- a/mypy/typeshed/stdlib/multiprocessing/queues.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/queues.pyi @@ -1,4 +1,3 @@ -import queue import sys from typing import Any, Generic, TypeVar @@ -9,19 +8,26 @@ __all__ = ["Queue", "SimpleQueue", "JoinableQueue"] _T = TypeVar("_T") -class Queue(queue.Queue[_T]): +class Queue(Generic[_T]): # FIXME: `ctx` is a circular dependency and it's not actually optional. # It's marked as such to be able to use the generic Queue in __init__.pyi. def __init__(self, maxsize: int = 0, *, ctx: Any = ...) -> None: ... - def get(self, block: bool = True, timeout: float | None = None) -> _T: ... def put(self, obj: _T, block: bool = True, timeout: float | None = None) -> None: ... - def put_nowait(self, item: _T) -> None: ... + def get(self, block: bool = True, timeout: float | None = None) -> _T: ... + def qsize(self) -> int: ... + def empty(self) -> bool: ... + def full(self) -> bool: ... def get_nowait(self) -> _T: ... + def put_nowait(self, obj: _T) -> None: ... def close(self) -> None: ... def join_thread(self) -> None: ... def cancel_join_thread(self) -> None: ... + if sys.version_info >= (3, 12): + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... -class JoinableQueue(Queue[_T]): ... +class JoinableQueue(Queue[_T]): + def task_done(self) -> None: ... + def join(self) -> None: ... class SimpleQueue(Generic[_T]): def __init__(self, *, ctx: Any = ...) -> None: ... @@ -30,6 +36,6 @@ class SimpleQueue(Generic[_T]): def empty(self) -> bool: ... def get(self) -> _T: ... - def put(self, item: _T) -> None: ... + def put(self, obj: _T) -> None: ... if sys.version_info >= (3, 9): def __class_getitem__(cls, item: Any) -> GenericAlias: ... diff --git a/mypy/typeshed/stdlib/multiprocessing/reduction.pyi b/mypy/typeshed/stdlib/multiprocessing/reduction.pyi index e5a8cde8f849..91532633e1b9 100644 --- a/mypy/typeshed/stdlib/multiprocessing/reduction.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/reduction.pyi @@ -8,8 +8,7 @@ from copyreg import _DispatchTableType from multiprocessing import connection from pickle import _ReducedType from socket import socket -from typing import Any -from typing_extensions import Literal +from typing import Any, Literal if sys.platform == "win32": __all__ = ["send_handle", "recv_handle", "ForkingPickler", "register", "dump", "DupHandle", "duplicate", "steal_handle"] @@ -32,13 +31,9 @@ register = ForkingPickler.register def dump(obj: Any, file: SupportsWrite[bytes], protocol: int | None = None) -> None: ... if sys.platform == "win32": - if sys.version_info >= (3, 8): - def duplicate( - handle: int, target_process: int | None = None, inheritable: bool = False, *, source_process: int | None = None - ) -> int: ... - else: - def duplicate(handle: int, target_process: int | None = None, inheritable: bool = False) -> int: ... - + def duplicate( + handle: int, target_process: int | None = None, inheritable: bool = False, *, source_process: int | None = None + ) -> int: ... def steal_handle(source_pid: int, handle: int) -> int: ... def send_handle(conn: connection.PipeConnection, handle: int, destination_pid: int) -> None: ... def recv_handle(conn: connection.PipeConnection) -> int: ... @@ -91,4 +86,5 @@ class AbstractReducer(metaclass=ABCMeta): sendfds = _sendfds recvfds = _recvfds DupFd = _DupFd + def __init__(self, *args: Unused) -> None: ... diff --git a/mypy/typeshed/stdlib/multiprocessing/resource_tracker.pyi b/mypy/typeshed/stdlib/multiprocessing/resource_tracker.pyi index e2b940796126..61da7fdf1ceb 100644 --- a/mypy/typeshed/stdlib/multiprocessing/resource_tracker.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/resource_tracker.pyi @@ -1,4 +1,4 @@ -from _typeshed import FileDescriptorOrPath, Incomplete +from _typeshed import FileDescriptorOrPath from collections.abc import Sized __all__ = ["ensure_running", "register", "unregister"] @@ -6,10 +6,10 @@ __all__ = ["ensure_running", "register", "unregister"] class ResourceTracker: def getfd(self) -> int | None: ... def ensure_running(self) -> None: ... - def register(self, name: Sized, rtype: Incomplete) -> None: ... - def unregister(self, name: Sized, rtype: Incomplete) -> None: ... + def register(self, name: Sized, rtype: str) -> None: ... + def unregister(self, name: Sized, rtype: str) -> None: ... -_resource_tracker: ResourceTracker = ... +_resource_tracker: ResourceTracker ensure_running = _resource_tracker.ensure_running register = _resource_tracker.register unregister = _resource_tracker.unregister diff --git a/mypy/typeshed/stdlib/multiprocessing/shared_memory.pyi b/mypy/typeshed/stdlib/multiprocessing/shared_memory.pyi index ae6e2a0ed19f..adbe8b943de6 100644 --- a/mypy/typeshed/stdlib/multiprocessing/shared_memory.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/shared_memory.pyi @@ -20,6 +20,7 @@ class SharedMemory: def size(self) -> int: ... def close(self) -> None: ... def unlink(self) -> None: ... + def __del__(self) -> None: ... class ShareableList(Generic[_SLT]): shm: SharedMemory diff --git a/mypy/typeshed/stdlib/multiprocessing/sharedctypes.pyi b/mypy/typeshed/stdlib/multiprocessing/sharedctypes.pyi index 686a45d9ae41..4093a97e6ca3 100644 --- a/mypy/typeshed/stdlib/multiprocessing/sharedctypes.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/sharedctypes.pyi @@ -4,8 +4,7 @@ from ctypes import _CData, _SimpleCData, c_char from multiprocessing.context import BaseContext from multiprocessing.synchronize import _LockLike from types import TracebackType -from typing import Any, Generic, Protocol, TypeVar, overload -from typing_extensions import Literal +from typing import Any, Generic, Literal, Protocol, TypeVar, overload __all__ = ["RawValue", "RawArray", "Value", "Array", "copy", "synchronized"] @@ -73,7 +72,7 @@ def synchronized(obj: ctypes.Array[_CT], lock: _LockLike | None = None, ctx: Any def synchronized(obj: _CT, lock: _LockLike | None = None, ctx: Any | None = None) -> SynchronizedBase[_CT]: ... class _AcquireFunc(Protocol): - def __call__(self, block: bool = ..., timeout: float | None = ...) -> bool: ... + def __call__(self, block: bool = ..., timeout: float | None = ..., /) -> bool: ... class SynchronizedBase(Generic[_CT]): acquire: _AcquireFunc @@ -84,7 +83,7 @@ class SynchronizedBase(Generic[_CT]): def get_lock(self) -> _LockLike: ... def __enter__(self) -> bool: ... def __exit__( - self, __exc_type: type[BaseException] | None, __exc_val: BaseException | None, __exc_tb: TracebackType | None + self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, / ) -> None: ... class Synchronized(SynchronizedBase[_SimpleCData[_T]], Generic[_T]): @@ -92,7 +91,13 @@ class Synchronized(SynchronizedBase[_SimpleCData[_T]], Generic[_T]): class SynchronizedArray(SynchronizedBase[ctypes.Array[_CT]], Generic[_CT]): def __len__(self) -> int: ... + @overload + def __getitem__(self, i: slice) -> list[_CT]: ... + @overload def __getitem__(self, i: int) -> _CT: ... + @overload + def __setitem__(self, i: slice, value: Iterable[_CT]) -> None: ... + @overload def __setitem__(self, i: int, value: _CT) -> None: ... def __getslice__(self, start: int, stop: int) -> list[_CT]: ... def __setslice__(self, start: int, stop: int, values: Iterable[_CT]) -> None: ... diff --git a/mypy/typeshed/stdlib/multiprocessing/synchronize.pyi b/mypy/typeshed/stdlib/multiprocessing/synchronize.pyi index 7043759078a2..b417925fb17b 100644 --- a/mypy/typeshed/stdlib/multiprocessing/synchronize.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/synchronize.pyi @@ -14,10 +14,7 @@ class Barrier(threading.Barrier): self, parties: int, action: Callable[[], object] | None = None, timeout: float | None = None, *ctx: BaseContext ) -> None: ... -class BoundedSemaphore(Semaphore): - def __init__(self, value: int = 1, *, ctx: BaseContext) -> None: ... - -class Condition(AbstractContextManager[bool]): +class Condition(AbstractContextManager[bool, None]): def __init__(self, lock: _LockLike | None = None, *, ctx: BaseContext) -> None: ... def notify(self, n: int = 1) -> None: ... def notify_all(self) -> None: ... @@ -26,16 +23,24 @@ class Condition(AbstractContextManager[bool]): def acquire(self, block: bool = ..., timeout: float | None = ...) -> bool: ... def release(self) -> None: ... def __exit__( - self, __exc_type: type[BaseException] | None, __exc_val: BaseException | None, __exc_tb: TracebackType | None + self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, / ) -> None: ... class Event: - def __init__(self, lock: _LockLike | None = ..., *, ctx: BaseContext) -> None: ... + def __init__(self, *, ctx: BaseContext) -> None: ... def is_set(self) -> bool: ... def set(self) -> None: ... def clear(self) -> None: ... def wait(self, timeout: float | None = None) -> bool: ... +# Not part of public API +class SemLock(AbstractContextManager[bool, None]): + def acquire(self, block: bool = ..., timeout: float | None = ...) -> bool: ... + def release(self) -> None: ... + def __exit__( + self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, / + ) -> None: ... + class Lock(SemLock): def __init__(self, *, ctx: BaseContext) -> None: ... @@ -45,10 +50,5 @@ class RLock(SemLock): class Semaphore(SemLock): def __init__(self, value: int = 1, *, ctx: BaseContext) -> None: ... -# Not part of public API -class SemLock(AbstractContextManager[bool]): - def acquire(self, block: bool = ..., timeout: float | None = ...) -> bool: ... - def release(self) -> None: ... - def __exit__( - self, __exc_type: type[BaseException] | None, __exc_val: BaseException | None, __exc_tb: TracebackType | None - ) -> None: ... +class BoundedSemaphore(Semaphore): + def __init__(self, value: int = 1, *, ctx: BaseContext) -> None: ... diff --git a/mypy/typeshed/stdlib/multiprocessing/util.pyi b/mypy/typeshed/stdlib/multiprocessing/util.pyi index 006ec3a9f6ce..790d6c7467f0 100644 --- a/mypy/typeshed/stdlib/multiprocessing/util.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/util.pyi @@ -1,9 +1,8 @@ import threading -from _typeshed import Incomplete, ReadableBuffer, SupportsTrunc, Unused +from _typeshed import ConvertibleToInt, Incomplete, Unused from collections.abc import Callable, Iterable, Mapping, MutableMapping, Sequence from logging import Logger, _Level as _LoggingLevel -from typing import Any, SupportsInt -from typing_extensions import SupportsIndex +from typing import Any, Generic, TypeVar, overload __all__ = [ "sub_debug", @@ -23,6 +22,9 @@ __all__ = [ "SUBWARNING", ] +_T = TypeVar("_T") +_R_co = TypeVar("_R_co", default=Any, covariant=True) + NOTSET: int SUBDEBUG: int DEBUG: int @@ -43,24 +45,40 @@ def is_abstract_socket_namespace(address: str | bytes | None) -> bool: ... abstract_sockets_supported: bool def get_temp_dir() -> str: ... -def register_after_fork(obj: Incomplete, func: Callable[[Incomplete], object]) -> None: ... +def register_after_fork(obj: _T, func: Callable[[_T], object]) -> None: ... -class Finalize: +class Finalize(Generic[_R_co]): + # "args" and "kwargs" are passed as arguments to "callback". + @overload + def __init__( + self, + obj: None, + callback: Callable[..., _R_co], + *, + args: Sequence[Any] = (), + kwargs: Mapping[str, Any] | None = None, + exitpriority: int, + ) -> None: ... + @overload + def __init__( + self, obj: None, callback: Callable[..., _R_co], args: Sequence[Any], kwargs: Mapping[str, Any] | None, exitpriority: int + ) -> None: ... + @overload def __init__( self, - obj: Incomplete | None, - callback: Callable[..., Incomplete], - args: Sequence[Any] = ..., + obj: Any, + callback: Callable[..., _R_co], + args: Sequence[Any] = (), kwargs: Mapping[str, Any] | None = None, exitpriority: int | None = None, ) -> None: ... def __call__( self, wr: Unused = None, - _finalizer_registry: MutableMapping[Incomplete, Incomplete] = ..., + _finalizer_registry: MutableMapping[Incomplete, Incomplete] = {}, sub_debug: Callable[..., object] = ..., getpid: Callable[[], int] = ..., - ) -> Incomplete: ... + ) -> _R_co: ... def cancel(self) -> None: ... def still_active(self) -> bool: ... @@ -77,9 +95,4 @@ class ForkAwareLocal(threading.local): ... MAXFD: int def close_all_fds_except(fds: Iterable[int]) -> None: ... -def spawnv_passfds( - path: bytes, - # args is anything that can be passed to the int constructor - args: Sequence[str | ReadableBuffer | SupportsInt | SupportsIndex | SupportsTrunc], - passfds: Sequence[int], -) -> int: ... +def spawnv_passfds(path: bytes, args: Sequence[ConvertibleToInt], passfds: Sequence[int]) -> int: ... diff --git a/mypy/typeshed/stdlib/nntplib.pyi b/mypy/typeshed/stdlib/nntplib.pyi index f948c1430c90..969c657e9aab 100644 --- a/mypy/typeshed/stdlib/nntplib.pyi +++ b/mypy/typeshed/stdlib/nntplib.pyi @@ -5,8 +5,8 @@ import sys from _typeshed import Unused from builtins import list as _list # conflicts with a method named "list" from collections.abc import Iterable -from typing import IO, Any, NamedTuple -from typing_extensions import Literal, Self, TypeAlias +from typing import IO, Any, Literal, NamedTuple +from typing_extensions import Self, TypeAlias __all__ = [ "NNTP", diff --git a/mypy/typeshed/stdlib/nt.pyi b/mypy/typeshed/stdlib/nt.pyi new file mode 100644 index 000000000000..4066096f4c71 --- /dev/null +++ b/mypy/typeshed/stdlib/nt.pyi @@ -0,0 +1,111 @@ +import sys + +if sys.platform == "win32": + # Actually defined here and re-exported from os at runtime, + # but this leads to less code duplication + from os import ( + F_OK as F_OK, + O_APPEND as O_APPEND, + O_BINARY as O_BINARY, + O_CREAT as O_CREAT, + O_EXCL as O_EXCL, + O_NOINHERIT as O_NOINHERIT, + O_RANDOM as O_RANDOM, + O_RDONLY as O_RDONLY, + O_RDWR as O_RDWR, + O_SEQUENTIAL as O_SEQUENTIAL, + O_SHORT_LIVED as O_SHORT_LIVED, + O_TEMPORARY as O_TEMPORARY, + O_TEXT as O_TEXT, + O_TRUNC as O_TRUNC, + O_WRONLY as O_WRONLY, + P_DETACH as P_DETACH, + P_NOWAIT as P_NOWAIT, + P_NOWAITO as P_NOWAITO, + P_OVERLAY as P_OVERLAY, + P_WAIT as P_WAIT, + R_OK as R_OK, + TMP_MAX as TMP_MAX, + W_OK as W_OK, + X_OK as X_OK, + DirEntry as DirEntry, + abort as abort, + access as access, + chdir as chdir, + chmod as chmod, + close as close, + closerange as closerange, + cpu_count as cpu_count, + device_encoding as device_encoding, + dup as dup, + dup2 as dup2, + error as error, + execv as execv, + execve as execve, + fspath as fspath, + fstat as fstat, + fsync as fsync, + ftruncate as ftruncate, + get_handle_inheritable as get_handle_inheritable, + get_inheritable as get_inheritable, + get_terminal_size as get_terminal_size, + getcwd as getcwd, + getcwdb as getcwdb, + getlogin as getlogin, + getpid as getpid, + getppid as getppid, + isatty as isatty, + kill as kill, + link as link, + listdir as listdir, + lseek as lseek, + lstat as lstat, + mkdir as mkdir, + open as open, + pipe as pipe, + putenv as putenv, + read as read, + readlink as readlink, + remove as remove, + rename as rename, + replace as replace, + rmdir as rmdir, + scandir as scandir, + set_handle_inheritable as set_handle_inheritable, + set_inheritable as set_inheritable, + spawnv as spawnv, + spawnve as spawnve, + startfile as startfile, + stat as stat, + stat_result as stat_result, + statvfs_result as statvfs_result, + strerror as strerror, + symlink as symlink, + system as system, + terminal_size as terminal_size, + times as times, + times_result as times_result, + truncate as truncate, + umask as umask, + uname_result as uname_result, + unlink as unlink, + urandom as urandom, + utime as utime, + waitpid as waitpid, + write as write, + ) + + if sys.version_info >= (3, 9): + from os import unsetenv as unsetenv, waitstatus_to_exitcode as waitstatus_to_exitcode + if sys.version_info >= (3, 11): + from os import EX_OK as EX_OK + if sys.version_info >= (3, 12): + from os import ( + get_blocking as get_blocking, + listdrives as listdrives, + listmounts as listmounts, + listvolumes as listvolumes, + set_blocking as set_blocking, + ) + + environ: dict[str, str] diff --git a/mypy/typeshed/stdlib/ntpath.pyi b/mypy/typeshed/stdlib/ntpath.pyi index f1fa137c6d88..079366018bf5 100644 --- a/mypy/typeshed/stdlib/ntpath.pyi +++ b/mypy/typeshed/stdlib/ntpath.pyi @@ -45,6 +45,9 @@ from posixpath import ( from typing import AnyStr, overload from typing_extensions import LiteralString +if sys.version_info >= (3, 12): + from posixpath import isjunction as isjunction, splitroot as splitroot + __all__ = [ "normcase", "isabs", @@ -85,6 +88,8 @@ __all__ = [ "samestat", "commonpath", ] +if sys.version_info >= (3, 12): + __all__ += ["isjunction", "splitroot"] altsep: LiteralString @@ -92,11 +97,11 @@ altsep: LiteralString # but must be defined as pos-only in the stub or cross-platform code doesn't type-check, # as the parameter name is different in posixpath.join() @overload -def join(__path: LiteralString, *paths: LiteralString) -> LiteralString: ... +def join(path: LiteralString, /, *paths: LiteralString) -> LiteralString: ... @overload -def join(__path: StrPath, *paths: StrPath) -> str: ... +def join(path: StrPath, /, *paths: StrPath) -> str: ... @overload -def join(__path: BytesPath, *paths: BytesPath) -> bytes: ... +def join(path: BytesPath, /, *paths: BytesPath) -> bytes: ... if sys.platform == "win32": if sys.version_info >= (3, 10): diff --git a/mypy/typeshed/stdlib/numbers.pyi b/mypy/typeshed/stdlib/numbers.pyi index 55f21041ae44..e129de2cdc67 100644 --- a/mypy/typeshed/stdlib/numbers.pyi +++ b/mypy/typeshed/stdlib/numbers.pyi @@ -1,129 +1,209 @@ # Note: these stubs are incomplete. The more complex type # signatures are currently omitted. +# +# Use _ComplexLike, _RealLike and _IntegralLike for return types in this module +# rather than `numbers.Complex`, `numbers.Real` and `numbers.Integral`, +# to avoid an excessive number of `type: ignore`s in subclasses of these ABCs +# (since type checkers don't see `complex` as a subtype of `numbers.Complex`, +# nor `float` as a subtype of `numbers.Real`, etc.) +from _typeshed import Incomplete from abc import ABCMeta, abstractmethod -from typing import Any, SupportsFloat, overload +from typing import Literal, Protocol, overload __all__ = ["Number", "Complex", "Real", "Rational", "Integral"] +############################ +# Protocols for return types +############################ + +# `_ComplexLike` is a structural-typing approximation +# of the `Complex` ABC, which is not (and cannot be) a protocol +# +# NOTE: We can't include `__complex__` here, +# as we want `int` to be seen as a subtype of `_ComplexLike`, +# and `int.__complex__` does not exist :( +class _ComplexLike(Protocol): + def __neg__(self) -> _ComplexLike: ... + def __pos__(self) -> _ComplexLike: ... + def __abs__(self) -> _RealLike: ... + +# _RealLike is a structural-typing approximation +# of the `Real` ABC, which is not (and cannot be) a protocol +class _RealLike(_ComplexLike, Protocol): + def __trunc__(self) -> _IntegralLike: ... + def __floor__(self) -> _IntegralLike: ... + def __ceil__(self) -> _IntegralLike: ... + def __float__(self) -> float: ... + # Overridden from `_ComplexLike` + # for a more precise return type: + def __neg__(self) -> _RealLike: ... + def __pos__(self) -> _RealLike: ... + +# _IntegralLike is a structural-typing approximation +# of the `Integral` ABC, which is not (and cannot be) a protocol +class _IntegralLike(_RealLike, Protocol): + def __invert__(self) -> _IntegralLike: ... + def __int__(self) -> int: ... + def __index__(self) -> int: ... + # Overridden from `_ComplexLike` + # for a more precise return type: + def __abs__(self) -> _IntegralLike: ... + # Overridden from `RealLike` + # for a more precise return type: + def __neg__(self) -> _IntegralLike: ... + def __pos__(self) -> _IntegralLike: ... + +################# +# Module "proper" +################# + class Number(metaclass=ABCMeta): @abstractmethod def __hash__(self) -> int: ... -class Complex(Number): +# See comment at the top of the file +# for why some of these return types are purposefully vague +class Complex(Number, _ComplexLike): @abstractmethod def __complex__(self) -> complex: ... def __bool__(self) -> bool: ... @property @abstractmethod - def real(self) -> Any: ... + def real(self) -> _RealLike: ... @property @abstractmethod - def imag(self) -> Any: ... + def imag(self) -> _RealLike: ... @abstractmethod - def __add__(self, other: Any) -> Any: ... + def __add__(self, other) -> _ComplexLike: ... @abstractmethod - def __radd__(self, other: Any) -> Any: ... + def __radd__(self, other) -> _ComplexLike: ... @abstractmethod - def __neg__(self) -> Any: ... + def __neg__(self) -> _ComplexLike: ... @abstractmethod - def __pos__(self) -> Any: ... - def __sub__(self, other: Any) -> Any: ... - def __rsub__(self, other: Any) -> Any: ... + def __pos__(self) -> _ComplexLike: ... + def __sub__(self, other) -> _ComplexLike: ... + def __rsub__(self, other) -> _ComplexLike: ... @abstractmethod - def __mul__(self, other: Any) -> Any: ... + def __mul__(self, other) -> _ComplexLike: ... @abstractmethod - def __rmul__(self, other: Any) -> Any: ... + def __rmul__(self, other) -> _ComplexLike: ... @abstractmethod - def __truediv__(self, other: Any) -> Any: ... + def __truediv__(self, other) -> _ComplexLike: ... @abstractmethod - def __rtruediv__(self, other: Any) -> Any: ... + def __rtruediv__(self, other) -> _ComplexLike: ... @abstractmethod - def __pow__(self, exponent: Any) -> Any: ... + def __pow__(self, exponent) -> _ComplexLike: ... @abstractmethod - def __rpow__(self, base: Any) -> Any: ... + def __rpow__(self, base) -> _ComplexLike: ... @abstractmethod - def __abs__(self) -> Real: ... + def __abs__(self) -> _RealLike: ... @abstractmethod - def conjugate(self) -> Any: ... + def conjugate(self) -> _ComplexLike: ... @abstractmethod def __eq__(self, other: object) -> bool: ... -class Real(Complex, SupportsFloat): +# See comment at the top of the file +# for why some of these return types are purposefully vague +class Real(Complex, _RealLike): @abstractmethod def __float__(self) -> float: ... @abstractmethod - def __trunc__(self) -> int: ... + def __trunc__(self) -> _IntegralLike: ... @abstractmethod - def __floor__(self) -> int: ... + def __floor__(self) -> _IntegralLike: ... @abstractmethod - def __ceil__(self) -> int: ... + def __ceil__(self) -> _IntegralLike: ... @abstractmethod @overload - def __round__(self, ndigits: None = None) -> int: ... + def __round__(self, ndigits: None = None) -> _IntegralLike: ... @abstractmethod @overload - def __round__(self, ndigits: int) -> Any: ... - def __divmod__(self, other: Any) -> Any: ... - def __rdivmod__(self, other: Any) -> Any: ... + def __round__(self, ndigits: int) -> _RealLike: ... + def __divmod__(self, other) -> tuple[_RealLike, _RealLike]: ... + def __rdivmod__(self, other) -> tuple[_RealLike, _RealLike]: ... @abstractmethod - def __floordiv__(self, other: Any) -> int: ... + def __floordiv__(self, other) -> _RealLike: ... @abstractmethod - def __rfloordiv__(self, other: Any) -> int: ... + def __rfloordiv__(self, other) -> _RealLike: ... @abstractmethod - def __mod__(self, other: Any) -> Any: ... + def __mod__(self, other) -> _RealLike: ... @abstractmethod - def __rmod__(self, other: Any) -> Any: ... + def __rmod__(self, other) -> _RealLike: ... @abstractmethod - def __lt__(self, other: Any) -> bool: ... + def __lt__(self, other) -> bool: ... @abstractmethod - def __le__(self, other: Any) -> bool: ... + def __le__(self, other) -> bool: ... def __complex__(self) -> complex: ... @property - def real(self) -> Any: ... + def real(self) -> _RealLike: ... @property - def imag(self) -> Any: ... - def conjugate(self) -> Any: ... + def imag(self) -> Literal[0]: ... + def conjugate(self) -> _RealLike: ... + # Not actually overridden at runtime, + # but we override these in the stub to give them more precise return types: + @abstractmethod + def __pos__(self) -> _RealLike: ... + @abstractmethod + def __neg__(self) -> _RealLike: ... +# See comment at the top of the file +# for why some of these return types are purposefully vague class Rational(Real): @property @abstractmethod - def numerator(self) -> int: ... + def numerator(self) -> _IntegralLike: ... @property @abstractmethod - def denominator(self) -> int: ... + def denominator(self) -> _IntegralLike: ... def __float__(self) -> float: ... -class Integral(Rational): +# See comment at the top of the file +# for why some of these return types are purposefully vague +class Integral(Rational, _IntegralLike): @abstractmethod def __int__(self) -> int: ... def __index__(self) -> int: ... @abstractmethod - def __pow__(self, exponent: Any, modulus: Any | None = None) -> Any: ... + def __pow__(self, exponent, modulus: Incomplete | None = None) -> _IntegralLike: ... @abstractmethod - def __lshift__(self, other: Any) -> Any: ... + def __lshift__(self, other) -> _IntegralLike: ... @abstractmethod - def __rlshift__(self, other: Any) -> Any: ... + def __rlshift__(self, other) -> _IntegralLike: ... @abstractmethod - def __rshift__(self, other: Any) -> Any: ... + def __rshift__(self, other) -> _IntegralLike: ... @abstractmethod - def __rrshift__(self, other: Any) -> Any: ... + def __rrshift__(self, other) -> _IntegralLike: ... @abstractmethod - def __and__(self, other: Any) -> Any: ... + def __and__(self, other) -> _IntegralLike: ... @abstractmethod - def __rand__(self, other: Any) -> Any: ... + def __rand__(self, other) -> _IntegralLike: ... @abstractmethod - def __xor__(self, other: Any) -> Any: ... + def __xor__(self, other) -> _IntegralLike: ... @abstractmethod - def __rxor__(self, other: Any) -> Any: ... + def __rxor__(self, other) -> _IntegralLike: ... @abstractmethod - def __or__(self, other: Any) -> Any: ... + def __or__(self, other) -> _IntegralLike: ... @abstractmethod - def __ror__(self, other: Any) -> Any: ... + def __ror__(self, other) -> _IntegralLike: ... @abstractmethod - def __invert__(self) -> Any: ... + def __invert__(self) -> _IntegralLike: ... def __float__(self) -> float: ... @property - def numerator(self) -> int: ... + def numerator(self) -> _IntegralLike: ... @property - def denominator(self) -> int: ... + def denominator(self) -> Literal[1]: ... + # Not actually overridden at runtime, + # but we override these in the stub to give them more precise return types: + @abstractmethod + def __pos__(self) -> _IntegralLike: ... + @abstractmethod + def __neg__(self) -> _IntegralLike: ... + @abstractmethod + def __abs__(self) -> _IntegralLike: ... + @abstractmethod + @overload + def __round__(self, ndigits: None = None) -> _IntegralLike: ... + @abstractmethod + @overload + def __round__(self, ndigits: int) -> _IntegralLike: ... diff --git a/mypy/typeshed/stdlib/opcode.pyi b/mypy/typeshed/stdlib/opcode.pyi index 1232454e71ea..14bdb7622142 100644 --- a/mypy/typeshed/stdlib/opcode.pyi +++ b/mypy/typeshed/stdlib/opcode.pyi @@ -1,5 +1,5 @@ import sys -from typing_extensions import Literal +from typing import Literal __all__ = [ "cmp_op", @@ -14,9 +14,12 @@ __all__ = [ "opmap", "HAVE_ARGUMENT", "EXTENDED_ARG", - "hasnargs", "stack_effect", ] +if sys.version_info >= (3, 12): + __all__ += ["hasarg", "hasexc"] +else: + __all__ += ["hasnargs"] if sys.version_info >= (3, 9): cmp_op: tuple[Literal["<"], Literal["<="], Literal["=="], Literal["!="], Literal[">"], Literal[">="]] @@ -42,16 +45,15 @@ hasjabs: list[int] haslocal: list[int] hascompare: list[int] hasfree: list[int] +if sys.version_info >= (3, 12): + hasarg: list[int] + hasexc: list[int] +else: + hasnargs: list[int] opname: list[str] opmap: dict[str, int] HAVE_ARGUMENT: Literal[90] EXTENDED_ARG: Literal[144] -if sys.version_info >= (3, 8): - def stack_effect(__opcode: int, __oparg: int | None = None, *, jump: bool | None = None) -> int: ... - -else: - def stack_effect(__opcode: int, __oparg: int | None = None) -> int: ... - -hasnargs: list[int] +def stack_effect(opcode: int, oparg: int | None = None, /, *, jump: bool | None = None) -> int: ... diff --git a/mypy/typeshed/stdlib/optparse.pyi b/mypy/typeshed/stdlib/optparse.pyi index a8c1c4cfb93e..3474648617c2 100644 --- a/mypy/typeshed/stdlib/optparse.pyi +++ b/mypy/typeshed/stdlib/optparse.pyi @@ -1,6 +1,7 @@ +from _typeshed import Incomplete from abc import abstractmethod from collections.abc import Callable, Iterable, Mapping, Sequence -from typing import IO, Any, AnyStr, overload +from typing import IO, Any, AnyStr, Literal, overload __all__ = [ "Option", @@ -26,8 +27,8 @@ NO_DEFAULT: tuple[str, ...] SUPPRESS_HELP: str SUPPRESS_USAGE: str -def check_builtin(option: Option, opt: Any, value: str) -> Any: ... -def check_choice(option: Option, opt: Any, value: str) -> str: ... +def check_builtin(option: Option, opt, value: str): ... +def check_choice(option: Option, opt, value: str) -> str: ... class OptParseError(Exception): msg: str @@ -54,26 +55,26 @@ class HelpFormatter: _short_opt_fmt: str current_indent: int default_tag: str - help_position: Any - help_width: Any + help_position: int + help_width: int | Any # initialized as None and computed later as int when storing option strings indent_increment: int level: int max_help_position: int option_strings: dict[Option, str] parser: OptionParser - short_first: Any + short_first: Incomplete width: int def __init__(self, indent_increment: int, max_help_position: int, width: int | None, short_first: int) -> None: ... def dedent(self) -> None: ... def expand_default(self, option: Option) -> str: ... - def format_description(self, description: str) -> str: ... - def format_epilog(self, epilog: str) -> str: ... + def format_description(self, description: str | None) -> str: ... + def format_epilog(self, epilog: str | None) -> str: ... @abstractmethod - def format_heading(self, heading: Any) -> str: ... + def format_heading(self, heading: str) -> str: ... def format_option(self, option: Option) -> str: ... def format_option_strings(self, option: Option) -> str: ... @abstractmethod - def format_usage(self, usage: Any) -> str: ... + def format_usage(self, usage: str) -> str: ... def indent(self) -> None: ... def set_long_opt_delimiter(self, delim: str) -> None: ... def set_parser(self, parser: OptionParser) -> None: ... @@ -98,25 +99,25 @@ class Option: ACTIONS: tuple[str, ...] ALWAYS_TYPED_ACTIONS: tuple[str, ...] ATTRS: list[str] - CHECK_METHODS: list[Callable[..., Any]] | None + CHECK_METHODS: list[Callable[..., Incomplete]] | None CONST_ACTIONS: tuple[str, ...] STORE_ACTIONS: tuple[str, ...] TYPED_ACTIONS: tuple[str, ...] TYPES: tuple[str, ...] - TYPE_CHECKER: dict[str, Callable[..., Any]] + TYPE_CHECKER: dict[str, Callable[[Option, str, Incomplete], Any]] _long_opts: list[str] _short_opts: list[str] action: str dest: str | None - default: Any + default: Incomplete nargs: int - type: Any - callback: Callable[..., Any] | None - callback_args: tuple[Any, ...] | None - callback_kwargs: dict[str, Any] | None + type: Incomplete + callback: Callable[..., Incomplete] | None + callback_args: tuple[Incomplete, ...] | None + callback_kwargs: dict[str, Incomplete] | None help: str | None metavar: str | None - def __init__(self, *opts: str | None, **attrs: Any) -> None: ... + def __init__(self, *opts: str | None, **attrs) -> None: ... def _check_action(self) -> None: ... def _check_callback(self) -> None: ... def _check_choice(self) -> None: ... @@ -125,13 +126,13 @@ class Option: def _check_nargs(self) -> None: ... def _check_opt_strings(self, opts: Iterable[str | None]) -> list[str]: ... def _check_type(self) -> None: ... - def _set_attrs(self, attrs: dict[str, Any]) -> None: ... + def _set_attrs(self, attrs: dict[str, Incomplete]) -> None: ... def _set_opt_strings(self, opts: Iterable[str]) -> None: ... - def check_value(self, opt: str, value: Any) -> Any: ... - def convert_value(self, opt: str, value: Any) -> Any: ... + def check_value(self, opt: str, value): ... + def convert_value(self, opt: str, value): ... def get_opt_string(self) -> str: ... - def process(self, opt: Any, value: Any, values: Any, parser: OptionParser) -> int: ... - def take_action(self, action: str, dest: str, opt: Any, value: Any, values: Any, parser: OptionParser) -> int: ... + def process(self, opt, value, values, parser: OptionParser) -> int: ... + def take_action(self, action: str, dest: str, opt, value, values, parser: OptionParser) -> int: ... def takes_value(self) -> bool: ... make_option = Option @@ -140,28 +141,30 @@ class OptionContainer: _long_opt: dict[str, Option] _short_opt: dict[str, Option] conflict_handler: str - defaults: dict[str, Any] - description: Any + defaults: dict[str, Incomplete] + description: str | None option_class: type[Option] - def __init__(self, option_class: type[Option], conflict_handler: Any, description: Any) -> None: ... - def _check_conflict(self, option: Any) -> None: ... + def __init__( + self, option_class: type[Option], conflict_handler: Literal["error", "resolve"], description: str | None + ) -> None: ... + def _check_conflict(self, option: Option) -> None: ... def _create_option_mappings(self) -> None: ... def _share_option_mappings(self, parser: OptionParser) -> None: ... @overload def add_option(self, opt: Option) -> Option: ... @overload - def add_option(self, *args: str | None, **kwargs: Any) -> Any: ... + def add_option(self, arg: str, /, *args: str | None, **kwargs) -> Option: ... def add_options(self, option_list: Iterable[Option]) -> None: ... def destroy(self) -> None: ... - def format_description(self, formatter: HelpFormatter | None) -> Any: ... - def format_help(self, formatter: HelpFormatter | None) -> str: ... - def format_option_help(self, formatter: HelpFormatter | None) -> str: ... - def get_description(self) -> Any: ... + def format_option_help(self, formatter: HelpFormatter) -> str: ... + def format_description(self, formatter: HelpFormatter) -> str: ... + def format_help(self, formatter: HelpFormatter) -> str: ... + def get_description(self) -> str | None: ... def get_option(self, opt_str: str) -> Option | None: ... def has_option(self, opt_str: str) -> bool: ... def remove_option(self, opt_str: str) -> None: ... - def set_conflict_handler(self, handler: Any) -> None: ... - def set_description(self, description: Any) -> None: ... + def set_conflict_handler(self, handler: Literal["error", "resolve"]) -> None: ... + def set_description(self, description: str | None) -> None: ... class OptionGroup(OptionContainer): option_list: list[Option] @@ -172,15 +175,15 @@ class OptionGroup(OptionContainer): def set_title(self, title: str) -> None: ... class Values: - def __init__(self, defaults: Mapping[str, Any] | None = None) -> None: ... - def _update(self, dict: Mapping[str, Any], mode: Any) -> None: ... - def _update_careful(self, dict: Mapping[str, Any]) -> None: ... - def _update_loose(self, dict: Mapping[str, Any]) -> None: ... - def ensure_value(self, attr: str, value: Any) -> Any: ... + def __init__(self, defaults: Mapping[str, Incomplete] | None = None) -> None: ... + def _update(self, dict: Mapping[str, Incomplete], mode) -> None: ... + def _update_careful(self, dict: Mapping[str, Incomplete]) -> None: ... + def _update_loose(self, dict: Mapping[str, Incomplete]) -> None: ... + def ensure_value(self, attr: str, value): ... def read_file(self, filename: str, mode: str = "careful") -> None: ... def read_module(self, modname: str, mode: str = "careful") -> None: ... - def __getattr__(self, name: str) -> Any: ... - def __setattr__(self, __name: str, __value: Any) -> None: ... + def __getattr__(self, name: str): ... + def __setattr__(self, name: str, value, /) -> None: ... def __eq__(self, other: object) -> bool: ... class OptionParser(OptionContainer): @@ -190,9 +193,9 @@ class OptionParser(OptionContainer): largs: list[str] | None option_groups: list[OptionGroup] option_list: list[Option] - process_default_values: Any + process_default_values: bool prog: str | None - rargs: list[Any] | None + rargs: list[str] | None standard_option_list: list[Option] usage: str | None values: Values | None @@ -214,28 +217,28 @@ class OptionParser(OptionContainer): def _add_version_option(self) -> None: ... def _create_option_list(self) -> None: ... def _get_all_options(self) -> list[Option]: ... - def _get_args(self, args: Iterable[Any]) -> list[Any]: ... + def _get_args(self, args: Iterable[Incomplete]) -> list[Incomplete]: ... def _init_parsing_state(self) -> None: ... def _match_long_opt(self, opt: str) -> str: ... def _populate_option_list(self, option_list: Iterable[Option], add_help: bool = True) -> None: ... - def _process_args(self, largs: list[Any], rargs: list[Any], values: Values) -> None: ... - def _process_long_opt(self, rargs: list[Any], values: Any) -> None: ... - def _process_short_opts(self, rargs: list[Any], values: Any) -> None: ... + def _process_args(self, largs: list[Incomplete], rargs: list[Incomplete], values: Values) -> None: ... + def _process_long_opt(self, rargs: list[Incomplete], values) -> None: ... + def _process_short_opts(self, rargs: list[Incomplete], values) -> None: ... @overload - def add_option_group(self, __opt_group: OptionGroup) -> OptionGroup: ... + def add_option_group(self, opt_group: OptionGroup, /) -> OptionGroup: ... @overload - def add_option_group(self, *args: Any, **kwargs: Any) -> OptionGroup: ... + def add_option_group(self, *args, **kwargs) -> OptionGroup: ... def check_values(self, values: Values, args: list[str]) -> tuple[Values, list[str]]: ... def disable_interspersed_args(self) -> None: ... def enable_interspersed_args(self) -> None: ... def error(self, msg: str) -> None: ... def exit(self, status: int = 0, msg: str | None = None) -> None: ... - def expand_prog_name(self, s: str | None) -> Any: ... - def format_epilog(self, formatter: HelpFormatter) -> Any: ... + def expand_prog_name(self, s: str) -> str: ... + def format_epilog(self, formatter: HelpFormatter) -> str: ... def format_help(self, formatter: HelpFormatter | None = None) -> str: ... def format_option_help(self, formatter: HelpFormatter | None = None) -> str: ... def get_default_values(self) -> Values: ... - def get_option_group(self, opt_str: str) -> Any: ... + def get_option_group(self, opt_str: str) -> OptionGroup | None: ... def get_prog_name(self) -> str: ... def get_usage(self) -> str: ... def get_version(self) -> str: ... @@ -246,7 +249,7 @@ class OptionParser(OptionContainer): def print_usage(self, file: IO[str] | None = None) -> None: ... def print_help(self, file: IO[str] | None = None) -> None: ... def print_version(self, file: IO[str] | None = None) -> None: ... - def set_default(self, dest: Any, value: Any) -> None: ... - def set_defaults(self, **kwargs: Any) -> None: ... - def set_process_default_values(self, process: Any) -> None: ... + def set_default(self, dest, value) -> None: ... + def set_defaults(self, **kwargs) -> None: ... + def set_process_default_values(self, process) -> None: ... def set_usage(self, usage: str) -> None: ... diff --git a/mypy/typeshed/stdlib/os/__init__.pyi b/mypy/typeshed/stdlib/os/__init__.pyi index 595b78789c6a..e1c7855c0bb6 100644 --- a/mypy/typeshed/stdlib/os/__init__.pyi +++ b/mypy/typeshed/stdlib/os/__init__.pyi @@ -2,6 +2,7 @@ import sys from _typeshed import ( AnyStr_co, BytesPath, + FileDescriptor, FileDescriptorLike, FileDescriptorOrPath, GenericPath, @@ -24,8 +25,22 @@ from collections.abc import Callable, Iterable, Iterator, Mapping, MutableMappin from contextlib import AbstractContextManager from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper as _TextIOWrapper from subprocess import Popen -from typing import IO, Any, AnyStr, BinaryIO, Generic, NoReturn, Protocol, TypeVar, overload, runtime_checkable -from typing_extensions import Final, Literal, Self, TypeAlias, final +from typing import ( + IO, + Any, + AnyStr, + BinaryIO, + Final, + Generic, + Literal, + NoReturn, + Protocol, + TypeVar, + final, + overload, + runtime_checkable, +) +from typing_extensions import Self, TypeAlias, Unpack, deprecated from . import path as _path @@ -69,9 +84,20 @@ if sys.platform != "win32": POSIX_FADV_WILLNEED: int POSIX_FADV_DONTNEED: int - SF_NODISKIO: int - SF_MNOWAIT: int - SF_SYNC: int + if sys.platform != "linux" and sys.platform != "darwin": + # In the os-module docs, these are marked as being available + # on "Unix, not Emscripten, not WASI." + # However, in the source code, a comment indicates they're "FreeBSD constants". + # sys.platform could have one of many values on a FreeBSD Python build, + # so the sys-module docs recommend doing `if sys.platform.startswith('freebsd')` + # to detect FreeBSD builds. Unfortunately that would be too dynamic + # for type checkers, however. + SF_NODISKIO: int + SF_MNOWAIT: int + SF_SYNC: int + + if sys.version_info >= (3, 11): + SF_NOCACHE: int if sys.platform == "linux": XATTR_SIZE_MAX: int @@ -121,6 +147,12 @@ if sys.platform == "linux": GRND_NONBLOCK: int GRND_RANDOM: int +if sys.platform == "darwin" and sys.version_info >= (3, 12): + PRIO_DARWIN_BG: int + PRIO_DARWIN_NONUI: int + PRIO_DARWIN_PROCESS: int + PRIO_DARWIN_THREAD: int + SEEK_SET: int SEEK_CUR: int SEEK_END: int @@ -230,7 +262,7 @@ class _Environ(MutableMapping[AnyStr, AnyStr], Generic[AnyStr]): unsetenv: Callable[[AnyStr, AnyStr], object], ) -> None: ... - def setdefault(self, key: AnyStr, value: AnyStr) -> AnyStr: ... # type: ignore[override] + def setdefault(self, key: AnyStr, value: AnyStr) -> AnyStr: ... def copy(self) -> dict[AnyStr, AnyStr]: ... def __delitem__(self, key: AnyStr) -> None: ... def __getitem__(self, key: AnyStr) -> AnyStr: ... @@ -252,12 +284,14 @@ environ: _Environ[str] if sys.platform != "win32": environb: _Environ[bytes] +if sys.version_info >= (3, 11) or sys.platform != "win32": + EX_OK: int + if sys.platform != "win32": confstr_names: dict[str, int] pathconf_names: dict[str, int] sysconf_names: dict[str, int] - EX_OK: int EX_USAGE: int EX_DATAERR: int EX_NOINPUT: int @@ -273,6 +307,9 @@ if sys.platform != "win32": EX_PROTOCOL: int EX_NOPERM: int EX_CONFIG: int + +# Exists on some Unix platforms, e.g. Solaris. +if sys.platform != "win32" and sys.platform != "darwin" and sys.platform != "linux": EX_NOTFOUND: int P_NOWAIT: int @@ -305,6 +342,7 @@ class stat_result(structseq[float], tuple[int, int, int, int, int, int, int, flo # More items may be added at the end by some implementations. if sys.version_info >= (3, 10): __match_args__: Final = ("st_mode", "st_ino", "st_dev", "st_nlink", "st_uid", "st_gid", "st_size") + @property def st_mode(self) -> int: ... # protection bits, @property @@ -324,8 +362,16 @@ class stat_result(structseq[float], tuple[int, int, int, int, int, int, int, flo @property def st_mtime(self) -> float: ... # time of most recent content modification, # platform dependent (time of most recent metadata change on Unix, or the time of creation on Windows) - @property - def st_ctime(self) -> float: ... + if sys.version_info >= (3, 12) and sys.platform == "win32": + @property + @deprecated( + "Use st_birthtime instead to retrieve the file creation time. In the future, this property will contain the last metadata change time." + ) + def st_ctime(self) -> float: ... + else: + @property + def st_ctime(self) -> float: ... + @property def st_atime_ns(self) -> int: ... # time of most recent access, in nanoseconds @property @@ -336,9 +382,13 @@ class stat_result(structseq[float], tuple[int, int, int, int, int, int, int, flo if sys.platform == "win32": @property def st_file_attributes(self) -> int: ... - if sys.version_info >= (3, 8): + @property + def st_reparse_tag(self) -> int: ... + if sys.version_info >= (3, 12): + @property + def st_birthtime(self) -> float: ... # time of file creation in seconds @property - def st_reparse_tag(self) -> int: ... + def st_birthtime_ns(self) -> int: ... # time of file creation in nanoseconds else: @property def st_blocks(self) -> int: ... # number of blocks allocated for file @@ -347,13 +397,13 @@ class stat_result(structseq[float], tuple[int, int, int, int, int, int, int, flo @property def st_rdev(self) -> int: ... # type of device if an inode device if sys.platform != "linux": - # These properties are available on MacOS, but not on Windows or Ubuntu. + # These properties are available on MacOS, but not Ubuntu. # On other Unix systems (such as FreeBSD), the following attributes may be # available (but may be only filled out if root tries to use them): @property def st_gen(self) -> int: ... # file generation number @property - def st_birthtime(self) -> int: ... # time of file creation + def st_birthtime(self) -> float: ... # time of file creation in seconds if sys.platform == "darwin": @property def st_flags(self) -> int: ... # user defined flags for file @@ -388,6 +438,8 @@ class DirEntry(Generic[AnyStr]): def __fspath__(self) -> AnyStr: ... if sys.version_info >= (3, 9): def __class_getitem__(cls, item: Any) -> GenericAlias: ... + if sys.version_info >= (3, 12): + def is_junction(self) -> bool: ... @final class statvfs_result(structseq[int], tuple[int, int, int, int, int, int, int, int, int, int, int]): @@ -404,6 +456,7 @@ class statvfs_result(structseq[int], tuple[int, int, int, int, int, int, int, in "f_flag", "f_namemax", ) + @property def f_bsize(self) -> int: ... @property @@ -440,12 +493,13 @@ def get_exec_path(env: Mapping[str, str] | None = None) -> list[str]: ... def getlogin() -> str: ... def getpid() -> int: ... def getppid() -> int: ... -def strerror(__code: int) -> str: ... -def umask(__mask: int) -> int: ... +def strerror(code: int, /) -> str: ... +def umask(mask: int, /) -> int: ... @final class uname_result(structseq[str], tuple[str, str, str, str, str]): if sys.version_info >= (3, 10): __match_args__: Final = ("sysname", "nodename", "release", "version", "machine") + @property def sysname(self) -> str: ... @property @@ -462,9 +516,9 @@ if sys.platform != "win32": def getegid() -> int: ... def geteuid() -> int: ... def getgid() -> int: ... - def getgrouplist(__user: str, __group: int) -> list[int]: ... + def getgrouplist(user: str, group: int, /) -> list[int]: ... def getgroups() -> list[int]: ... # Unix only, behaves differently on Mac - def initgroups(__username: str, __gid: int) -> None: ... + def initgroups(username: str, gid: int, /) -> None: ... def getpgid(pid: int) -> int: ... def getpgrp() -> int: ... def getpriority(which: int, who: int) -> int: ... @@ -474,21 +528,21 @@ if sys.platform != "win32": def getresgid() -> tuple[int, int, int]: ... def getuid() -> int: ... - def setegid(__egid: int) -> None: ... - def seteuid(__euid: int) -> None: ... - def setgid(__gid: int) -> None: ... - def setgroups(__groups: Sequence[int]) -> None: ... + def setegid(egid: int, /) -> None: ... + def seteuid(euid: int, /) -> None: ... + def setgid(gid: int, /) -> None: ... + def setgroups(groups: Sequence[int], /) -> None: ... def setpgrp() -> None: ... - def setpgid(__pid: int, __pgrp: int) -> None: ... - def setregid(__rgid: int, __egid: int) -> None: ... + def setpgid(pid: int, pgrp: int, /) -> None: ... + def setregid(rgid: int, egid: int, /) -> None: ... if sys.platform != "darwin": - def setresgid(rgid: int, egid: int, sgid: int) -> None: ... - def setresuid(ruid: int, euid: int, suid: int) -> None: ... + def setresgid(rgid: int, egid: int, sgid: int, /) -> None: ... + def setresuid(ruid: int, euid: int, suid: int, /) -> None: ... - def setreuid(__ruid: int, __euid: int) -> None: ... - def getsid(__pid: int) -> int: ... + def setreuid(ruid: int, euid: int, /) -> None: ... + def getsid(pid: int, /) -> int: ... def setsid() -> None: ... - def setuid(__uid: int) -> None: ... + def setuid(uid: int, /) -> None: ... def uname() -> uname_result: ... @overload @@ -501,14 +555,14 @@ if sys.platform != "win32": def getenvb(key: bytes) -> bytes | None: ... @overload def getenvb(key: bytes, default: _T) -> bytes | _T: ... - def putenv(__name: StrOrBytesPath, __value: StrOrBytesPath) -> None: ... - def unsetenv(__name: StrOrBytesPath) -> None: ... + def putenv(name: StrOrBytesPath, value: StrOrBytesPath, /) -> None: ... + def unsetenv(name: StrOrBytesPath, /) -> None: ... else: - def putenv(__name: str, __value: str) -> None: ... + def putenv(name: str, value: str, /) -> None: ... if sys.version_info >= (3, 9): - def unsetenv(__name: str) -> None: ... + def unsetenv(name: str, /) -> None: ... _Opener: TypeAlias = Callable[[str, int], int] @@ -590,43 +644,50 @@ def fdopen( opener: _Opener | None = ..., ) -> IO[Any]: ... def close(fd: int) -> None: ... -def closerange(__fd_low: int, __fd_high: int) -> None: ... +def closerange(fd_low: int, fd_high: int, /) -> None: ... def device_encoding(fd: int) -> str | None: ... -def dup(__fd: int) -> int: ... +def dup(fd: int, /) -> int: ... def dup2(fd: int, fd2: int, inheritable: bool = True) -> int: ... def fstat(fd: int) -> stat_result: ... -def ftruncate(__fd: int, __length: int) -> None: ... +def ftruncate(fd: int, length: int, /) -> None: ... def fsync(fd: FileDescriptorLike) -> None: ... -def isatty(__fd: int) -> bool: ... +def isatty(fd: int, /) -> bool: ... if sys.platform != "win32" and sys.version_info >= (3, 11): - def login_tty(__fd: int) -> None: ... + def login_tty(fd: int, /) -> None: ... + +if sys.version_info >= (3, 11): + def lseek(fd: int, position: int, whence: int, /) -> int: ... + +else: + def lseek(fd: int, position: int, how: int, /) -> int: ... -def lseek(__fd: int, __position: int, __how: int) -> int: ... def open(path: StrOrBytesPath, flags: int, mode: int = 0o777, *, dir_fd: int | None = None) -> int: ... def pipe() -> tuple[int, int]: ... -def read(__fd: int, __length: int) -> bytes: ... +def read(fd: int, length: int, /) -> bytes: ... + +if sys.version_info >= (3, 12) or sys.platform != "win32": + def get_blocking(fd: int, /) -> bool: ... + def set_blocking(fd: int, blocking: bool, /) -> None: ... if sys.platform != "win32": def fchmod(fd: int, mode: int) -> None: ... def fchown(fd: int, uid: int, gid: int) -> None: ... - def fpathconf(__fd: int, __name: str | int) -> int: ... - def fstatvfs(__fd: int) -> statvfs_result: ... - def get_blocking(__fd: int) -> bool: ... - def set_blocking(__fd: int, __blocking: bool) -> None: ... - def lockf(__fd: int, __command: int, __length: int) -> None: ... + def fpathconf(fd: int, name: str | int, /) -> int: ... + def fstatvfs(fd: int, /) -> statvfs_result: ... + def lockf(fd: int, command: int, length: int, /) -> None: ... def openpty() -> tuple[int, int]: ... # some flavors of Unix if sys.platform != "darwin": def fdatasync(fd: FileDescriptorLike) -> None: ... - def pipe2(__flags: int) -> tuple[int, int]: ... # some flavors of Unix - def posix_fallocate(__fd: int, __offset: int, __length: int) -> None: ... - def posix_fadvise(__fd: int, __offset: int, __length: int, __advice: int) -> None: ... + def pipe2(flags: int, /) -> tuple[int, int]: ... # some flavors of Unix + def posix_fallocate(fd: int, offset: int, length: int, /) -> None: ... + def posix_fadvise(fd: int, offset: int, length: int, advice: int, /) -> None: ... - def pread(__fd: int, __length: int, __offset: int) -> bytes: ... - def pwrite(__fd: int, __buffer: ReadableBuffer, __offset: int) -> int: ... + def pread(fd: int, length: int, offset: int, /) -> bytes: ... + def pwrite(fd: int, buffer: ReadableBuffer, offset: int, /) -> int: ... # In CI, stubtest sometimes reports that these are available on MacOS, sometimes not - def preadv(__fd: int, __buffers: SupportsLenAndGetItem[WriteableBuffer], __offset: int, __flags: int = 0) -> int: ... - def pwritev(__fd: int, __buffers: SupportsLenAndGetItem[ReadableBuffer], __offset: int, __flags: int = 0) -> int: ... + def preadv(fd: int, buffers: SupportsLenAndGetItem[WriteableBuffer], offset: int, flags: int = 0, /) -> int: ... + def pwritev(fd: int, buffers: SupportsLenAndGetItem[ReadableBuffer], offset: int, flags: int = 0, /) -> int: ... if sys.platform != "darwin": if sys.version_info >= (3, 10): RWF_APPEND: int # docs say available on 3.7+, stubtest says otherwise @@ -634,45 +695,48 @@ if sys.platform != "win32": RWF_SYNC: int RWF_HIPRI: int RWF_NOWAIT: int - @overload - def sendfile(out_fd: int, in_fd: int, offset: int | None, count: int) -> int: ... - @overload - def sendfile( - out_fd: int, - in_fd: int, - offset: int, - count: int, - headers: Sequence[ReadableBuffer] = ..., - trailers: Sequence[ReadableBuffer] = ..., - flags: int = 0, - ) -> int: ... # FreeBSD and Mac OS X only - def readv(__fd: int, __buffers: SupportsLenAndGetItem[WriteableBuffer]) -> int: ... - def writev(__fd: int, __buffers: SupportsLenAndGetItem[ReadableBuffer]) -> int: ... + + if sys.platform == "linux": + def sendfile(out_fd: FileDescriptor, in_fd: FileDescriptor, offset: int | None, count: int) -> int: ... + else: + def sendfile( + out_fd: FileDescriptor, + in_fd: FileDescriptor, + offset: int, + count: int, + headers: Sequence[ReadableBuffer] = ..., + trailers: Sequence[ReadableBuffer] = ..., + flags: int = 0, + ) -> int: ... # FreeBSD and Mac OS X only + + def readv(fd: int, buffers: SupportsLenAndGetItem[WriteableBuffer], /) -> int: ... + def writev(fd: int, buffers: SupportsLenAndGetItem[ReadableBuffer], /) -> int: ... @final class terminal_size(structseq[int], tuple[int, int]): if sys.version_info >= (3, 10): __match_args__: Final = ("columns", "lines") + @property def columns(self) -> int: ... @property def lines(self) -> int: ... -def get_terminal_size(__fd: int = ...) -> terminal_size: ... -def get_inheritable(__fd: int) -> bool: ... -def set_inheritable(__fd: int, __inheritable: bool) -> None: ... +def get_terminal_size(fd: int = ..., /) -> terminal_size: ... +def get_inheritable(fd: int, /) -> bool: ... +def set_inheritable(fd: int, inheritable: bool, /) -> None: ... if sys.platform == "win32": - def get_handle_inheritable(__handle: int) -> bool: ... - def set_handle_inheritable(__handle: int, __inheritable: bool) -> None: ... + def get_handle_inheritable(handle: int, /) -> bool: ... + def set_handle_inheritable(handle: int, inheritable: bool, /) -> None: ... if sys.platform != "win32": # Unix only - def tcgetpgrp(__fd: int) -> int: ... - def tcsetpgrp(__fd: int, __pgid: int) -> None: ... - def ttyname(__fd: int) -> str: ... + def tcgetpgrp(fd: int, /) -> int: ... + def tcsetpgrp(fd: int, pgid: int, /) -> None: ... + def ttyname(fd: int, /) -> str: ... -def write(__fd: int, __data: ReadableBuffer) -> int: ... +def write(fd: int, data: ReadableBuffer, /) -> int: ... def access( path: FileDescriptorOrPath, mode: int, *, dir_fd: int | None = None, effective_ids: bool = False, follow_symlinks: bool = True ) -> bool: ... @@ -683,7 +747,7 @@ if sys.platform != "win32": def getcwd() -> str: ... def getcwdb() -> bytes: ... -def chmod(path: FileDescriptorOrPath, mode: int, *, dir_fd: int | None = None, follow_symlinks: bool = True) -> None: ... +def chmod(path: FileDescriptorOrPath, mode: int, *, dir_fd: int | None = None, follow_symlinks: bool = ...) -> None: ... if sys.platform != "win32" and sys.platform != "linux": def chflags(path: StrOrBytesPath, flags: int, follow_symlinks: bool = True) -> None: ... # some flavors of Unix @@ -715,9 +779,9 @@ def makedirs(name: StrOrBytesPath, mode: int = 0o777, exist_ok: bool = False) -> if sys.platform != "win32": def mknod(path: StrOrBytesPath, mode: int = 0o600, device: int = 0, *, dir_fd: int | None = None) -> None: ... - def major(__device: int) -> int: ... - def minor(__device: int) -> int: ... - def makedev(__major: int, __minor: int) -> int: ... + def major(device: int, /) -> int: ... + def minor(device: int, /) -> int: ... + def makedev(major: int, minor: int, /) -> int: ... def pathconf(path: FileDescriptorOrPath, name: str | int) -> int: ... # Unix only def readlink(path: GenericPath[AnyStr], *, dir_fd: int | None = None) -> AnyStr: ... @@ -730,7 +794,7 @@ def replace( ) -> None: ... def rmdir(path: StrOrBytesPath, *, dir_fd: int | None = None) -> None: ... -class _ScandirIterator(Iterator[DirEntry[AnyStr]], AbstractContextManager[_ScandirIterator[AnyStr]]): +class _ScandirIterator(Iterator[DirEntry[AnyStr]], AbstractContextManager[_ScandirIterator[AnyStr], None]): def __next__(self) -> DirEntry[AnyStr]: ... def __exit__(self, *args: Unused) -> None: ... def close(self) -> None: ... @@ -805,12 +869,16 @@ if sys.platform != "win32": def abort() -> NoReturn: ... # These are defined as execl(file, *args) but the first *arg is mandatory. -def execl(file: StrOrBytesPath, __arg0: StrOrBytesPath, *args: StrOrBytesPath) -> NoReturn: ... -def execlp(file: StrOrBytesPath, __arg0: StrOrBytesPath, *args: StrOrBytesPath) -> NoReturn: ... +def execl(file: StrOrBytesPath, *args: Unpack[tuple[StrOrBytesPath, Unpack[tuple[StrOrBytesPath, ...]]]]) -> NoReturn: ... +def execlp(file: StrOrBytesPath, *args: Unpack[tuple[StrOrBytesPath, Unpack[tuple[StrOrBytesPath, ...]]]]) -> NoReturn: ... # These are: execle(file, *args, env) but env is pulled from the last element of the args. -def execle(file: StrOrBytesPath, __arg0: StrOrBytesPath, *args: Any) -> NoReturn: ... -def execlpe(file: StrOrBytesPath, __arg0: StrOrBytesPath, *args: Any) -> NoReturn: ... +def execle( + file: StrOrBytesPath, *args: Unpack[tuple[StrOrBytesPath, Unpack[tuple[StrOrBytesPath, ...]], _ExecEnv]] +) -> NoReturn: ... +def execlpe( + file: StrOrBytesPath, *args: Unpack[tuple[StrOrBytesPath, Unpack[tuple[StrOrBytesPath, ...]], _ExecEnv]] +) -> NoReturn: ... # The docs say `args: tuple or list of strings` # The implementation enforces tuple or list so we can't use Sequence. @@ -833,21 +901,21 @@ _ExecVArgs: TypeAlias = ( # we limit to str | bytes. _ExecEnv: TypeAlias = Mapping[bytes, bytes | str] | Mapping[str, bytes | str] -def execv(__path: StrOrBytesPath, __argv: _ExecVArgs) -> NoReturn: ... +def execv(path: StrOrBytesPath, argv: _ExecVArgs, /) -> NoReturn: ... def execve(path: FileDescriptorOrPath, argv: _ExecVArgs, env: _ExecEnv) -> NoReturn: ... def execvp(file: StrOrBytesPath, args: _ExecVArgs) -> NoReturn: ... def execvpe(file: StrOrBytesPath, args: _ExecVArgs, env: _ExecEnv) -> NoReturn: ... def _exit(status: int) -> NoReturn: ... -def kill(__pid: int, __signal: int) -> None: ... +def kill(pid: int, signal: int, /) -> None: ... if sys.platform != "win32": # Unix only def fork() -> int: ... def forkpty() -> tuple[int, int]: ... # some flavors of Unix - def killpg(__pgid: int, __signal: int) -> None: ... - def nice(__increment: int) -> int: ... + def killpg(pgid: int, signal: int, /) -> None: ... + def nice(increment: int, /) -> int: ... if sys.platform != "darwin": - def plock(__op: int) -> None: ... # ???op is int? + def plock(op: int, /) -> None: ... # ???op is int? class _wrap_close(_TextIOWrapper): def __init__(self, stream: _TextIOWrapper, proc: Popen[str]) -> None: ... @@ -862,14 +930,15 @@ if sys.platform != "win32": def spawnve(mode: int, file: StrOrBytesPath, args: _ExecVArgs, env: _ExecEnv) -> int: ... else: - def spawnv(__mode: int, __path: StrOrBytesPath, __argv: _ExecVArgs) -> int: ... - def spawnve(__mode: int, __path: StrOrBytesPath, __argv: _ExecVArgs, __env: _ExecEnv) -> int: ... + def spawnv(mode: int, path: StrOrBytesPath, argv: _ExecVArgs, /) -> int: ... + def spawnve(mode: int, path: StrOrBytesPath, argv: _ExecVArgs, env: _ExecEnv, /) -> int: ... def system(command: StrOrBytesPath) -> int: ... @final class times_result(structseq[float], tuple[float, float, float, float, float]): if sys.version_info >= (3, 10): __match_args__: Final = ("user", "system", "children_user", "children_system", "elapsed") + @property def user(self) -> float: ... @property @@ -882,10 +951,19 @@ class times_result(structseq[float], tuple[float, float, float, float, float]): def elapsed(self) -> float: ... def times() -> times_result: ... -def waitpid(__pid: int, __options: int) -> tuple[int, int]: ... +def waitpid(pid: int, options: int, /) -> tuple[int, int]: ... if sys.platform == "win32": - def startfile(path: StrOrBytesPath, operation: str | None = None) -> None: ... + if sys.version_info >= (3, 10): + def startfile( + filepath: StrOrBytesPath, + operation: str = ..., + arguments: str = "", + cwd: StrOrBytesPath | None = None, + show_cmd: int = 1, + ) -> None: ... + else: + def startfile(filepath: StrOrBytesPath, operation: str = ...) -> None: ... else: def spawnlp(mode: int, file: StrOrBytesPath, arg0: StrOrBytesPath, *args: StrOrBytesPath) -> int: ... @@ -898,6 +976,7 @@ else: class waitid_result(structseq[int], tuple[int, int, int, int, int]): if sys.version_info >= (3, 10): __match_args__: Final = ("si_pid", "si_uid", "si_signo", "si_status", "si_code") + @property def si_pid(self) -> int: ... @property @@ -909,11 +988,13 @@ else: @property def si_code(self) -> int: ... - def waitid(__idtype: int, __ident: int, __options: int) -> waitid_result: ... + def waitid(idtype: int, ident: int, options: int, /) -> waitid_result | None: ... - def wait3(options: int) -> tuple[int, int, Any]: ... - def wait4(pid: int, options: int) -> tuple[int, int, Any]: ... - def WCOREDUMP(__status: int) -> bool: ... + from resource import struct_rusage + + def wait3(options: int) -> tuple[int, int, struct_rusage]: ... + def wait4(pid: int, options: int) -> tuple[int, int, struct_rusage]: ... + def WCOREDUMP(status: int, /) -> bool: ... def WIFCONTINUED(status: int) -> bool: ... def WIFSTOPPED(status: int) -> bool: ... def WIFSIGNALED(status: int) -> bool: ... @@ -921,42 +1002,44 @@ else: def WEXITSTATUS(status: int) -> int: ... def WSTOPSIG(status: int) -> int: ... def WTERMSIG(status: int) -> int: ... - if sys.version_info >= (3, 8): - def posix_spawn( - path: StrOrBytesPath, - argv: _ExecVArgs, - env: _ExecEnv, - *, - file_actions: Sequence[tuple[Any, ...]] | None = ..., - setpgroup: int | None = ..., - resetids: bool = ..., - setsid: bool = ..., - setsigmask: Iterable[int] = ..., - setsigdef: Iterable[int] = ..., - scheduler: tuple[Any, sched_param] | None = ..., - ) -> int: ... - def posix_spawnp( - path: StrOrBytesPath, - argv: _ExecVArgs, - env: _ExecEnv, - *, - file_actions: Sequence[tuple[Any, ...]] | None = ..., - setpgroup: int | None = ..., - resetids: bool = ..., - setsid: bool = ..., - setsigmask: Iterable[int] = ..., - setsigdef: Iterable[int] = ..., - scheduler: tuple[Any, sched_param] | None = ..., - ) -> int: ... - POSIX_SPAWN_OPEN: int - POSIX_SPAWN_CLOSE: int - POSIX_SPAWN_DUP2: int + def posix_spawn( + path: StrOrBytesPath, + argv: _ExecVArgs, + env: _ExecEnv, + /, + *, + file_actions: Sequence[tuple[Any, ...]] | None = ..., + setpgroup: int | None = ..., + resetids: bool = ..., + setsid: bool = ..., + setsigmask: Iterable[int] = ..., + setsigdef: Iterable[int] = ..., + scheduler: tuple[Any, sched_param] | None = ..., + ) -> int: ... + def posix_spawnp( + path: StrOrBytesPath, + argv: _ExecVArgs, + env: _ExecEnv, + /, + *, + file_actions: Sequence[tuple[Any, ...]] | None = ..., + setpgroup: int | None = ..., + resetids: bool = ..., + setsid: bool = ..., + setsigmask: Iterable[int] = ..., + setsigdef: Iterable[int] = ..., + scheduler: tuple[Any, sched_param] | None = ..., + ) -> int: ... + POSIX_SPAWN_OPEN: int + POSIX_SPAWN_CLOSE: int + POSIX_SPAWN_DUP2: int if sys.platform != "win32": @final class sched_param(structseq[int], tuple[int]): if sys.version_info >= (3, 10): __match_args__: Final = ("sched_priority",) + def __new__(cls, sched_priority: int) -> Self: ... @property def sched_priority(self) -> int: ... @@ -965,26 +1048,26 @@ if sys.platform != "win32": def sched_get_priority_max(policy: int) -> int: ... # some flavors of Unix def sched_yield() -> None: ... # some flavors of Unix if sys.platform != "darwin": - def sched_setscheduler(__pid: int, __policy: int, __param: sched_param) -> None: ... # some flavors of Unix - def sched_getscheduler(__pid: int) -> int: ... # some flavors of Unix - def sched_rr_get_interval(__pid: int) -> float: ... # some flavors of Unix - def sched_setparam(__pid: int, __param: sched_param) -> None: ... # some flavors of Unix - def sched_getparam(__pid: int) -> sched_param: ... # some flavors of Unix - def sched_setaffinity(__pid: int, __mask: Iterable[int]) -> None: ... # some flavors of Unix - def sched_getaffinity(__pid: int) -> set[int]: ... # some flavors of Unix + def sched_setscheduler(pid: int, policy: int, param: sched_param, /) -> None: ... # some flavors of Unix + def sched_getscheduler(pid: int, /) -> int: ... # some flavors of Unix + def sched_rr_get_interval(pid: int, /) -> float: ... # some flavors of Unix + def sched_setparam(pid: int, param: sched_param, /) -> None: ... # some flavors of Unix + def sched_getparam(pid: int, /) -> sched_param: ... # some flavors of Unix + def sched_setaffinity(pid: int, mask: Iterable[int], /) -> None: ... # some flavors of Unix + def sched_getaffinity(pid: int, /) -> set[int]: ... # some flavors of Unix def cpu_count() -> int | None: ... if sys.platform != "win32": # Unix only - def confstr(__name: str | int) -> str | None: ... + def confstr(name: str | int, /) -> str | None: ... def getloadavg() -> tuple[float, float, float]: ... - def sysconf(__name: str | int) -> int: ... + def sysconf(name: str | int, /) -> int: ... if sys.platform == "linux": def getrandom(size: int, flags: int = 0) -> bytes: ... -def urandom(__size: int) -> bytes: ... +def urandom(size: int, /) -> bytes: ... if sys.platform != "win32": def register_at_fork( @@ -994,41 +1077,81 @@ if sys.platform != "win32": after_in_child: Callable[..., Any] | None = ..., ) -> None: ... -if sys.version_info >= (3, 8): - if sys.platform == "win32": - class _AddedDllDirectory: - path: str | None - def __init__(self, path: str | None, cookie: _T, remove_dll_directory: Callable[[_T], object]) -> None: ... - def close(self) -> None: ... - def __enter__(self) -> Self: ... - def __exit__(self, *args: Unused) -> None: ... - - def add_dll_directory(path: str) -> _AddedDllDirectory: ... - if sys.platform == "linux": - MFD_CLOEXEC: int - MFD_ALLOW_SEALING: int - MFD_HUGETLB: int - MFD_HUGE_SHIFT: int - MFD_HUGE_MASK: int - MFD_HUGE_64KB: int - MFD_HUGE_512KB: int - MFD_HUGE_1MB: int - MFD_HUGE_2MB: int - MFD_HUGE_8MB: int - MFD_HUGE_16MB: int - MFD_HUGE_32MB: int - MFD_HUGE_256MB: int - MFD_HUGE_512MB: int - MFD_HUGE_1GB: int - MFD_HUGE_2GB: int - MFD_HUGE_16GB: int - def memfd_create(name: str, flags: int = ...) -> int: ... - def copy_file_range( - src: int, dst: int, count: int, offset_src: int | None = ..., offset_dst: int | None = ... - ) -> int: ... +if sys.platform == "win32": + class _AddedDllDirectory: + path: str | None + def __init__(self, path: str | None, cookie: _T, remove_dll_directory: Callable[[_T], object]) -> None: ... + def close(self) -> None: ... + def __enter__(self) -> Self: ... + def __exit__(self, *args: Unused) -> None: ... + + def add_dll_directory(path: str) -> _AddedDllDirectory: ... + +if sys.platform == "linux": + MFD_CLOEXEC: int + MFD_ALLOW_SEALING: int + MFD_HUGETLB: int + MFD_HUGE_SHIFT: int + MFD_HUGE_MASK: int + MFD_HUGE_64KB: int + MFD_HUGE_512KB: int + MFD_HUGE_1MB: int + MFD_HUGE_2MB: int + MFD_HUGE_8MB: int + MFD_HUGE_16MB: int + MFD_HUGE_32MB: int + MFD_HUGE_256MB: int + MFD_HUGE_512MB: int + MFD_HUGE_1GB: int + MFD_HUGE_2GB: int + MFD_HUGE_16GB: int + def memfd_create(name: str, flags: int = ...) -> int: ... + def copy_file_range(src: int, dst: int, count: int, offset_src: int | None = ..., offset_dst: int | None = ...) -> int: ... if sys.version_info >= (3, 9): def waitstatus_to_exitcode(status: int) -> int: ... if sys.platform == "linux": def pidfd_open(pid: int, flags: int = ...) -> int: ... + +if sys.version_info >= (3, 12) and sys.platform == "win32": + def listdrives() -> list[str]: ... + def listmounts(volume: str) -> list[str]: ... + def listvolumes() -> list[str]: ... + +if sys.version_info >= (3, 10) and sys.platform == "linux": + EFD_CLOEXEC: int + EFD_NONBLOCK: int + EFD_SEMAPHORE: int + SPLICE_F_MORE: int + SPLICE_F_MOVE: int + SPLICE_F_NONBLOCK: int + def eventfd(initval: int, flags: int = 524288) -> FileDescriptor: ... + def eventfd_read(fd: FileDescriptor) -> int: ... + def eventfd_write(fd: FileDescriptor, value: int) -> None: ... + def splice( + src: FileDescriptor, + dst: FileDescriptor, + count: int, + offset_src: int | None = ..., + offset_dst: int | None = ..., + flags: int = 0, + ) -> int: ... + +if sys.version_info >= (3, 12) and sys.platform == "linux": + CLONE_FILES: int + CLONE_FS: int + CLONE_NEWCGROUP: int + CLONE_NEWIPC: int + CLONE_NEWNET: int + CLONE_NEWNS: int + CLONE_NEWPID: int + CLONE_NEWTIME: int + CLONE_NEWUSER: int + CLONE_NEWUTS: int + CLONE_SIGHAND: int + CLONE_SYSVSEM: int + CLONE_THREAD: int + CLONE_VM: int + def unshare(flags: int) -> None: ... + def setns(fd: FileDescriptorLike, nstype: int = 0) -> None: ... diff --git a/mypy/typeshed/stdlib/ossaudiodev.pyi b/mypy/typeshed/stdlib/ossaudiodev.pyi index d956a89729fd..b9ee3edab033 100644 --- a/mypy/typeshed/stdlib/ossaudiodev.pyi +++ b/mypy/typeshed/stdlib/ossaudiodev.pyi @@ -1,6 +1,5 @@ import sys -from typing import Any, overload -from typing_extensions import Literal +from typing import Any, Literal, overload if sys.platform != "win32" and sys.platform != "darwin": AFMT_AC3: int diff --git a/mypy/typeshed/stdlib/parser.pyi b/mypy/typeshed/stdlib/parser.pyi index cce8594eac58..bafc8015fed9 100644 --- a/mypy/typeshed/stdlib/parser.pyi +++ b/mypy/typeshed/stdlib/parser.pyi @@ -1,8 +1,7 @@ from _typeshed import StrOrBytesPath from collections.abc import Sequence from types import CodeType -from typing import Any -from typing_extensions import final +from typing import Any, final def expr(source: str) -> STType: ... def suite(source: str) -> STType: ... diff --git a/mypy/typeshed/stdlib/pathlib.pyi b/mypy/typeshed/stdlib/pathlib.pyi index 114678ed574d..0013e221f2e1 100644 --- a/mypy/typeshed/stdlib/pathlib.pyi +++ b/mypy/typeshed/stdlib/pathlib.pyi @@ -8,13 +8,14 @@ from _typeshed import ( ReadableBuffer, StrOrBytesPath, StrPath, + Unused, ) from collections.abc import Callable, Generator, Iterator, Sequence from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper from os import PathLike, stat_result from types import TracebackType -from typing import IO, Any, BinaryIO, overload -from typing_extensions import Literal, Self +from typing import IO, Any, BinaryIO, Literal, overload +from typing_extensions import Self, deprecated if sys.version_info >= (3, 9): from types import GenericAlias @@ -38,8 +39,13 @@ class PurePath(PathLike[str]): def suffixes(self) -> list[str]: ... @property def stem(self) -> str: ... - def __new__(cls, *args: StrPath) -> Self: ... - def __eq__(self, other: object) -> bool: ... + if sys.version_info >= (3, 12): + def __new__(cls, *args: StrPath, **kwargs: Unused) -> Self: ... + def __init__(self, *args: StrPath) -> None: ... + else: + def __new__(cls, *args: StrPath) -> Self: ... + + def __hash__(self) -> int: ... def __fspath__(self) -> str: ... def __lt__(self, other: PurePath) -> bool: ... def __le__(self, other: PurePath) -> bool: ... @@ -52,11 +58,21 @@ class PurePath(PathLike[str]): def as_uri(self) -> str: ... def is_absolute(self) -> bool: ... def is_reserved(self) -> bool: ... - if sys.version_info >= (3, 9): + if sys.version_info >= (3, 12): + def is_relative_to(self, other: StrPath, /, *_deprecated: StrPath) -> bool: ... + elif sys.version_info >= (3, 9): def is_relative_to(self, *other: StrPath) -> bool: ... - def match(self, path_pattern: str) -> bool: ... - def relative_to(self, *other: StrPath) -> Self: ... + if sys.version_info >= (3, 12): + def match(self, path_pattern: str, *, case_sensitive: bool | None = None) -> bool: ... + else: + def match(self, path_pattern: str) -> bool: ... + + if sys.version_info >= (3, 12): + def relative_to(self, other: StrPath, /, *_deprecated: StrPath, walk_up: bool = False) -> Self: ... + else: + def relative_to(self, *other: StrPath) -> Self: ... + def with_name(self, name: str) -> Self: ... if sys.version_info >= (3, 9): def with_stem(self, stem: str) -> Self: ... @@ -70,6 +86,9 @@ class PurePath(PathLike[str]): if sys.version_info >= (3, 9) and sys.version_info < (3, 11): def __class_getitem__(cls, type: Any) -> GenericAlias: ... + if sys.version_info >= (3, 12): + def with_segments(self, *args: StrPath) -> Self: ... + class PurePosixPath(PurePath): ... class PureWindowsPath(PurePath): ... @@ -86,8 +105,15 @@ class Path(PurePath): def stat(self) -> stat_result: ... def chmod(self, mode: int) -> None: ... - def exists(self) -> bool: ... - def glob(self, pattern: str) -> Generator[Self, None, None]: ... + if sys.version_info >= (3, 12): + def exists(self, *, follow_symlinks: bool = True) -> bool: ... + def glob(self, pattern: str, *, case_sensitive: bool | None = None) -> Generator[Self, None, None]: ... + def rglob(self, pattern: str, *, case_sensitive: bool | None = None) -> Generator[Self, None, None]: ... + else: + def exists(self) -> bool: ... + def glob(self, pattern: str) -> Generator[Self, None, None]: ... + def rglob(self, pattern: str) -> Generator[Self, None, None]: ... + def is_dir(self) -> bool: ... def is_file(self) -> bool: ... def is_symlink(self) -> bool: ... @@ -95,6 +121,9 @@ class Path(PurePath): def is_fifo(self) -> bool: ... def is_block_device(self) -> bool: ... def is_char_device(self) -> bool: ... + if sys.version_info >= (3, 12): + def is_junction(self) -> bool: ... + def iterdir(self) -> Generator[Self, None, None]: ... def lchmod(self, mode: int) -> None: ... def lstat(self) -> stat_result: ... @@ -159,30 +188,25 @@ class Path(PurePath): # so it's safer to pretend they don't exist def owner(self) -> str: ... def group(self) -> str: ... + + # This method does "exist" on Windows on <3.12, but always raises NotImplementedError + # On py312+, it works properly on Windows, as with all other platforms + if sys.platform != "win32" or sys.version_info >= (3, 12): def is_mount(self) -> bool: ... if sys.version_info >= (3, 9): def readlink(self) -> Self: ... - if sys.version_info >= (3, 8): - def rename(self, target: str | PurePath) -> Self: ... - def replace(self, target: str | PurePath) -> Self: ... - else: - def rename(self, target: str | PurePath) -> None: ... - def replace(self, target: str | PurePath) -> None: ... + def rename(self, target: str | PurePath) -> Self: ... + def replace(self, target: str | PurePath) -> Self: ... def resolve(self, strict: bool = False) -> Self: ... - def rglob(self, pattern: str) -> Generator[Self, None, None]: ... def rmdir(self) -> None: ... - def symlink_to(self, target: str | Path, target_is_directory: bool = False) -> None: ... + def symlink_to(self, target: StrOrBytesPath, target_is_directory: bool = False) -> None: ... if sys.version_info >= (3, 10): - def hardlink_to(self, target: str | Path) -> None: ... + def hardlink_to(self, target: StrOrBytesPath) -> None: ... def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None: ... - if sys.version_info >= (3, 8): - def unlink(self, missing_ok: bool = False) -> None: ... - else: - def unlink(self) -> None: ... - + def unlink(self, missing_ok: bool = False) -> None: ... @classmethod def home(cls) -> Self: ... def absolute(self) -> Self: ... @@ -197,8 +221,12 @@ class Path(PurePath): ) -> int: ... else: def write_text(self, data: str, encoding: str | None = None, errors: str | None = None) -> int: ... - if sys.version_info >= (3, 8) and sys.version_info < (3, 12): - def link_to(self, target: StrOrBytesPath) -> None: ... + if sys.version_info < (3, 12): + if sys.version_info >= (3, 10): + @deprecated("Deprecated as of Python 3.10 and removed in Python 3.12. Use hardlink_to() instead.") + def link_to(self, target: StrOrBytesPath) -> None: ... + else: + def link_to(self, target: StrOrBytesPath) -> None: ... if sys.version_info >= (3, 12): def walk( self, top_down: bool = ..., on_error: Callable[[OSError], object] | None = ..., follow_symlinks: bool = ... diff --git a/mypy/typeshed/stdlib/pdb.pyi b/mypy/typeshed/stdlib/pdb.pyi index e2871bb54fa0..4cc708d9d5fe 100644 --- a/mypy/typeshed/stdlib/pdb.pyi +++ b/mypy/typeshed/stdlib/pdb.pyi @@ -125,10 +125,13 @@ class Pdb(Bdb, Cmd): def sigint_handler(self, signum: signal.Signals, frame: FrameType) -> None: ... def message(self, msg: str) -> None: ... def error(self, msg: str) -> None: ... + if sys.version_info >= (3, 12): + def set_convenience_variable(self, frame: FrameType, name: str, value: Any) -> None: ... + def _select_frame(self, number: int) -> None: ... def _getval_except(self, arg: str, frame: FrameType | None = None) -> object: ... def _print_lines( - self, lines: Sequence[str], start: int, breaks: Sequence[int] = ..., frame: FrameType | None = None + self, lines: Sequence[str], start: int, breaks: Sequence[int] = (), frame: FrameType | None = None ) -> None: ... def _cmdloop(self) -> None: ... def do_display(self, arg: str) -> bool | None: ... @@ -168,7 +171,10 @@ class Pdb(Bdb, Cmd): def find_function(funcname: str, filename: str) -> tuple[str, str, int] | None: ... def main() -> None: ... def help() -> None: ... -def getsourcelines(obj: _SourceObjectType) -> tuple[list[str], int]: ... + +if sys.version_info < (3, 10): + def getsourcelines(obj: _SourceObjectType) -> tuple[list[str], int]: ... + def lasti2lineno(code: CodeType, lasti: int) -> int: ... class _rstr(str): diff --git a/mypy/typeshed/stdlib/pickle.pyi b/mypy/typeshed/stdlib/pickle.pyi index 57c4cb03e484..98ec80b0f14e 100644 --- a/mypy/typeshed/stdlib/pickle.pyi +++ b/mypy/typeshed/stdlib/pickle.pyi @@ -1,10 +1,10 @@ -import sys from _typeshed import ReadableBuffer, SupportsWrite from collections.abc import Callable, Iterable, Iterator, Mapping -from typing import Any, ClassVar, Protocol, SupportsBytes -from typing_extensions import SupportsIndex, TypeAlias, final +from typing import Any, ClassVar, Protocol, SupportsBytes, SupportsIndex, final +from typing_extensions import TypeAlias __all__ = [ + "PickleBuffer", "PickleError", "PicklingError", "UnpicklingError", @@ -30,6 +30,7 @@ __all__ = [ "BINUNICODE", "BINUNICODE8", "BUILD", + "BYTEARRAY8", "DEFAULT_PROTOCOL", "DICT", "DUP", @@ -61,6 +62,7 @@ __all__ = [ "NEWOBJ", "NEWOBJ_EX", "NEWTRUE", + "NEXT_BUFFER", "NONE", "OBJ", "PERSID", @@ -68,6 +70,7 @@ __all__ = [ "POP_MARK", "PROTO", "PUT", + "READONLY_BUFFER", "REDUCE", "SETITEM", "SETITEMS", @@ -85,58 +88,53 @@ __all__ = [ "UNICODE", ] -if sys.version_info >= (3, 8): - __all__ += ["BYTEARRAY8", "NEXT_BUFFER", "PickleBuffer", "READONLY_BUFFER"] - HIGHEST_PROTOCOL: int DEFAULT_PROTOCOL: int bytes_types: tuple[type[Any], ...] # undocumented class _ReadableFileobj(Protocol): - def read(self, __n: int) -> bytes: ... + def read(self, n: int, /) -> bytes: ... def readline(self) -> bytes: ... -if sys.version_info >= (3, 8): - @final - class PickleBuffer: - def __init__(self, buffer: ReadableBuffer) -> None: ... - def raw(self) -> memoryview: ... - def release(self) -> None: ... - _BufferCallback: TypeAlias = Callable[[PickleBuffer], Any] | None - def dump( - obj: Any, - file: SupportsWrite[bytes], - protocol: int | None = None, - *, - fix_imports: bool = True, - buffer_callback: _BufferCallback = None, - ) -> None: ... - def dumps( - obj: Any, protocol: int | None = None, *, fix_imports: bool = True, buffer_callback: _BufferCallback = None - ) -> bytes: ... - def load( - file: _ReadableFileobj, - *, - fix_imports: bool = True, - encoding: str = "ASCII", - errors: str = "strict", - buffers: Iterable[Any] | None = ..., - ) -> Any: ... - def loads( - __data: ReadableBuffer, - *, - fix_imports: bool = True, - encoding: str = "ASCII", - errors: str = "strict", - buffers: Iterable[Any] | None = ..., - ) -> Any: ... +@final +class PickleBuffer: + def __init__(self, buffer: ReadableBuffer) -> None: ... + def raw(self) -> memoryview: ... + def release(self) -> None: ... + def __buffer__(self, flags: int, /) -> memoryview: ... + def __release_buffer__(self, buffer: memoryview, /) -> None: ... + +_BufferCallback: TypeAlias = Callable[[PickleBuffer], Any] | None -else: - def dump(obj: Any, file: SupportsWrite[bytes], protocol: int | None = None, *, fix_imports: bool = True) -> None: ... - def dumps(obj: Any, protocol: int | None = None, *, fix_imports: bool = True) -> bytes: ... - def load(file: _ReadableFileobj, *, fix_imports: bool = True, encoding: str = "ASCII", errors: str = "strict") -> Any: ... - def loads(data: ReadableBuffer, *, fix_imports: bool = True, encoding: str = "ASCII", errors: str = "strict") -> Any: ... +def dump( + obj: Any, + file: SupportsWrite[bytes], + protocol: int | None = None, + *, + fix_imports: bool = True, + buffer_callback: _BufferCallback = None, +) -> None: ... +def dumps( + obj: Any, protocol: int | None = None, *, fix_imports: bool = True, buffer_callback: _BufferCallback = None +) -> bytes: ... +def load( + file: _ReadableFileobj, + *, + fix_imports: bool = True, + encoding: str = "ASCII", + errors: str = "strict", + buffers: Iterable[Any] | None = (), +) -> Any: ... +def loads( + data: ReadableBuffer, + /, + *, + fix_imports: bool = True, + encoding: str = "ASCII", + errors: str = "strict", + buffers: Iterable[Any] | None = (), +) -> Any: ... class PickleError(Exception): ... class PicklingError(PickleError): ... @@ -156,43 +154,33 @@ class Pickler: bin: bool # undocumented dispatch: ClassVar[dict[type, Callable[[Unpickler, Any], None]]] # undocumented, _Pickler only - if sys.version_info >= (3, 8): - def __init__( - self, - file: SupportsWrite[bytes], - protocol: int | None = ..., - *, - fix_imports: bool = ..., - buffer_callback: _BufferCallback = ..., - ) -> None: ... - def reducer_override(self, obj: Any) -> Any: ... - else: - def __init__(self, file: SupportsWrite[bytes], protocol: int | None = ..., *, fix_imports: bool = ...) -> None: ... - - def dump(self, __obj: Any) -> None: ... + def __init__( + self, + file: SupportsWrite[bytes], + protocol: int | None = ..., + *, + fix_imports: bool = ..., + buffer_callback: _BufferCallback = ..., + ) -> None: ... + def reducer_override(self, obj: Any) -> Any: ... + def dump(self, obj: Any, /) -> None: ... def clear_memo(self) -> None: ... def persistent_id(self, obj: Any) -> Any: ... class Unpickler: dispatch: ClassVar[dict[int, Callable[[Unpickler], None]]] # undocumented, _Unpickler only - if sys.version_info >= (3, 8): - def __init__( - self, - file: _ReadableFileobj, - *, - fix_imports: bool = ..., - encoding: str = ..., - errors: str = ..., - buffers: Iterable[Any] | None = ..., - ) -> None: ... - else: - def __init__( - self, file: _ReadableFileobj, *, fix_imports: bool = ..., encoding: str = ..., errors: str = ... - ) -> None: ... - + def __init__( + self, + file: _ReadableFileobj, + *, + fix_imports: bool = ..., + encoding: str = ..., + errors: str = ..., + buffers: Iterable[Any] | None = ..., + ) -> None: ... def load(self) -> Any: ... - def find_class(self, __module_name: str, __global_name: str) -> Any: ... + def find_class(self, module_name: str, global_name: str, /) -> Any: ... def persistent_load(self, pid: Any) -> Any: ... MARK: bytes @@ -270,11 +258,10 @@ STACK_GLOBAL: bytes MEMOIZE: bytes FRAME: bytes -if sys.version_info >= (3, 8): - # Protocol 5 - BYTEARRAY8: bytes - NEXT_BUFFER: bytes - READONLY_BUFFER: bytes +# protocol 5 +BYTEARRAY8: bytes +NEXT_BUFFER: bytes +READONLY_BUFFER: bytes def encode_long(x: int) -> bytes: ... # undocumented def decode_long(data: Iterable[SupportsIndex] | SupportsBytes | ReadableBuffer) -> int: ... # undocumented diff --git a/mypy/typeshed/stdlib/pkgutil.pyi b/mypy/typeshed/stdlib/pkgutil.pyi index f9808c9e5de8..7e7fa4fda9a1 100644 --- a/mypy/typeshed/stdlib/pkgutil.pyi +++ b/mypy/typeshed/stdlib/pkgutil.pyi @@ -1,8 +1,9 @@ import sys from _typeshed import SupportsRead +from _typeshed.importlib import LoaderProtocol, MetaPathFinderProtocol, PathEntryFinderProtocol from collections.abc import Callable, Iterable, Iterator -from importlib.abc import Loader, MetaPathFinder, PathEntryFinder from typing import IO, Any, NamedTuple, TypeVar +from typing_extensions import deprecated __all__ = [ "get_importer", @@ -12,32 +13,35 @@ __all__ = [ "walk_packages", "iter_modules", "get_data", - "ImpImporter", - "ImpLoader", "read_code", "extend_path", "ModuleInfo", ] +if sys.version_info < (3, 12): + __all__ += ["ImpImporter", "ImpLoader"] _PathT = TypeVar("_PathT", bound=Iterable[str]) class ModuleInfo(NamedTuple): - module_finder: MetaPathFinder | PathEntryFinder + module_finder: MetaPathFinderProtocol | PathEntryFinderProtocol name: str ispkg: bool def extend_path(path: _PathT, name: str) -> _PathT: ... -class ImpImporter: - def __init__(self, path: str | None = None) -> None: ... +if sys.version_info < (3, 12): + class ImpImporter: + def __init__(self, path: str | None = None) -> None: ... -class ImpLoader: - def __init__(self, fullname: str, file: IO[str], filename: str, etc: tuple[str, str, int]) -> None: ... + class ImpLoader: + def __init__(self, fullname: str, file: IO[str], filename: str, etc: tuple[str, str, int]) -> None: ... -def find_loader(fullname: str) -> Loader | None: ... -def get_importer(path_item: str) -> PathEntryFinder | None: ... -def get_loader(module_or_name: str) -> Loader | None: ... -def iter_importers(fullname: str = "") -> Iterator[MetaPathFinder | PathEntryFinder]: ... +@deprecated("Use importlib.util.find_spec() instead. Will be removed in Python 3.14.") +def find_loader(fullname: str) -> LoaderProtocol | None: ... +def get_importer(path_item: str) -> PathEntryFinderProtocol | None: ... +@deprecated("Use importlib.util.find_spec() instead. Will be removed in Python 3.14.") +def get_loader(module_or_name: str) -> LoaderProtocol | None: ... +def iter_importers(fullname: str = "") -> Iterator[MetaPathFinderProtocol | PathEntryFinderProtocol]: ... def iter_modules(path: Iterable[str] | None = None, prefix: str = "") -> Iterator[ModuleInfo]: ... def read_code(stream: SupportsRead[bytes]) -> Any: ... # undocumented def walk_packages( diff --git a/mypy/typeshed/stdlib/platform.pyi b/mypy/typeshed/stdlib/platform.pyi index 291f302b4c7d..f0e6d4123e1d 100644 --- a/mypy/typeshed/stdlib/platform.pyi +++ b/mypy/typeshed/stdlib/platform.pyi @@ -1,42 +1,15 @@ import sys - -if sys.version_info < (3, 8): - import os - - DEV_NULL = os.devnull from typing import NamedTuple -if sys.version_info >= (3, 8): - def libc_ver(executable: str | None = None, lib: str = "", version: str = "", chunksize: int = 16384) -> tuple[str, str]: ... - -else: - def libc_ver( - executable: str = sys.executable, lib: str = "", version: str = "", chunksize: int = 16384 - ) -> tuple[str, str]: ... - -if sys.version_info < (3, 8): - def linux_distribution( - distname: str = "", - version: str = "", - id: str = "", - supported_dists: tuple[str, ...] = ..., - full_distribution_name: bool = ..., - ) -> tuple[str, str, str]: ... - def dist( - distname: str = "", version: str = "", id: str = "", supported_dists: tuple[str, ...] = ... - ) -> tuple[str, str, str]: ... - +def libc_ver(executable: str | None = None, lib: str = "", version: str = "", chunksize: int = 16384) -> tuple[str, str]: ... def win32_ver(release: str = "", version: str = "", csd: str = "", ptype: str = "") -> tuple[str, str, str, str]: ... - -if sys.version_info >= (3, 8): - def win32_edition() -> str: ... - def win32_is_iot() -> bool: ... - +def win32_edition() -> str: ... +def win32_is_iot() -> bool: ... def mac_ver( - release: str = "", versioninfo: tuple[str, str, str] = ..., machine: str = "" + release: str = "", versioninfo: tuple[str, str, str] = ("", "", ""), machine: str = "" ) -> tuple[str, tuple[str, str, str], str]: ... def java_ver( - release: str = "", vendor: str = "", vminfo: tuple[str, str, str] = ..., osinfo: tuple[str, str, str] = ... + release: str = "", vendor: str = "", vminfo: tuple[str, str, str] = ("", "", ""), osinfo: tuple[str, str, str] = ("", "", "") ) -> tuple[str, str, tuple[str, str, str], tuple[str, str, str]]: ... def system_alias(system: str, release: str, version: str) -> tuple[str, str, str]: ... def architecture(executable: str = sys.executable, bits: str = "", linkage: str = "") -> tuple[str, str]: ... diff --git a/mypy/typeshed/stdlib/plistlib.pyi b/mypy/typeshed/stdlib/plistlib.pyi index 5b76c935f76e..09637673ce21 100644 --- a/mypy/typeshed/stdlib/plistlib.pyi +++ b/mypy/typeshed/stdlib/plistlib.pyi @@ -6,48 +6,33 @@ from enum import Enum from typing import IO, Any from typing_extensions import Self -if sys.version_info >= (3, 9): - __all__ = ["InvalidFileException", "FMT_XML", "FMT_BINARY", "load", "dump", "loads", "dumps", "UID"] -elif sys.version_info >= (3, 8): - __all__ = [ - "readPlist", - "writePlist", - "readPlistFromBytes", - "writePlistToBytes", - "Data", - "InvalidFileException", - "FMT_XML", - "FMT_BINARY", - "load", - "dump", - "loads", - "dumps", - "UID", - ] -else: - __all__ = [ - "readPlist", - "writePlist", - "readPlistFromBytes", - "writePlistToBytes", - "Data", - "InvalidFileException", - "FMT_XML", - "FMT_BINARY", - "load", - "dump", - "loads", - "dumps", - ] +__all__ = ["InvalidFileException", "FMT_XML", "FMT_BINARY", "load", "dump", "loads", "dumps", "UID"] +if sys.version_info < (3, 9): + __all__ += ["readPlist", "writePlist", "readPlistFromBytes", "writePlistToBytes", "Data"] class PlistFormat(Enum): - FMT_XML: int - FMT_BINARY: int + FMT_XML = 1 + FMT_BINARY = 2 FMT_XML = PlistFormat.FMT_XML FMT_BINARY = PlistFormat.FMT_BINARY +if sys.version_info >= (3, 13): + def load( + fp: IO[bytes], + *, + fmt: PlistFormat | None = None, + dict_type: type[MutableMapping[str, Any]] = ..., + aware_datetime: bool = False, + ) -> Any: ... + def loads( + value: ReadableBuffer | str, + *, + fmt: PlistFormat | None = None, + dict_type: type[MutableMapping[str, Any]] = ..., + aware_datetime: bool = False, + ) -> Any: ... -if sys.version_info >= (3, 9): +elif sys.version_info >= (3, 9): def load(fp: IO[bytes], *, fmt: PlistFormat | None = None, dict_type: type[MutableMapping[str, Any]] = ...) -> Any: ... def loads( value: ReadableBuffer, *, fmt: PlistFormat | None = None, dict_type: type[MutableMapping[str, Any]] = ... @@ -69,21 +54,41 @@ else: dict_type: type[MutableMapping[str, Any]] = ..., ) -> Any: ... -def dump( - value: Mapping[str, Any] | list[Any] | tuple[Any, ...] | str | bool | float | bytes | bytearray | datetime, - fp: IO[bytes], - *, - fmt: PlistFormat = ..., - sort_keys: bool = True, - skipkeys: bool = False, -) -> None: ... -def dumps( - value: Mapping[str, Any] | list[Any] | tuple[Any, ...] | str | bool | float | bytes | bytearray | datetime, - *, - fmt: PlistFormat = ..., - skipkeys: bool = False, - sort_keys: bool = True, -) -> bytes: ... +if sys.version_info >= (3, 13): + def dump( + value: Mapping[str, Any] | list[Any] | tuple[Any, ...] | str | bool | float | bytes | bytearray | datetime, + fp: IO[bytes], + *, + fmt: PlistFormat = ..., + sort_keys: bool = True, + skipkeys: bool = False, + aware_datetime: bool = False, + ) -> None: ... + def dumps( + value: Mapping[str, Any] | list[Any] | tuple[Any, ...] | str | bool | float | bytes | bytearray | datetime, + *, + fmt: PlistFormat = ..., + skipkeys: bool = False, + sort_keys: bool = True, + aware_datetime: bool = False, + ) -> bytes: ... + +else: + def dump( + value: Mapping[str, Any] | list[Any] | tuple[Any, ...] | str | bool | float | bytes | bytearray | datetime, + fp: IO[bytes], + *, + fmt: PlistFormat = ..., + sort_keys: bool = True, + skipkeys: bool = False, + ) -> None: ... + def dumps( + value: Mapping[str, Any] | list[Any] | tuple[Any, ...] | str | bool | float | bytes | bytearray | datetime, + *, + fmt: PlistFormat = ..., + skipkeys: bool = False, + sort_keys: bool = True, + ) -> bytes: ... if sys.version_info < (3, 9): def readPlist(pathOrFile: str | IO[bytes]) -> Any: ... @@ -96,13 +101,13 @@ if sys.version_info < (3, 9): data: bytes def __init__(self, data: bytes) -> None: ... -if sys.version_info >= (3, 8): - class UID: - data: int - def __init__(self, data: int) -> None: ... - def __index__(self) -> int: ... - def __reduce__(self) -> tuple[type[Self], tuple[int]]: ... - def __eq__(self, other: object) -> bool: ... +class UID: + data: int + def __init__(self, data: int) -> None: ... + def __index__(self) -> int: ... + def __reduce__(self) -> tuple[type[Self], tuple[int]]: ... + def __hash__(self) -> int: ... + def __eq__(self, other: object) -> bool: ... class InvalidFileException(ValueError): def __init__(self, message: str = "Invalid file") -> None: ... diff --git a/mypy/typeshed/stdlib/poplib.pyi b/mypy/typeshed/stdlib/poplib.pyi index c64e47e8ef72..12f1d16a0d6f 100644 --- a/mypy/typeshed/stdlib/poplib.pyi +++ b/mypy/typeshed/stdlib/poplib.pyi @@ -1,9 +1,10 @@ import socket import ssl +import sys from builtins import list as _list # conflicts with a method named "list" from re import Pattern -from typing import Any, BinaryIO, NoReturn, overload -from typing_extensions import Literal, TypeAlias +from typing import Any, BinaryIO, Literal, NoReturn, overload +from typing_extensions import TypeAlias __all__ = ["POP3", "error_proto", "POP3_SSL"] @@ -51,14 +52,20 @@ class POP3: def stls(self, context: ssl.SSLContext | None = None) -> bytes: ... class POP3_SSL(POP3): - def __init__( - self, - host: str, - port: int = 995, - keyfile: str | None = None, - certfile: str | None = None, - timeout: float = ..., - context: ssl.SSLContext | None = None, - ) -> None: ... - # "context" is actually the last argument, but that breaks LSP and it doesn't really matter because all the arguments are ignored - def stls(self, context: Any = None, keyfile: Any = None, certfile: Any = None) -> NoReturn: ... + if sys.version_info >= (3, 12): + def __init__( + self, host: str, port: int = 995, *, timeout: float = ..., context: ssl.SSLContext | None = None + ) -> None: ... + def stls(self, context: Any = None) -> NoReturn: ... + else: + def __init__( + self, + host: str, + port: int = 995, + keyfile: str | None = None, + certfile: str | None = None, + timeout: float = ..., + context: ssl.SSLContext | None = None, + ) -> None: ... + # "context" is actually the last argument, but that breaks LSP and it doesn't really matter because all the arguments are ignored + def stls(self, context: Any = None, keyfile: Any = None, certfile: Any = None) -> NoReturn: ... diff --git a/mypy/typeshed/stdlib/posix.pyi b/mypy/typeshed/stdlib/posix.pyi index ffd96757586b..b31b8f3d3524 100644 --- a/mypy/typeshed/stdlib/posix.pyi +++ b/mypy/typeshed/stdlib/posix.pyi @@ -14,7 +14,6 @@ if sys.platform != "win32": EX_NOHOST as EX_NOHOST, EX_NOINPUT as EX_NOINPUT, EX_NOPERM as EX_NOPERM, - EX_NOTFOUND as EX_NOTFOUND, EX_NOUSER as EX_NOUSER, EX_OK as EX_OK, EX_OSERR as EX_OSERR, @@ -29,6 +28,7 @@ if sys.platform != "win32": F_TEST as F_TEST, F_TLOCK as F_TLOCK, F_ULOCK as F_ULOCK, + NGROUPS_MAX as NGROUPS_MAX, O_APPEND as O_APPEND, O_ASYNC as O_ASYNC, O_CREAT as O_CREAT, @@ -51,6 +51,9 @@ if sys.platform != "win32": P_ALL as P_ALL, P_PGID as P_PGID, P_PID as P_PID, + POSIX_SPAWN_CLOSE as POSIX_SPAWN_CLOSE, + POSIX_SPAWN_DUP2 as POSIX_SPAWN_DUP2, + POSIX_SPAWN_OPEN as POSIX_SPAWN_OPEN, PRIO_PGRP as PRIO_PGRP, PRIO_PROCESS as PRIO_PROCESS, PRIO_USER as PRIO_USER, @@ -161,12 +164,17 @@ if sys.platform != "win32": pathconf as pathconf, pathconf_names as pathconf_names, pipe as pipe, + posix_spawn as posix_spawn, + posix_spawnp as posix_spawnp, pread as pread, + preadv as preadv, putenv as putenv, pwrite as pwrite, + pwritev as pwritev, read as read, readlink as readlink, readv as readv, + register_at_fork as register_at_fork, remove as remove, rename as rename, replace as replace, @@ -222,23 +230,18 @@ if sys.platform != "win32": writev as writev, ) - if sys.platform == "linux": - from os import ( - GRND_NONBLOCK as GRND_NONBLOCK, - GRND_RANDOM as GRND_RANDOM, - RTLD_DEEPBIND as RTLD_DEEPBIND, - XATTR_CREATE as XATTR_CREATE, - XATTR_REPLACE as XATTR_REPLACE, - XATTR_SIZE_MAX as XATTR_SIZE_MAX, - getrandom as getrandom, - getxattr as getxattr, - listxattr as listxattr, - removexattr as removexattr, - setxattr as setxattr, - ) - else: + if sys.version_info >= (3, 9): + from os import CLD_KILLED as CLD_KILLED, CLD_STOPPED as CLD_STOPPED, waitstatus_to_exitcode as waitstatus_to_exitcode + + if sys.version_info >= (3, 11): + from os import login_tty as login_tty + + if sys.platform != "linux": from os import chflags as chflags, lchflags as lchflags, lchmod as lchmod + if sys.platform != "linux" and sys.platform != "darwin": + from os import EX_NOTFOUND as EX_NOTFOUND + if sys.platform != "darwin": from os import ( POSIX_FADV_DONTNEED as POSIX_FADV_DONTNEED, @@ -247,6 +250,10 @@ if sys.platform != "win32": POSIX_FADV_RANDOM as POSIX_FADV_RANDOM, POSIX_FADV_SEQUENTIAL as POSIX_FADV_SEQUENTIAL, POSIX_FADV_WILLNEED as POSIX_FADV_WILLNEED, + RWF_DSYNC as RWF_DSYNC, + RWF_HIPRI as RWF_HIPRI, + RWF_NOWAIT as RWF_NOWAIT, + RWF_SYNC as RWF_SYNC, fdatasync as fdatasync, getresgid as getresgid, getresuid as getresuid, @@ -269,50 +276,85 @@ if sys.platform != "win32": if sys.version_info >= (3, 10): from os import RWF_APPEND as RWF_APPEND - if sys.version_info >= (3, 11): - from os import login_tty as login_tty - - if sys.version_info >= (3, 9): - from os import CLD_KILLED as CLD_KILLED, CLD_STOPPED as CLD_STOPPED, waitstatus_to_exitcode as waitstatus_to_exitcode + if sys.platform == "linux": + from os import ( + GRND_NONBLOCK as GRND_NONBLOCK, + GRND_RANDOM as GRND_RANDOM, + MFD_ALLOW_SEALING as MFD_ALLOW_SEALING, + MFD_CLOEXEC as MFD_CLOEXEC, + MFD_HUGE_1GB as MFD_HUGE_1GB, + MFD_HUGE_1MB as MFD_HUGE_1MB, + MFD_HUGE_2GB as MFD_HUGE_2GB, + MFD_HUGE_2MB as MFD_HUGE_2MB, + MFD_HUGE_8MB as MFD_HUGE_8MB, + MFD_HUGE_16GB as MFD_HUGE_16GB, + MFD_HUGE_16MB as MFD_HUGE_16MB, + MFD_HUGE_32MB as MFD_HUGE_32MB, + MFD_HUGE_64KB as MFD_HUGE_64KB, + MFD_HUGE_256MB as MFD_HUGE_256MB, + MFD_HUGE_512KB as MFD_HUGE_512KB, + MFD_HUGE_512MB as MFD_HUGE_512MB, + MFD_HUGE_MASK as MFD_HUGE_MASK, + MFD_HUGE_SHIFT as MFD_HUGE_SHIFT, + MFD_HUGETLB as MFD_HUGETLB, + RTLD_DEEPBIND as RTLD_DEEPBIND, + XATTR_CREATE as XATTR_CREATE, + XATTR_REPLACE as XATTR_REPLACE, + XATTR_SIZE_MAX as XATTR_SIZE_MAX, + copy_file_range as copy_file_range, + getrandom as getrandom, + getxattr as getxattr, + listxattr as listxattr, + memfd_create as memfd_create, + removexattr as removexattr, + setxattr as setxattr, + ) - if sys.platform == "linux": + if sys.version_info >= (3, 9): from os import P_PIDFD as P_PIDFD, pidfd_open as pidfd_open - if sys.version_info >= (3, 8): - from os import ( - POSIX_SPAWN_CLOSE as POSIX_SPAWN_CLOSE, - POSIX_SPAWN_DUP2 as POSIX_SPAWN_DUP2, - POSIX_SPAWN_OPEN as POSIX_SPAWN_OPEN, - posix_spawn as posix_spawn, - posix_spawnp as posix_spawnp, - ) + if sys.version_info >= (3, 10): + from os import ( + EFD_CLOEXEC as EFD_CLOEXEC, + EFD_NONBLOCK as EFD_NONBLOCK, + EFD_SEMAPHORE as EFD_SEMAPHORE, + SPLICE_F_MORE as SPLICE_F_MORE, + SPLICE_F_MOVE as SPLICE_F_MOVE, + SPLICE_F_NONBLOCK as SPLICE_F_NONBLOCK, + eventfd as eventfd, + eventfd_read as eventfd_read, + eventfd_write as eventfd_write, + splice as splice, + ) - if sys.platform == "linux": + if sys.version_info >= (3, 12): from os import ( - MFD_ALLOW_SEALING as MFD_ALLOW_SEALING, - MFD_CLOEXEC as MFD_CLOEXEC, - MFD_HUGE_1GB as MFD_HUGE_1GB, - MFD_HUGE_1MB as MFD_HUGE_1MB, - MFD_HUGE_2GB as MFD_HUGE_2GB, - MFD_HUGE_2MB as MFD_HUGE_2MB, - MFD_HUGE_8MB as MFD_HUGE_8MB, - MFD_HUGE_16GB as MFD_HUGE_16GB, - MFD_HUGE_16MB as MFD_HUGE_16MB, - MFD_HUGE_32MB as MFD_HUGE_32MB, - MFD_HUGE_64KB as MFD_HUGE_64KB, - MFD_HUGE_256MB as MFD_HUGE_256MB, - MFD_HUGE_512KB as MFD_HUGE_512KB, - MFD_HUGE_512MB as MFD_HUGE_512MB, - MFD_HUGE_MASK as MFD_HUGE_MASK, - MFD_HUGE_SHIFT as MFD_HUGE_SHIFT, - MFD_HUGETLB as MFD_HUGETLB, - copy_file_range as copy_file_range, - memfd_create as memfd_create, + CLONE_FILES as CLONE_FILES, + CLONE_FS as CLONE_FS, + CLONE_NEWCGROUP as CLONE_NEWCGROUP, + CLONE_NEWIPC as CLONE_NEWIPC, + CLONE_NEWNET as CLONE_NEWNET, + CLONE_NEWNS as CLONE_NEWNS, + CLONE_NEWPID as CLONE_NEWPID, + CLONE_NEWTIME as CLONE_NEWTIME, + CLONE_NEWUSER as CLONE_NEWUSER, + CLONE_NEWUTS as CLONE_NEWUTS, + CLONE_SIGHAND as CLONE_SIGHAND, + CLONE_SYSVSEM as CLONE_SYSVSEM, + CLONE_THREAD as CLONE_THREAD, + CLONE_VM as CLONE_VM, + setns as setns, + unshare as unshare, ) - from os import preadv as preadv, pwritev as pwritev, register_at_fork as register_at_fork - if sys.platform != "darwin": - from os import RWF_DSYNC as RWF_DSYNC, RWF_HIPRI as RWF_HIPRI, RWF_NOWAIT as RWF_NOWAIT, RWF_SYNC as RWF_SYNC + if sys.platform == "darwin": + if sys.version_info >= (3, 12): + from os import ( + PRIO_DARWIN_BG as PRIO_DARWIN_BG, + PRIO_DARWIN_NONUI as PRIO_DARWIN_NONUI, + PRIO_DARWIN_PROCESS as PRIO_DARWIN_PROCESS, + PRIO_DARWIN_THREAD as PRIO_DARWIN_THREAD, + ) # Not same as os.environ or os.environb # Because of this variable, we can't do "from posix import *" in os/__init__.pyi diff --git a/mypy/typeshed/stdlib/posixpath.pyi b/mypy/typeshed/stdlib/posixpath.pyi index 1945190be5f8..1fc471ac7d0b 100644 --- a/mypy/typeshed/stdlib/posixpath.pyi +++ b/mypy/typeshed/stdlib/posixpath.pyi @@ -1,6 +1,6 @@ import sys from _typeshed import AnyOrLiteralStr, BytesPath, FileDescriptorOrPath, StrOrBytesPath, StrPath -from collections.abc import Sequence +from collections.abc import Iterable from genericpath import ( commonprefix as commonprefix, exists as exists, @@ -58,6 +58,8 @@ __all__ = [ "relpath", "commonpath", ] +if sys.version_info >= (3, 12): + __all__ += ["isjunction", "splitroot"] supports_unicode_filenames: bool # aliases (also in os) @@ -100,21 +102,21 @@ def normpath(path: PathLike[AnyStr]) -> AnyStr: ... @overload def normpath(path: AnyOrLiteralStr) -> AnyOrLiteralStr: ... @overload -def commonpath(paths: Sequence[LiteralString]) -> LiteralString: ... +def commonpath(paths: Iterable[LiteralString]) -> LiteralString: ... @overload -def commonpath(paths: Sequence[StrPath]) -> str: ... +def commonpath(paths: Iterable[StrPath]) -> str: ... @overload -def commonpath(paths: Sequence[BytesPath]) -> bytes: ... +def commonpath(paths: Iterable[BytesPath]) -> bytes: ... # First parameter is not actually pos-only, # but must be defined as pos-only in the stub or cross-platform code doesn't type-check, # as the parameter name is different in ntpath.join() @overload -def join(__a: LiteralString, *paths: LiteralString) -> LiteralString: ... +def join(a: LiteralString, /, *paths: LiteralString) -> LiteralString: ... @overload -def join(__a: StrPath, *paths: StrPath) -> str: ... +def join(a: StrPath, /, *paths: StrPath) -> str: ... @overload -def join(__a: BytesPath, *paths: BytesPath) -> bytes: ... +def join(a: BytesPath, /, *paths: BytesPath) -> bytes: ... if sys.version_info >= (3, 10): @overload @@ -150,3 +152,10 @@ def isabs(s: StrOrBytesPath) -> bool: ... def islink(path: FileDescriptorOrPath) -> bool: ... def ismount(path: FileDescriptorOrPath) -> bool: ... def lexists(path: FileDescriptorOrPath) -> bool: ... + +if sys.version_info >= (3, 12): + def isjunction(path: StrOrBytesPath) -> bool: ... + @overload + def splitroot(p: AnyOrLiteralStr) -> tuple[AnyOrLiteralStr, AnyOrLiteralStr, AnyOrLiteralStr]: ... + @overload + def splitroot(p: PathLike[AnyStr]) -> tuple[AnyStr, AnyStr, AnyStr]: ... diff --git a/mypy/typeshed/stdlib/pprint.pyi b/mypy/typeshed/stdlib/pprint.pyi index 5a909c69b077..171878f4165d 100644 --- a/mypy/typeshed/stdlib/pprint.pyi +++ b/mypy/typeshed/stdlib/pprint.pyi @@ -1,10 +1,7 @@ import sys from typing import IO -if sys.version_info >= (3, 8): - __all__ = ["pprint", "pformat", "isreadable", "isrecursive", "saferepr", "PrettyPrinter", "pp"] -else: - __all__ = ["pprint", "pformat", "isreadable", "isrecursive", "saferepr", "PrettyPrinter"] +__all__ = ["pprint", "pformat", "isreadable", "isrecursive", "saferepr", "PrettyPrinter", "pp"] if sys.version_info >= (3, 10): def pformat( @@ -18,7 +15,7 @@ if sys.version_info >= (3, 10): underscore_numbers: bool = False, ) -> str: ... -elif sys.version_info >= (3, 8): +else: def pformat( object: object, indent: int = 1, @@ -29,9 +26,6 @@ elif sys.version_info >= (3, 8): sort_dicts: bool = True, ) -> str: ... -else: - def pformat(object: object, indent: int = 1, width: int = 80, depth: int | None = None, *, compact: bool = False) -> str: ... - if sys.version_info >= (3, 10): def pp( object: object, @@ -45,7 +39,7 @@ if sys.version_info >= (3, 10): underscore_numbers: bool = ..., ) -> None: ... -elif sys.version_info >= (3, 8): +else: def pp( object: object, stream: IO[str] | None = ..., @@ -70,18 +64,6 @@ if sys.version_info >= (3, 10): underscore_numbers: bool = False, ) -> None: ... -elif sys.version_info >= (3, 8): - def pprint( - object: object, - stream: IO[str] | None = None, - indent: int = 1, - width: int = 80, - depth: int | None = None, - *, - compact: bool = False, - sort_dicts: bool = True, - ) -> None: ... - else: def pprint( object: object, @@ -91,6 +73,7 @@ else: depth: int | None = None, *, compact: bool = False, + sort_dicts: bool = True, ) -> None: ... def isreadable(object: object) -> bool: ... @@ -110,17 +93,6 @@ class PrettyPrinter: sort_dicts: bool = True, underscore_numbers: bool = False, ) -> None: ... - elif sys.version_info >= (3, 8): - def __init__( - self, - indent: int = 1, - width: int = 80, - depth: int | None = None, - stream: IO[str] | None = None, - *, - compact: bool = False, - sort_dicts: bool = True, - ) -> None: ... else: def __init__( self, @@ -130,6 +102,7 @@ class PrettyPrinter: stream: IO[str] | None = None, *, compact: bool = False, + sort_dicts: bool = True, ) -> None: ... def pformat(self, object: object) -> str: ... diff --git a/mypy/typeshed/stdlib/profile.pyi b/mypy/typeshed/stdlib/profile.pyi index 6ae375004158..73eba36344fe 100644 --- a/mypy/typeshed/stdlib/profile.pyi +++ b/mypy/typeshed/stdlib/profile.pyi @@ -27,5 +27,5 @@ class Profile: def snapshot_stats(self) -> None: ... def run(self, cmd: str) -> Self: ... def runctx(self, cmd: str, globals: dict[str, Any], locals: dict[str, Any]) -> Self: ... - def runcall(self, __func: Callable[_P, _T], *args: _P.args, **kw: _P.kwargs) -> _T: ... + def runcall(self, func: Callable[_P, _T], /, *args: _P.args, **kw: _P.kwargs) -> _T: ... def calibrate(self, m: int, verbose: int = 0) -> float: ... diff --git a/mypy/typeshed/stdlib/pstats.pyi b/mypy/typeshed/stdlib/pstats.pyi index 5d25d1bb3641..83256b433035 100644 --- a/mypy/typeshed/stdlib/pstats.pyi +++ b/mypy/typeshed/stdlib/pstats.pyi @@ -1,11 +1,10 @@ import sys -from _typeshed import StrOrBytesPath +from _typeshed import StrEnum, StrOrBytesPath from collections.abc import Iterable from cProfile import Profile as _cProfile -from enum import Enum from profile import Profile -from typing import IO, Any, overload -from typing_extensions import Literal, Self, TypeAlias +from typing import IO, Any, Literal, overload +from typing_extensions import Self, TypeAlias if sys.version_info >= (3, 9): __all__ = ["Stats", "SortKey", "FunctionProfile", "StatsProfile"] @@ -14,16 +13,16 @@ else: _Selector: TypeAlias = str | float | int -class SortKey(str, Enum): - CALLS: str - CUMULATIVE: str - FILENAME: str - LINE: str - NAME: str - NFL: str - PCALLS: str - STDNAME: str - TIME: str +class SortKey(StrEnum): + CALLS = "calls" + CUMULATIVE = "cumulative" + FILENAME = "filename" + LINE = "line" + NAME = "name" + NFL = "nfl" + PCALLS = "pcalls" + STDNAME = "stdname" + TIME = "time" if sys.version_info >= (3, 9): from dataclasses import dataclass @@ -37,6 +36,7 @@ if sys.version_info >= (3, 9): percall_cumtime: float file_name: str line_number: int + @dataclass(unsafe_hash=True) class StatsProfile: total_tt: float @@ -48,7 +48,8 @@ class Stats: sort_arg_dict_default: _SortArgDict def __init__( self, - __arg: None | str | Profile | _cProfile = ..., + arg: None | str | Profile | _cProfile = ..., + /, *args: None | str | Profile | _cProfile | Self, stream: IO[Any] | None = None, ) -> None: ... diff --git a/mypy/typeshed/stdlib/pty.pyi b/mypy/typeshed/stdlib/pty.pyi index a6a2d8fabb69..022b08046c54 100644 --- a/mypy/typeshed/stdlib/pty.pyi +++ b/mypy/typeshed/stdlib/pty.pyi @@ -1,6 +1,7 @@ import sys from collections.abc import Callable, Iterable -from typing_extensions import Literal, TypeAlias +from typing import Literal +from typing_extensions import TypeAlias if sys.platform != "win32": __all__ = ["openpty", "fork", "spawn"] diff --git a/mypy/typeshed/stdlib/pwd.pyi b/mypy/typeshed/stdlib/pwd.pyi index 80813479d7af..a84ba324718a 100644 --- a/mypy/typeshed/stdlib/pwd.pyi +++ b/mypy/typeshed/stdlib/pwd.pyi @@ -1,13 +1,13 @@ import sys from _typeshed import structseq -from typing import Any -from typing_extensions import Final, final +from typing import Any, Final, final if sys.platform != "win32": @final class struct_passwd(structseq[Any], tuple[str, str, int, int, str, str, str]): if sys.version_info >= (3, 10): __match_args__: Final = ("pw_name", "pw_passwd", "pw_uid", "pw_gid", "pw_gecos", "pw_dir", "pw_shell") + @property def pw_name(self) -> str: ... @property @@ -24,5 +24,5 @@ if sys.platform != "win32": def pw_shell(self) -> str: ... def getpwall() -> list[struct_passwd]: ... - def getpwuid(__uid: int) -> struct_passwd: ... - def getpwnam(__name: str) -> struct_passwd: ... + def getpwuid(uid: int, /) -> struct_passwd: ... + def getpwnam(name: str, /) -> struct_passwd: ... diff --git a/mypy/typeshed/stdlib/py_compile.pyi b/mypy/typeshed/stdlib/py_compile.pyi index 48f1d7dc3e70..334ce79b5dd0 100644 --- a/mypy/typeshed/stdlib/py_compile.pyi +++ b/mypy/typeshed/stdlib/py_compile.pyi @@ -12,32 +12,20 @@ class PyCompileError(Exception): def __init__(self, exc_type: type[BaseException], exc_value: BaseException, file: str, msg: str = "") -> None: ... class PycInvalidationMode(enum.Enum): - TIMESTAMP: int - CHECKED_HASH: int - UNCHECKED_HASH: int + TIMESTAMP = 1 + CHECKED_HASH = 2 + UNCHECKED_HASH = 3 def _get_default_invalidation_mode() -> PycInvalidationMode: ... - -if sys.version_info >= (3, 8): - def compile( - file: AnyStr, - cfile: AnyStr | None = None, - dfile: AnyStr | None = None, - doraise: bool = False, - optimize: int = -1, - invalidation_mode: PycInvalidationMode | None = None, - quiet: int = 0, - ) -> AnyStr | None: ... - -else: - def compile( - file: AnyStr, - cfile: AnyStr | None = None, - dfile: AnyStr | None = None, - doraise: bool = False, - optimize: int = -1, - invalidation_mode: PycInvalidationMode | None = None, - ) -> AnyStr | None: ... +def compile( + file: AnyStr, + cfile: AnyStr | None = None, + dfile: AnyStr | None = None, + doraise: bool = False, + optimize: int = -1, + invalidation_mode: PycInvalidationMode | None = None, + quiet: int = 0, +) -> AnyStr | None: ... if sys.version_info >= (3, 10): def main() -> None: ... diff --git a/mypy/typeshed/stdlib/pyclbr.pyi b/mypy/typeshed/stdlib/pyclbr.pyi index 38658a03139c..504a5d5f115a 100644 --- a/mypy/typeshed/stdlib/pyclbr.pyi +++ b/mypy/typeshed/stdlib/pyclbr.pyi @@ -1,20 +1,35 @@ import sys -from collections.abc import Sequence +from collections.abc import Mapping, Sequence __all__ = ["readmodule", "readmodule_ex", "Class", "Function"] -class Class: +class _Object: module: str name: str - super: list[Class | str] | None - methods: dict[str, int] file: int lineno: int if sys.version_info >= (3, 10): end_lineno: int | None - parent: Class | None + parent: _Object | None + + # This is a dict at runtime, but we're typing it as Mapping to + # avoid variance issues in the subclasses + children: Mapping[str, _Object] + + if sys.version_info >= (3, 10): + def __init__( + self, module: str, name: str, file: str, lineno: int, end_lineno: int | None, parent: _Object | None + ) -> None: ... + else: + def __init__(self, module: str, name: str, file: str, lineno: int, parent: _Object | None) -> None: ... + +class Function(_Object): + if sys.version_info >= (3, 10): + is_async: bool + + parent: Function | Class | None children: dict[str, Class | Function] if sys.version_info >= (3, 10): @@ -22,29 +37,20 @@ class Class: self, module: str, name: str, - super_: list[Class | str] | None, file: str, lineno: int, - parent: Class | None = None, + parent: Function | Class | None = None, + is_async: bool = False, *, end_lineno: int | None = None, ) -> None: ... else: - def __init__( - self, module: str, name: str, super: list[Class | str] | None, file: str, lineno: int, parent: Class | None = None - ) -> None: ... - -class Function: - module: str - name: str - file: int - lineno: int - - if sys.version_info >= (3, 10): - end_lineno: int | None - is_async: bool + def __init__(self, module: str, name: str, file: str, lineno: int, parent: Function | Class | None = None) -> None: ... - parent: Function | Class | None +class Class(_Object): + super: list[Class | str] | None + methods: dict[str, int] + parent: Class | None children: dict[str, Class | Function] if sys.version_info >= (3, 10): @@ -52,15 +58,17 @@ class Function: self, module: str, name: str, + super_: list[Class | str] | None, file: str, lineno: int, - parent: Function | Class | None = None, - is_async: bool = False, + parent: Class | None = None, *, end_lineno: int | None = None, ) -> None: ... else: - def __init__(self, module: str, name: str, file: str, lineno: int, parent: Function | Class | None = None) -> None: ... + def __init__( + self, module: str, name: str, super: list[Class | str] | None, file: str, lineno: int, parent: Class | None = None + ) -> None: ... def readmodule(module: str, path: Sequence[str] | None = None) -> dict[str, Class]: ... def readmodule_ex(module: str, path: Sequence[str] | None = None) -> dict[str, Class | Function | list[str]]: ... diff --git a/mypy/typeshed/stdlib/pydoc.pyi b/mypy/typeshed/stdlib/pydoc.pyi index c6893d50c66a..3134de79352d 100644 --- a/mypy/typeshed/stdlib/pydoc.pyi +++ b/mypy/typeshed/stdlib/pydoc.pyi @@ -5,8 +5,8 @@ from builtins import list as _list # "list" conflicts with method name from collections.abc import Callable, Container, Mapping, MutableMapping from reprlib import Repr from types import MethodType, ModuleType, TracebackType -from typing import IO, Any, AnyStr, NoReturn, TypeVar -from typing_extensions import Final, TypeGuard +from typing import IO, Any, AnyStr, Final, NoReturn, TypeVar +from typing_extensions import TypeGuard __all__ = ["help"] @@ -30,7 +30,7 @@ def visiblename(name: str, all: Container[str] | None = None, obj: object = None def classify_class_attrs(object: object) -> list[tuple[str, str, type, str]]: ... def ispackage(path: str) -> bool: ... def source_synopsis(file: IO[AnyStr]) -> AnyStr | None: ... -def synopsis(filename: str, cache: MutableMapping[str, tuple[int, str]] = ...) -> str | None: ... +def synopsis(filename: str, cache: MutableMapping[str, tuple[int, str]] = {}) -> str | None: ... class ErrorDuringImport(Exception): filename: str @@ -40,7 +40,7 @@ class ErrorDuringImport(Exception): def __init__(self, filename: str, exc_info: OptExcInfo) -> None: ... def importfile(path: str) -> ModuleType: ... -def safeimport(path: str, forceload: bool = ..., cache: MutableMapping[str, ModuleType] = ...) -> ModuleType: ... +def safeimport(path: str, forceload: bool = ..., cache: MutableMapping[str, ModuleType] = {}) -> ModuleType | None: ... class Doc: PYTHONDOCS: str @@ -61,6 +61,7 @@ class Doc: def getdocloc(self, object: object, basedir: str = ...) -> str | None: ... class HTMLRepr(Repr): + def __init__(self) -> None: ... def escape(self, text: str) -> str: ... def repr(self, object: object) -> str: ... def repr1(self, x: object, level: complex) -> str: ... @@ -70,7 +71,7 @@ class HTMLRepr(Repr): def repr_unicode(self, x: AnyStr, level: complex) -> str: ... class HTMLDoc(Doc): - _repr_instance: HTMLRepr = ... + _repr_instance: HTMLRepr repr = _repr_instance.repr escape = _repr_instance.escape def page(self, title: str, contents: str) -> str: ... @@ -113,9 +114,9 @@ class HTMLDoc(Doc): self, text: str, escape: Callable[[str], str] | None = None, - funcs: Mapping[str, str] = ..., - classes: Mapping[str, str] = ..., - methods: Mapping[str, str] = ..., + funcs: Mapping[str, str] = {}, + classes: Mapping[str, str] = {}, + methods: Mapping[str, str] = {}, ) -> str: ... def formattree( self, tree: list[tuple[type, tuple[type, ...]] | list[Any]], modname: str, parent: type | None = None @@ -126,8 +127,8 @@ class HTMLDoc(Doc): object: object, name: str | None = None, mod: str | None = None, - funcs: Mapping[str, str] = ..., - classes: Mapping[str, str] = ..., + funcs: Mapping[str, str] = {}, + classes: Mapping[str, str] = {}, *ignored: Any, ) -> str: ... def formatvalue(self, object: object) -> str: ... @@ -136,9 +137,9 @@ class HTMLDoc(Doc): object: object, name: str | None = None, mod: str | None = None, - funcs: Mapping[str, str] = ..., - classes: Mapping[str, str] = ..., - methods: Mapping[str, str] = ..., + funcs: Mapping[str, str] = {}, + classes: Mapping[str, str] = {}, + methods: Mapping[str, str] = {}, cl: type | None = None, ) -> str: ... def docproperty(self, object: object, name: str | None = None, mod: str | None = None, cl: Any | None = None) -> str: ... # type: ignore[override] @@ -148,13 +149,14 @@ class HTMLDoc(Doc): def filelink(self, url: str, path: str) -> str: ... class TextRepr(Repr): + def __init__(self) -> None: ... def repr1(self, x: object, level: complex) -> str: ... def repr_string(self, x: str, level: complex) -> str: ... def repr_str(self, x: str, level: complex) -> str: ... def repr_instance(self, x: object, level: complex) -> str: ... class TextDoc(Doc): - _repr_instance: TextRepr = ... + _repr_instance: TextRepr repr = _repr_instance.repr def bold(self, text: str) -> str: ... def indent(self, text: str, prefix: str = " ") -> str: ... @@ -195,12 +197,24 @@ def resolve(thing: str | object, forceload: bool = ...) -> tuple[object, str] | def render_doc( thing: str | object, title: str = "Python Library Documentation: %s", forceload: bool = ..., renderer: Doc | None = None ) -> str: ... -def doc( - thing: str | object, - title: str = "Python Library Documentation: %s", - forceload: bool = ..., - output: SupportsWrite[str] | None = None, -) -> None: ... + +if sys.version_info >= (3, 11): + def doc( + thing: str | object, + title: str = "Python Library Documentation: %s", + forceload: bool = ..., + output: SupportsWrite[str] | None = None, + is_cli: bool = False, + ) -> None: ... + +else: + def doc( + thing: str | object, + title: str = "Python Library Documentation: %s", + forceload: bool = ..., + output: SupportsWrite[str] | None = None, + ) -> None: ... + def writedoc(thing: str | object, forceload: bool = ...) -> None: ... def writedocs(dir: str, pkgpath: str = "", done: Any | None = None) -> None: ... @@ -216,7 +230,11 @@ class Helper: def __call__(self, request: str | Helper | object = ...) -> None: ... def interact(self) -> None: ... def getline(self, prompt: str) -> str: ... - def help(self, request: Any) -> None: ... + if sys.version_info >= (3, 11): + def help(self, request: Any, is_cli: bool = False) -> None: ... + else: + def help(self, request: Any) -> None: ... + def intro(self) -> None: ... def list(self, items: _list[str], columns: int = 4, width: int = 80) -> None: ... def listkeywords(self) -> None: ... diff --git a/mypy/typeshed/stdlib/pyexpat/__init__.pyi b/mypy/typeshed/stdlib/pyexpat/__init__.pyi index 9e1eea08be54..88bf9464d130 100644 --- a/mypy/typeshed/stdlib/pyexpat/__init__.pyi +++ b/mypy/typeshed/stdlib/pyexpat/__init__.pyi @@ -1,8 +1,8 @@ from _typeshed import ReadableBuffer, SupportsRead from collections.abc import Callable from pyexpat import errors as errors, model as model -from typing import Any -from typing_extensions import TypeAlias, final +from typing import Any, final +from typing_extensions import TypeAlias EXPAT_VERSION: str # undocumented version_info: tuple[int, int, int] # undocumented @@ -24,14 +24,16 @@ _Model: TypeAlias = tuple[int, int, str | None, tuple[Any, ...]] @final class XMLParserType: - def Parse(self, __data: str | ReadableBuffer, __isfinal: bool = False) -> int: ... - def ParseFile(self, __file: SupportsRead[bytes]) -> int: ... - def SetBase(self, __base: str) -> None: ... + def Parse(self, data: str | ReadableBuffer, isfinal: bool = False, /) -> int: ... + def ParseFile(self, file: SupportsRead[bytes], /) -> int: ... + def SetBase(self, base: str, /) -> None: ... def GetBase(self) -> str | None: ... def GetInputContext(self) -> bytes | None: ... - def ExternalEntityParserCreate(self, __context: str | None, __encoding: str = ...) -> XMLParserType: ... - def SetParamEntityParsing(self, __flag: int) -> int: ... - def UseForeignDTD(self, __flag: bool = True) -> None: ... + def ExternalEntityParserCreate(self, context: str | None, encoding: str = ..., /) -> XMLParserType: ... + def SetParamEntityParsing(self, flag: int, /) -> int: ... + def UseForeignDTD(self, flag: bool = True, /) -> None: ... + def GetReparseDeferralEnabled(self) -> bool: ... + def SetReparseDeferralEnabled(self, enabled: bool, /) -> None: ... @property def intern(self) -> dict[str, str]: ... buffer_size: int @@ -52,9 +54,12 @@ class XMLParserType: EndDoctypeDeclHandler: Callable[[], Any] | None ElementDeclHandler: Callable[[str, _Model], Any] | None AttlistDeclHandler: Callable[[str, str, str, str | None, bool], Any] | None - StartElementHandler: Callable[[str, dict[str, str]], Any] | Callable[[str, list[str]], Any] | Callable[ - [str, dict[str, str], list[str]], Any - ] | None + StartElementHandler: ( + Callable[[str, dict[str, str]], Any] + | Callable[[str, list[str]], Any] + | Callable[[str, dict[str, str], list[str]], Any] + | None + ) EndElementHandler: Callable[[str], Any] | None ProcessingInstructionHandler: Callable[[str, str], Any] | None CharacterDataHandler: Callable[[str], Any] | None @@ -72,7 +77,7 @@ class XMLParserType: ExternalEntityRefHandler: Callable[[str, str | None, str | None, str | None], int] | None SkippedEntityHandler: Callable[[str, bool], Any] | None -def ErrorString(__code: int) -> str: ... +def ErrorString(code: int, /) -> str: ... # intern is undocumented def ParserCreate( diff --git a/mypy/typeshed/stdlib/queue.pyi b/mypy/typeshed/stdlib/queue.pyi index 3537e445ed97..d7cae5f2ac79 100644 --- a/mypy/typeshed/stdlib/queue.pyi +++ b/mypy/typeshed/stdlib/queue.pyi @@ -12,6 +12,9 @@ _T = TypeVar("_T") class Empty(Exception): ... class Full(Exception): ... +if sys.version_info >= (3, 13): + class ShutDown(Exception): ... + class Queue(Generic[_T]): maxsize: int @@ -20,6 +23,8 @@ class Queue(Generic[_T]): not_full: Condition # undocumented all_tasks_done: Condition # undocumented unfinished_tasks: int # undocumented + if sys.version_info >= (3, 13): + is_shutdown: bool # undocumented # Despite the fact that `queue` has `deque` type, # we treat it as `Any` to allow different implementations in subtypes. queue: Any # undocumented @@ -29,6 +34,9 @@ class Queue(Generic[_T]): def full(self) -> bool: ... def get(self, block: bool = True, timeout: float | None = None) -> _T: ... def get_nowait(self) -> _T: ... + if sys.version_info >= (3, 13): + def shutdown(self, immediate: bool = False) -> None: ... + def _get(self) -> _T: ... def put(self, item: _T, block: bool = True, timeout: float | None = None) -> None: ... def put_nowait(self, item: _T) -> None: ... diff --git a/mypy/typeshed/stdlib/random.pyi b/mypy/typeshed/stdlib/random.pyi index 4849878691f5..9fd1c64f2bba 100644 --- a/mypy/typeshed/stdlib/random.pyi +++ b/mypy/typeshed/stdlib/random.pyi @@ -34,6 +34,8 @@ __all__ = [ if sys.version_info >= (3, 9): __all__ += ["randbytes"] +if sys.version_info >= (3, 12): + __all__ += ["binomialvariate"] _T = TypeVar("_T") @@ -79,8 +81,15 @@ class Random(_random.Random): def uniform(self, a: float, b: float) -> float: ... def triangular(self, low: float = 0.0, high: float = 1.0, mode: float | None = None) -> float: ... + if sys.version_info >= (3, 12): + def binomialvariate(self, n: int = 1, p: float = 0.5) -> int: ... + def betavariate(self, alpha: float, beta: float) -> float: ... - def expovariate(self, lambd: float) -> float: ... + if sys.version_info >= (3, 12): + def expovariate(self, lambd: float = 1.0) -> float: ... + else: + def expovariate(self, lambd: float) -> float: ... + def gammavariate(self, alpha: float, beta: float) -> float: ... if sys.version_info >= (3, 11): def gauss(self, mu: float = 0.0, sigma: float = 1.0) -> float: ... @@ -100,9 +109,7 @@ class SystemRandom(Random): def getstate(self, *args: Any, **kwds: Any) -> NoReturn: ... def setstate(self, *args: Any, **kwds: Any) -> NoReturn: ... -# ----- random function stubs ----- - -_inst: Random = ... +_inst: Random seed = _inst.seed random = _inst.random uniform = _inst.uniform @@ -119,6 +126,8 @@ expovariate = _inst.expovariate vonmisesvariate = _inst.vonmisesvariate gammavariate = _inst.gammavariate gauss = _inst.gauss +if sys.version_info >= (3, 12): + binomialvariate = _inst.binomialvariate betavariate = _inst.betavariate paretovariate = _inst.paretovariate weibullvariate = _inst.weibullvariate diff --git a/mypy/typeshed/stdlib/re.pyi b/mypy/typeshed/stdlib/re.pyi index 4e53141ade84..7945c5f46cdc 100644 --- a/mypy/typeshed/stdlib/re.pyi +++ b/mypy/typeshed/stdlib/re.pyi @@ -4,8 +4,8 @@ import sys from _typeshed import ReadableBuffer from collections.abc import Callable, Iterator, Mapping from sre_constants import error as error -from typing import Any, AnyStr, Generic, TypeVar, overload -from typing_extensions import Literal, TypeAlias, final +from typing import Any, AnyStr, Generic, Literal, TypeVar, final, overload +from typing_extensions import TypeAlias if sys.version_info >= (3, 9): from types import GenericAlias @@ -67,16 +67,16 @@ class Match(Generic[AnyStr]): @overload def expand(self: Match[str], template: str) -> str: ... @overload - def expand(self: Match[bytes], template: ReadableBuffer) -> bytes: ... # type: ignore[misc] + def expand(self: Match[bytes], template: ReadableBuffer) -> bytes: ... # type: ignore[overload-overlap] @overload def expand(self, template: AnyStr) -> AnyStr: ... # group() returns "AnyStr" or "AnyStr | None", depending on the pattern. @overload - def group(self, __group: Literal[0] = 0) -> AnyStr: ... + def group(self, group: Literal[0] = 0, /) -> AnyStr: ... @overload - def group(self, __group: str | int) -> AnyStr | Any: ... + def group(self, group: str | int, /) -> AnyStr | Any: ... @overload - def group(self, __group1: str | int, __group2: str | int, *groups: str | int) -> tuple[AnyStr | Any, ...]: ... + def group(self, group1: str | int, group2: str | int, /, *groups: str | int) -> tuple[AnyStr | Any, ...]: ... # Each item of groups()'s return tuple is either "AnyStr" or # "AnyStr | None", depending on the pattern. @overload @@ -89,18 +89,18 @@ class Match(Generic[AnyStr]): def groupdict(self) -> dict[str, AnyStr | Any]: ... @overload def groupdict(self, default: _T) -> dict[str, AnyStr | _T]: ... - def start(self, __group: int | str = 0) -> int: ... - def end(self, __group: int | str = 0) -> int: ... - def span(self, __group: int | str = 0) -> tuple[int, int]: ... + def start(self, group: int | str = 0, /) -> int: ... + def end(self, group: int | str = 0, /) -> int: ... + def span(self, group: int | str = 0, /) -> tuple[int, int]: ... @property def regs(self) -> tuple[tuple[int, int], ...]: ... # undocumented # __getitem__() returns "AnyStr" or "AnyStr | None", depending on the pattern. @overload - def __getitem__(self, __key: Literal[0]) -> AnyStr: ... + def __getitem__(self, key: Literal[0], /) -> AnyStr: ... @overload - def __getitem__(self, __key: int | str) -> AnyStr | Any: ... + def __getitem__(self, key: int | str, /) -> AnyStr | Any: ... def __copy__(self) -> Match[AnyStr]: ... - def __deepcopy__(self, __memo: Any) -> Match[AnyStr]: ... + def __deepcopy__(self, memo: Any, /) -> Match[AnyStr]: ... if sys.version_info >= (3, 9): def __class_getitem__(cls, item: Any) -> GenericAlias: ... @@ -117,19 +117,19 @@ class Pattern(Generic[AnyStr]): @overload def search(self: Pattern[str], string: str, pos: int = 0, endpos: int = sys.maxsize) -> Match[str] | None: ... @overload - def search(self: Pattern[bytes], string: ReadableBuffer, pos: int = 0, endpos: int = sys.maxsize) -> Match[bytes] | None: ... # type: ignore[misc] + def search(self: Pattern[bytes], string: ReadableBuffer, pos: int = 0, endpos: int = sys.maxsize) -> Match[bytes] | None: ... # type: ignore[overload-overlap] @overload def search(self, string: AnyStr, pos: int = 0, endpos: int = sys.maxsize) -> Match[AnyStr] | None: ... @overload def match(self: Pattern[str], string: str, pos: int = 0, endpos: int = sys.maxsize) -> Match[str] | None: ... @overload - def match(self: Pattern[bytes], string: ReadableBuffer, pos: int = 0, endpos: int = sys.maxsize) -> Match[bytes] | None: ... # type: ignore[misc] + def match(self: Pattern[bytes], string: ReadableBuffer, pos: int = 0, endpos: int = sys.maxsize) -> Match[bytes] | None: ... # type: ignore[overload-overlap] @overload def match(self, string: AnyStr, pos: int = 0, endpos: int = sys.maxsize) -> Match[AnyStr] | None: ... @overload def fullmatch(self: Pattern[str], string: str, pos: int = 0, endpos: int = sys.maxsize) -> Match[str] | None: ... @overload - def fullmatch(self: Pattern[bytes], string: ReadableBuffer, pos: int = 0, endpos: int = sys.maxsize) -> Match[bytes] | None: ... # type: ignore[misc] + def fullmatch(self: Pattern[bytes], string: ReadableBuffer, pos: int = 0, endpos: int = sys.maxsize) -> Match[bytes] | None: ... # type: ignore[overload-overlap] @overload def fullmatch(self, string: AnyStr, pos: int = 0, endpos: int = sys.maxsize) -> Match[AnyStr] | None: ... @overload @@ -148,13 +148,13 @@ class Pattern(Generic[AnyStr]): @overload def finditer(self: Pattern[str], string: str, pos: int = 0, endpos: int = sys.maxsize) -> Iterator[Match[str]]: ... @overload - def finditer(self: Pattern[bytes], string: ReadableBuffer, pos: int = 0, endpos: int = sys.maxsize) -> Iterator[Match[bytes]]: ... # type: ignore[misc] + def finditer(self: Pattern[bytes], string: ReadableBuffer, pos: int = 0, endpos: int = sys.maxsize) -> Iterator[Match[bytes]]: ... # type: ignore[overload-overlap] @overload def finditer(self, string: AnyStr, pos: int = 0, endpos: int = sys.maxsize) -> Iterator[Match[AnyStr]]: ... @overload def sub(self: Pattern[str], repl: str | Callable[[Match[str]], str], string: str, count: int = 0) -> str: ... @overload - def sub( # type: ignore[misc] + def sub( # type: ignore[overload-overlap] self: Pattern[bytes], repl: ReadableBuffer | Callable[[Match[bytes]], ReadableBuffer], string: ReadableBuffer, @@ -165,7 +165,7 @@ class Pattern(Generic[AnyStr]): @overload def subn(self: Pattern[str], repl: str | Callable[[Match[str]], str], string: str, count: int = 0) -> tuple[str, int]: ... @overload - def subn( # type: ignore[misc] + def subn( # type: ignore[overload-overlap] self: Pattern[bytes], repl: ReadableBuffer | Callable[[Match[bytes]], ReadableBuffer], string: ReadableBuffer, @@ -174,7 +174,9 @@ class Pattern(Generic[AnyStr]): @overload def subn(self, repl: AnyStr | Callable[[Match[AnyStr]], AnyStr], string: AnyStr, count: int = 0) -> tuple[AnyStr, int]: ... def __copy__(self) -> Pattern[AnyStr]: ... - def __deepcopy__(self, __memo: Any) -> Pattern[AnyStr]: ... + def __deepcopy__(self, memo: Any, /) -> Pattern[AnyStr]: ... + def __eq__(self, value: object, /) -> bool: ... + def __hash__(self) -> int: ... if sys.version_info >= (3, 9): def __class_getitem__(cls, item: Any) -> GenericAlias: ... diff --git a/mypy/typeshed/stdlib/readline.pyi b/mypy/typeshed/stdlib/readline.pyi index 14c01a986351..688ae48d9f92 100644 --- a/mypy/typeshed/stdlib/readline.pyi +++ b/mypy/typeshed/stdlib/readline.pyi @@ -7,30 +7,30 @@ if sys.platform != "win32": _Completer: TypeAlias = Callable[[str, int], str | None] _CompDisp: TypeAlias = Callable[[str, Sequence[str], int], None] - def parse_and_bind(__string: str) -> None: ... - def read_init_file(__filename: StrOrBytesPath | None = None) -> None: ... + def parse_and_bind(string: str, /) -> None: ... + def read_init_file(filename: StrOrBytesPath | None = None, /) -> None: ... def get_line_buffer() -> str: ... - def insert_text(__string: str) -> None: ... + def insert_text(string: str, /) -> None: ... def redisplay() -> None: ... - def read_history_file(__filename: StrOrBytesPath | None = None) -> None: ... - def write_history_file(__filename: StrOrBytesPath | None = None) -> None: ... - def append_history_file(__nelements: int, __filename: StrOrBytesPath | None = None) -> None: ... + def read_history_file(filename: StrOrBytesPath | None = None, /) -> None: ... + def write_history_file(filename: StrOrBytesPath | None = None, /) -> None: ... + def append_history_file(nelements: int, filename: StrOrBytesPath | None = None, /) -> None: ... def get_history_length() -> int: ... - def set_history_length(__length: int) -> None: ... + def set_history_length(length: int, /) -> None: ... def clear_history() -> None: ... def get_current_history_length() -> int: ... - def get_history_item(__index: int) -> str: ... - def remove_history_item(__pos: int) -> None: ... - def replace_history_item(__pos: int, __line: str) -> None: ... - def add_history(__string: str) -> None: ... - def set_auto_history(__enabled: bool) -> None: ... - def set_startup_hook(__function: Callable[[], object] | None = None) -> None: ... - def set_pre_input_hook(__function: Callable[[], object] | None = None) -> None: ... - def set_completer(__function: _Completer | None = None) -> None: ... + def get_history_item(index: int, /) -> str: ... + def remove_history_item(pos: int, /) -> None: ... + def replace_history_item(pos: int, line: str, /) -> None: ... + def add_history(string: str, /) -> None: ... + def set_auto_history(enabled: bool, /) -> None: ... + def set_startup_hook(function: Callable[[], object] | None = None, /) -> None: ... + def set_pre_input_hook(function: Callable[[], object] | None = None, /) -> None: ... + def set_completer(function: _Completer | None = None, /) -> None: ... def get_completer() -> _Completer | None: ... def get_completion_type() -> int: ... def get_begidx() -> int: ... def get_endidx() -> int: ... - def set_completer_delims(__string: str) -> None: ... + def set_completer_delims(string: str, /) -> None: ... def get_completer_delims() -> str: ... - def set_completion_display_matches_hook(__function: _CompDisp | None = None) -> None: ... + def set_completion_display_matches_hook(function: _CompDisp | None = None, /) -> None: ... diff --git a/mypy/typeshed/stdlib/reprlib.pyi b/mypy/typeshed/stdlib/reprlib.pyi index 21c8a5cd4e0c..68ada6569348 100644 --- a/mypy/typeshed/stdlib/reprlib.pyi +++ b/mypy/typeshed/stdlib/reprlib.pyi @@ -1,3 +1,4 @@ +import sys from array import array from collections import deque from collections.abc import Callable @@ -22,6 +23,30 @@ class Repr: maxlong: int maxstring: int maxother: int + if sys.version_info >= (3, 11): + fillvalue: str + if sys.version_info >= (3, 12): + indent: str | int | None + + if sys.version_info >= (3, 12): + def __init__( + self, + *, + maxlevel: int = 6, + maxtuple: int = 6, + maxlist: int = 6, + maxarray: int = 5, + maxdict: int = 4, + maxset: int = 6, + maxfrozenset: int = 6, + maxdeque: int = 6, + maxstring: int = 30, + maxlong: int = 40, + maxother: int = 30, + fillvalue: str = "...", + indent: str | int | None = None, + ) -> None: ... + def repr(self, x: Any) -> str: ... def repr1(self, x: Any, level: int) -> str: ... def repr_tuple(self, x: tuple[Any, ...], level: int) -> str: ... diff --git a/mypy/typeshed/stdlib/resource.pyi b/mypy/typeshed/stdlib/resource.pyi index f2e979ff89af..5e468c2cead5 100644 --- a/mypy/typeshed/stdlib/resource.pyi +++ b/mypy/typeshed/stdlib/resource.pyi @@ -1,7 +1,6 @@ import sys from _typeshed import structseq -from typing import overload -from typing_extensions import Final, final +from typing import Final, final if sys.platform != "win32": RLIMIT_AS: int @@ -25,6 +24,7 @@ if sys.platform != "win32": RLIMIT_RTTIME: int RLIMIT_SIGPENDING: int RUSAGE_THREAD: int + @final class struct_rusage( structseq[float], tuple[float, float, int, int, int, int, int, int, int, int, int, int, int, int, int, int] @@ -48,6 +48,7 @@ if sys.platform != "win32": "ru_nvcsw", "ru_nivcsw", ) + @property def ru_utime(self) -> float: ... @property @@ -82,12 +83,12 @@ if sys.platform != "win32": def ru_nivcsw(self) -> int: ... def getpagesize() -> int: ... - def getrlimit(__resource: int) -> tuple[int, int]: ... - def getrusage(__who: int) -> struct_rusage: ... - def setrlimit(__resource: int, __limits: tuple[int, int]) -> None: ... + def getrlimit(resource: int, /) -> tuple[int, int]: ... + def getrusage(who: int, /) -> struct_rusage: ... + def setrlimit(resource: int, limits: tuple[int, int], /) -> None: ... if sys.platform == "linux": - @overload - def prlimit(pid: int, resource: int, limits: tuple[int, int]) -> tuple[int, int]: ... - @overload - def prlimit(pid: int, resource: int) -> tuple[int, int]: ... + if sys.version_info >= (3, 12): + def prlimit(pid: int, resource: int, limits: tuple[int, int] | None = None, /) -> tuple[int, int]: ... + else: + def prlimit(pid: int, resource: int, limits: tuple[int, int] = ..., /) -> tuple[int, int]: ... error = OSError diff --git a/mypy/typeshed/stdlib/sched.pyi b/mypy/typeshed/stdlib/sched.pyi index a8ec78d68fd2..75dd63d0414a 100644 --- a/mypy/typeshed/stdlib/sched.pyi +++ b/mypy/typeshed/stdlib/sched.pyi @@ -30,10 +30,10 @@ class scheduler: def __init__(self, timefunc: Callable[[], float] = ..., delayfunc: Callable[[float], object] = ...) -> None: ... def enterabs( - self, time: float, priority: Any, action: _ActionCallback, argument: tuple[Any, ...] = ..., kwargs: dict[str, Any] = ... + self, time: float, priority: Any, action: _ActionCallback, argument: tuple[Any, ...] = (), kwargs: dict[str, Any] = ... ) -> Event: ... def enter( - self, delay: float, priority: Any, action: _ActionCallback, argument: tuple[Any, ...] = ..., kwargs: dict[str, Any] = ... + self, delay: float, priority: Any, action: _ActionCallback, argument: tuple[Any, ...] = (), kwargs: dict[str, Any] = ... ) -> Event: ... def run(self, blocking: bool = True) -> float | None: ... def cancel(self, event: Event) -> None: ... diff --git a/mypy/typeshed/stdlib/select.pyi b/mypy/typeshed/stdlib/select.pyi index 412fd71ee38d..6d4c8d8f4c15 100644 --- a/mypy/typeshed/stdlib/select.pyi +++ b/mypy/typeshed/stdlib/select.pyi @@ -2,8 +2,8 @@ import sys from _typeshed import FileDescriptorLike from collections.abc import Iterable from types import TracebackType -from typing import Any -from typing_extensions import Self, final +from typing import Any, final +from typing_extensions import Self if sys.platform != "win32": PIPE_BUF: int @@ -15,7 +15,8 @@ if sys.platform != "win32": POLLOUT: int POLLPRI: int POLLRDBAND: int - POLLRDHUP: int + if sys.platform == "linux": + POLLRDHUP: int POLLRDNORM: int POLLWRBAND: int POLLWRNORM: int @@ -27,7 +28,7 @@ class poll: def poll(self, timeout: float | None = ...) -> list[tuple[int, int]]: ... def select( - __rlist: Iterable[Any], __wlist: Iterable[Any], __xlist: Iterable[Any], __timeout: float | None = None + rlist: Iterable[Any], wlist: Iterable[Any], xlist: Iterable[Any], timeout: float | None = None, / ) -> tuple[list[Any], list[Any], list[Any]]: ... error = OSError @@ -51,6 +52,7 @@ if sys.platform != "linux" and sys.platform != "win32": data: Any = ..., udata: Any = ..., ) -> None: ... + # BSD only @final class kqueue: @@ -58,11 +60,12 @@ if sys.platform != "linux" and sys.platform != "win32": def __init__(self) -> None: ... def close(self) -> None: ... def control( - self, __changelist: Iterable[kevent] | None, __maxevents: int, __timeout: float | None = None + self, changelist: Iterable[kevent] | None, maxevents: int, timeout: float | None = None, / ) -> list[kevent]: ... def fileno(self) -> int: ... @classmethod - def fromfd(cls, __fd: FileDescriptorLike) -> kqueue: ... + def fromfd(cls, fd: FileDescriptorLike, /) -> kqueue: ... + KQ_EV_ADD: int KQ_EV_CLEAR: int KQ_EV_DELETE: int @@ -109,9 +112,10 @@ if sys.platform == "linux": def __enter__(self) -> Self: ... def __exit__( self, - __exc_type: type[BaseException] | None = None, - __exc_val: BaseException | None = ..., - __exc_tb: TracebackType | None = None, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = ..., + exc_tb: TracebackType | None = None, + /, ) -> None: ... def close(self) -> None: ... closed: bool @@ -121,7 +125,8 @@ if sys.platform == "linux": def unregister(self, fd: FileDescriptorLike) -> None: ... def poll(self, timeout: float | None = None, maxevents: int = -1) -> list[tuple[int, int]]: ... @classmethod - def fromfd(cls, __fd: FileDescriptorLike) -> epoll: ... + def fromfd(cls, fd: FileDescriptorLike, /) -> epoll: ... + EPOLLERR: int EPOLLEXCLUSIVE: int EPOLLET: int @@ -136,7 +141,6 @@ if sys.platform == "linux": EPOLLRDNORM: int EPOLLWRBAND: int EPOLLWRNORM: int - EPOLL_RDHUP: int EPOLL_CLOEXEC: int if sys.platform != "linux" and sys.platform != "darwin" and sys.platform != "win32": diff --git a/mypy/typeshed/stdlib/selectors.pyi b/mypy/typeshed/stdlib/selectors.pyi index 90a923f09355..a857d0e242ab 100644 --- a/mypy/typeshed/stdlib/selectors.pyi +++ b/mypy/typeshed/stdlib/selectors.pyi @@ -31,43 +31,37 @@ class BaseSelector(metaclass=ABCMeta): def __enter__(self) -> Self: ... def __exit__(self, *args: Unused) -> None: ... -class SelectSelector(BaseSelector): +class _BaseSelectorImpl(BaseSelector, metaclass=ABCMeta): def register(self, fileobj: FileDescriptorLike, events: _EventMask, data: Any = None) -> SelectorKey: ... def unregister(self, fileobj: FileDescriptorLike) -> SelectorKey: ... - def select(self, timeout: float | None = None) -> list[tuple[SelectorKey, _EventMask]]: ... + def modify(self, fileobj: FileDescriptorLike, events: _EventMask, data: Any = None) -> SelectorKey: ... def get_map(self) -> Mapping[FileDescriptorLike, SelectorKey]: ... +class SelectSelector(_BaseSelectorImpl): + def select(self, timeout: float | None = None) -> list[tuple[SelectorKey, _EventMask]]: ... + +class _PollLikeSelector(_BaseSelectorImpl): + def select(self, timeout: float | None = None) -> list[tuple[SelectorKey, _EventMask]]: ... + if sys.platform != "win32": - class PollSelector(BaseSelector): - def register(self, fileobj: FileDescriptorLike, events: _EventMask, data: Any = None) -> SelectorKey: ... - def unregister(self, fileobj: FileDescriptorLike) -> SelectorKey: ... - def select(self, timeout: float | None = None) -> list[tuple[SelectorKey, _EventMask]]: ... - def get_map(self) -> Mapping[FileDescriptorLike, SelectorKey]: ... + class PollSelector(_PollLikeSelector): ... if sys.platform == "linux": - class EpollSelector(BaseSelector): + class EpollSelector(_PollLikeSelector): def fileno(self) -> int: ... - def register(self, fileobj: FileDescriptorLike, events: _EventMask, data: Any = None) -> SelectorKey: ... - def unregister(self, fileobj: FileDescriptorLike) -> SelectorKey: ... - def select(self, timeout: float | None = None) -> list[tuple[SelectorKey, _EventMask]]: ... - def get_map(self) -> Mapping[FileDescriptorLike, SelectorKey]: ... -class DevpollSelector(BaseSelector): +class DevpollSelector(_PollLikeSelector): def fileno(self) -> int: ... - def register(self, fileobj: FileDescriptorLike, events: _EventMask, data: Any = ...) -> SelectorKey: ... - def unregister(self, fileobj: FileDescriptorLike) -> SelectorKey: ... - def select(self, timeout: float | None = ...) -> list[tuple[SelectorKey, _EventMask]]: ... - def get_map(self) -> Mapping[FileDescriptorLike, SelectorKey]: ... -class KqueueSelector(BaseSelector): - def fileno(self) -> int: ... - def register(self, fileobj: FileDescriptorLike, events: _EventMask, data: Any = None) -> SelectorKey: ... - def unregister(self, fileobj: FileDescriptorLike) -> SelectorKey: ... - def select(self, timeout: float | None = None) -> list[tuple[SelectorKey, _EventMask]]: ... - def get_map(self) -> Mapping[FileDescriptorLike, SelectorKey]: ... +if sys.platform != "win32": + class KqueueSelector(_BaseSelectorImpl): + def fileno(self) -> int: ... + def select(self, timeout: float | None = None) -> list[tuple[SelectorKey, _EventMask]]: ... -class DefaultSelector(BaseSelector): - def register(self, fileobj: FileDescriptorLike, events: _EventMask, data: Any = None) -> SelectorKey: ... - def unregister(self, fileobj: FileDescriptorLike) -> SelectorKey: ... +# Not a real class at runtime, it is just a conditional alias to other real selectors. +# The runtime logic is more fine-grained than a `sys.platform` check; +# not really expressible in the stubs +class DefaultSelector(_BaseSelectorImpl): def select(self, timeout: float | None = None) -> list[tuple[SelectorKey, _EventMask]]: ... - def get_map(self) -> Mapping[FileDescriptorLike, SelectorKey]: ... + if sys.platform != "win32": + def fileno(self) -> int: ... diff --git a/mypy/typeshed/stdlib/shelve.pyi b/mypy/typeshed/stdlib/shelve.pyi index 82d0b03f4049..654c2ea097f7 100644 --- a/mypy/typeshed/stdlib/shelve.pyi +++ b/mypy/typeshed/stdlib/shelve.pyi @@ -1,3 +1,5 @@ +import sys +from _typeshed import StrOrBytesPath from collections.abc import Iterator, MutableMapping from dbm import _TFlags from types import TracebackType @@ -15,8 +17,10 @@ class Shelf(MutableMapping[str, _VT]): ) -> None: ... def __iter__(self) -> Iterator[str]: ... def __len__(self) -> int: ... + @overload # type: ignore[override] + def get(self, key: str, default: None = None) -> _VT | None: ... @overload - def get(self, key: str) -> _VT | None: ... + def get(self, key: str, default: _VT) -> _VT: ... @overload def get(self, key: str, default: _T) -> _VT | _T: ... def __getitem__(self, key: str) -> _VT: ... @@ -27,6 +31,7 @@ class Shelf(MutableMapping[str, _VT]): def __exit__( self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None ) -> None: ... + def __del__(self) -> None: ... def close(self) -> None: ... def sync(self) -> None: ... @@ -38,6 +43,17 @@ class BsdDbShelf(Shelf[_VT]): def last(self) -> tuple[str, _VT]: ... class DbfilenameShelf(Shelf[_VT]): - def __init__(self, filename: str, flag: _TFlags = "c", protocol: int | None = None, writeback: bool = False) -> None: ... + if sys.version_info >= (3, 11): + def __init__( + self, filename: StrOrBytesPath, flag: _TFlags = "c", protocol: int | None = None, writeback: bool = False + ) -> None: ... + else: + def __init__(self, filename: str, flag: _TFlags = "c", protocol: int | None = None, writeback: bool = False) -> None: ... -def open(filename: str, flag: _TFlags = "c", protocol: int | None = None, writeback: bool = False) -> Shelf[Any]: ... +if sys.version_info >= (3, 11): + def open( + filename: StrOrBytesPath, flag: _TFlags = "c", protocol: int | None = None, writeback: bool = False + ) -> Shelf[Any]: ... + +else: + def open(filename: str, flag: _TFlags = "c", protocol: int | None = None, writeback: bool = False) -> Shelf[Any]: ... diff --git a/mypy/typeshed/stdlib/shlex.pyi b/mypy/typeshed/stdlib/shlex.pyi index fa04932db676..daa8df439b26 100644 --- a/mypy/typeshed/stdlib/shlex.pyi +++ b/mypy/typeshed/stdlib/shlex.pyi @@ -1,20 +1,32 @@ import sys +from collections import deque from collections.abc import Iterable -from typing import TextIO -from typing_extensions import Self +from io import TextIOWrapper +from typing import Literal, Protocol, overload, type_check_only +from typing_extensions import Self, deprecated -if sys.version_info >= (3, 8): - __all__ = ["shlex", "split", "quote", "join"] -else: - __all__ = ["shlex", "split", "quote"] +__all__ = ["shlex", "split", "quote", "join"] + +@type_check_only +class _ShlexInstream(Protocol): + def read(self, size: Literal[1], /) -> str: ... + def readline(self) -> object: ... + def close(self) -> object: ... -def split(s: str, comments: bool = False, posix: bool = True) -> list[str]: ... +if sys.version_info >= (3, 12): + def split(s: str | _ShlexInstream, comments: bool = False, posix: bool = True) -> list[str]: ... -if sys.version_info >= (3, 8): - def join(split_command: Iterable[str]) -> str: ... +else: + @overload + def split(s: str | _ShlexInstream, comments: bool = False, posix: bool = True) -> list[str]: ... + @overload + @deprecated("Passing None for 's' to shlex.split() is deprecated and will raise an error in Python 3.12.") + def split(s: None, comments: bool = False, posix: bool = True) -> list[str]: ... +def join(split_command: Iterable[str]) -> str: ... def quote(s: str) -> str: ... +# TODO: Make generic over infile once PEP 696 is implemented. class shlex(Iterable[str]): commenters: str wordchars: str @@ -24,27 +36,28 @@ class shlex(Iterable[str]): escapedquotes: str whitespace_split: bool infile: str | None - instream: TextIO + instream: _ShlexInstream source: str debug: int lineno: int token: str - eof: str + filestack: deque[tuple[str | None, _ShlexInstream, int]] + eof: str | None @property def punctuation_chars(self) -> str: ... def __init__( self, - instream: str | TextIO | None = None, + instream: str | _ShlexInstream | None = None, infile: str | None = None, posix: bool = False, punctuation_chars: bool | str = False, ) -> None: ... - def get_token(self) -> str: ... + def get_token(self) -> str | None: ... def push_token(self, tok: str) -> None: ... - def read_token(self) -> str: ... - def sourcehook(self, newfile: str) -> tuple[str, TextIO]: ... - def push_source(self, newstream: str | TextIO, newfile: str | None = None) -> None: ... + def read_token(self) -> str | None: ... + def sourcehook(self, newfile: str) -> tuple[str, TextIOWrapper] | None: ... + def push_source(self, newstream: str | _ShlexInstream, newfile: str | None = None) -> None: ... def pop_source(self) -> None: ... - def error_leader(self, infile: str | None = None, lineno: int | None = None) -> None: ... + def error_leader(self, infile: str | None = None, lineno: int | None = None) -> str: ... def __iter__(self) -> Self: ... def __next__(self) -> str: ... diff --git a/mypy/typeshed/stdlib/shutil.pyi b/mypy/typeshed/stdlib/shutil.pyi index 0e4f521e5e34..a06181ce876d 100644 --- a/mypy/typeshed/stdlib/shutil.pyi +++ b/mypy/typeshed/stdlib/shutil.pyi @@ -2,8 +2,9 @@ import os import sys from _typeshed import BytesPath, FileDescriptorOrPath, StrOrBytesPath, StrPath, SupportsRead, SupportsWrite from collections.abc import Callable, Iterable, Sequence +from tarfile import _TarfileFilter from typing import Any, AnyStr, NamedTuple, Protocol, TypeVar, overload -from typing_extensions import TypeAlias +from typing_extensions import TypeAlias, deprecated __all__ = [ "copyfileobj", @@ -47,12 +48,7 @@ class ExecError(OSError): ... class ReadError(OSError): ... class RegistryError(Exception): ... -if sys.version_info >= (3, 8): - def copyfileobj(fsrc: SupportsRead[AnyStr], fdst: SupportsWrite[AnyStr], length: int = 0) -> None: ... - -else: - def copyfileobj(fsrc: SupportsRead[AnyStr], fdst: SupportsWrite[AnyStr], length: int = 16384) -> None: ... - +def copyfileobj(fsrc: SupportsRead[AnyStr], fdst: SupportsWrite[AnyStr], length: int = 0) -> None: ... def copyfile(src: StrOrBytesPath, dst: _StrOrBytesPathT, *, follow_symlinks: bool = True) -> _StrOrBytesPathT: ... def copymode(src: StrOrBytesPath, dst: StrOrBytesPath, *, follow_symlinks: bool = True) -> None: ... def copystat(src: StrOrBytesPath, dst: StrOrBytesPath, *, follow_symlinks: bool = True) -> None: ... @@ -65,44 +61,52 @@ def copy2(src: StrPath, dst: StrPath, *, follow_symlinks: bool = True) -> _PathR @overload def copy2(src: BytesPath, dst: BytesPath, *, follow_symlinks: bool = True) -> _PathReturn: ... def ignore_patterns(*patterns: StrPath) -> Callable[[Any, list[str]], set[str]]: ... - -if sys.version_info >= (3, 8): - def copytree( - src: StrPath, - dst: StrPath, - symlinks: bool = False, - ignore: None | Callable[[str, list[str]], Iterable[str]] | Callable[[StrPath, list[str]], Iterable[str]] = None, - copy_function: Callable[[str, str], object] = ..., - ignore_dangling_symlinks: bool = False, - dirs_exist_ok: bool = False, - ) -> _PathReturn: ... - -else: - def copytree( - src: StrPath, - dst: StrPath, - symlinks: bool = False, - ignore: None | Callable[[str, list[str]], Iterable[str]] | Callable[[StrPath, list[str]], Iterable[str]] = None, - copy_function: Callable[[str, str], object] = ..., - ignore_dangling_symlinks: bool = False, - ) -> _PathReturn: ... - -_OnErrorCallback: TypeAlias = Callable[[Callable[..., Any], Any, Any], object] +def copytree( + src: StrPath, + dst: StrPath, + symlinks: bool = False, + ignore: None | Callable[[str, list[str]], Iterable[str]] | Callable[[StrPath, list[str]], Iterable[str]] = None, + copy_function: Callable[[str, str], object] = ..., + ignore_dangling_symlinks: bool = False, + dirs_exist_ok: bool = False, +) -> _PathReturn: ... + +_OnErrorCallback: TypeAlias = Callable[[Callable[..., Any], str, Any], object] +_OnExcCallback: TypeAlias = Callable[[Callable[..., Any], str, Exception], object] class _RmtreeType(Protocol): avoids_symlink_attacks: bool - if sys.version_info >= (3, 11): + if sys.version_info >= (3, 12): + @overload + def __call__(self, path: StrOrBytesPath, ignore_errors: bool = False, *, dir_fd: int | None = None) -> None: ... + @overload + @deprecated("The `onerror` parameter is deprecated and will be removed in Python 3.14. Use `onexc` instead.") def __call__( self, path: StrOrBytesPath, - ignore_errors: bool = ..., - onerror: _OnErrorCallback | None = ..., + ignore_errors: bool = False, + onerror: _OnErrorCallback | None = None, *, - dir_fd: int | None = ..., + dir_fd: int | None = None, + ) -> None: ... + @overload + def __call__( + self, path: StrOrBytesPath, ignore_errors: bool = False, *, onexc: _OnExcCallback, dir_fd: int | None = None + ) -> None: ... + elif sys.version_info >= (3, 11): + def __call__( + self, + path: StrOrBytesPath, + ignore_errors: bool = False, + onerror: _OnErrorCallback | None = None, + *, + dir_fd: int | None = None, ) -> None: ... else: - def __call__(self, path: StrOrBytesPath, ignore_errors: bool = ..., onerror: _OnErrorCallback | None = ...) -> None: ... + def __call__( + self, path: StrOrBytesPath, ignore_errors: bool = False, onerror: _OnErrorCallback | None = None + ) -> None: ... rmtree: _RmtreeType @@ -129,23 +133,17 @@ def disk_usage(path: FileDescriptorOrPath) -> _ntuple_diskusage: ... # see https://bugs.python.org/issue33140. We keep it here because it's # in __all__. @overload -def chown(path: StrOrBytesPath, user: str | int, group: None = None) -> None: ... +def chown(path: FileDescriptorOrPath, user: str | int, group: None = None) -> None: ... @overload -def chown(path: StrOrBytesPath, user: None = None, *, group: str | int) -> None: ... +def chown(path: FileDescriptorOrPath, user: None = None, *, group: str | int) -> None: ... @overload -def chown(path: StrOrBytesPath, user: None, group: str | int) -> None: ... +def chown(path: FileDescriptorOrPath, user: None, group: str | int) -> None: ... @overload -def chown(path: StrOrBytesPath, user: str | int, group: str | int) -> None: ... - -if sys.version_info >= (3, 8): - @overload - def which(cmd: _StrPathT, mode: int = 1, path: StrPath | None = None) -> str | _StrPathT | None: ... - @overload - def which(cmd: bytes, mode: int = 1, path: StrPath | None = None) -> bytes | None: ... - -else: - def which(cmd: _StrPathT, mode: int = 1, path: StrPath | None = None) -> str | _StrPathT | None: ... - +def chown(path: FileDescriptorOrPath, user: str | int, group: str | int) -> None: ... +@overload +def which(cmd: _StrPathT, mode: int = 1, path: StrPath | None = None) -> str | _StrPathT | None: ... +@overload +def which(cmd: bytes, mode: int = 1, path: StrPath | None = None) -> bytes | None: ... def make_archive( base_name: str, format: str, @@ -167,7 +165,9 @@ def register_archive_format( name: str, function: Callable[[str, str], object], extra_args: None = None, description: str = "" ) -> None: ... def unregister_archive_format(name: str) -> None: ... -def unpack_archive(filename: StrPath, extract_dir: StrPath | None = None, format: str | None = None) -> None: ... +def unpack_archive( + filename: StrPath, extract_dir: StrPath | None = None, format: str | None = None, *, filter: _TarfileFilter | None = None +) -> None: ... @overload def register_unpack_format( name: str, @@ -182,4 +182,4 @@ def register_unpack_format( ) -> None: ... def unregister_unpack_format(name: str) -> None: ... def get_unpack_formats() -> list[tuple[str, list[str], str]]: ... -def get_terminal_size(fallback: tuple[int, int] = ...) -> os.terminal_size: ... +def get_terminal_size(fallback: tuple[int, int] = (80, 24)) -> os.terminal_size: ... diff --git a/mypy/typeshed/stdlib/signal.pyi b/mypy/typeshed/stdlib/signal.pyi index e411d47016b6..cbb7440b9147 100644 --- a/mypy/typeshed/stdlib/signal.pyi +++ b/mypy/typeshed/stdlib/signal.pyi @@ -3,60 +3,63 @@ from _typeshed import structseq from collections.abc import Callable, Iterable from enum import IntEnum from types import FrameType -from typing import Any -from typing_extensions import Final, Never, TypeAlias, final +from typing import Any, Final, final +from typing_extensions import Never, TypeAlias NSIG: int class Signals(IntEnum): - SIGABRT: int - SIGEMT: int - SIGFPE: int - SIGILL: int - SIGINFO: int - SIGINT: int - SIGSEGV: int - SIGTERM: int + SIGABRT = 6 + SIGFPE = 8 + SIGILL = 4 + SIGINT = 2 + SIGSEGV = 11 + SIGTERM = 15 if sys.platform == "win32": - SIGBREAK: int - CTRL_C_EVENT: int - CTRL_BREAK_EVENT: int + SIGBREAK = 21 + CTRL_C_EVENT = 0 + CTRL_BREAK_EVENT = 1 else: - SIGALRM: int - SIGBUS: int - SIGCHLD: int - SIGCONT: int - SIGHUP: int - SIGIO: int - SIGIOT: int - SIGKILL: int - SIGPIPE: int - SIGPROF: int - SIGQUIT: int - SIGSTOP: int - SIGSYS: int - SIGTRAP: int - SIGTSTP: int - SIGTTIN: int - SIGTTOU: int - SIGURG: int - SIGUSR1: int - SIGUSR2: int - SIGVTALRM: int - SIGWINCH: int - SIGXCPU: int - SIGXFSZ: int + SIGALRM = 14 + SIGBUS = 7 + SIGCHLD = 17 + SIGCONT = 18 + SIGHUP = 1 + SIGIO = 29 + SIGIOT = 6 + SIGKILL = 9 + SIGPIPE = 13 + SIGPROF = 27 + SIGQUIT = 3 + SIGSTOP = 19 + SIGSYS = 31 + SIGTRAP = 5 + SIGTSTP = 20 + SIGTTIN = 21 + SIGTTOU = 22 + SIGURG = 23 + SIGUSR1 = 10 + SIGUSR2 = 12 + SIGVTALRM = 26 + SIGWINCH = 28 + SIGXCPU = 24 + SIGXFSZ = 25 + if sys.platform != "linux": + SIGEMT = 7 + SIGINFO = 29 if sys.platform != "darwin": - SIGCLD: int - SIGPOLL: int - SIGPWR: int - SIGRTMAX: int - SIGRTMIN: int + SIGCLD = 17 + SIGPOLL = 29 + SIGPWR = 30 + SIGRTMAX = 64 + SIGRTMIN = 34 + if sys.version_info >= (3, 11): + SIGSTKFLT = 16 class Handlers(IntEnum): - SIG_DFL: int - SIG_IGN: int + SIG_DFL = 0 + SIG_IGN = 1 SIG_DFL: Handlers SIG_IGN: Handlers @@ -64,21 +67,19 @@ SIG_IGN: Handlers _SIGNUM: TypeAlias = int | Signals _HANDLER: TypeAlias = Callable[[int, FrameType | None], Any] | int | Handlers | None -def default_int_handler(__signalnum: int, __frame: FrameType | None) -> Never: ... +def default_int_handler(signalnum: int, frame: FrameType | None, /) -> Never: ... if sys.version_info >= (3, 10): # arguments changed in 3.10.2 def getsignal(signalnum: _SIGNUM) -> _HANDLER: ... def signal(signalnum: _SIGNUM, handler: _HANDLER) -> _HANDLER: ... else: - def getsignal(__signalnum: _SIGNUM) -> _HANDLER: ... - def signal(__signalnum: _SIGNUM, __handler: _HANDLER) -> _HANDLER: ... + def getsignal(signalnum: _SIGNUM, /) -> _HANDLER: ... + def signal(signalnum: _SIGNUM, handler: _HANDLER, /) -> _HANDLER: ... SIGABRT: Signals -SIGEMT: Signals SIGFPE: Signals SIGILL: Signals -SIGINFO: Signals SIGINT: Signals SIGSEGV: Signals SIGTERM: Signals @@ -88,6 +89,9 @@ if sys.platform == "win32": CTRL_C_EVENT: Signals CTRL_BREAK_EVENT: Signals else: + if sys.platform != "linux": + SIGINFO: Signals + SIGEMT: Signals SIGALRM: Signals SIGBUS: Signals SIGCHLD: Signals @@ -119,38 +123,43 @@ else: ITIMER_VIRTUAL: int class Sigmasks(IntEnum): - SIG_BLOCK: int - SIG_UNBLOCK: int - SIG_SETMASK: int + SIG_BLOCK = 0 + SIG_UNBLOCK = 1 + SIG_SETMASK = 2 + SIG_BLOCK = Sigmasks.SIG_BLOCK SIG_UNBLOCK = Sigmasks.SIG_UNBLOCK SIG_SETMASK = Sigmasks.SIG_SETMASK - def alarm(__seconds: int) -> int: ... - def getitimer(__which: int) -> tuple[float, float]: ... + def alarm(seconds: int, /) -> int: ... + def getitimer(which: int, /) -> tuple[float, float]: ... def pause() -> None: ... - def pthread_kill(__thread_id: int, __signalnum: int) -> None: ... + def pthread_kill(thread_id: int, signalnum: int, /) -> None: ... if sys.version_info >= (3, 10): # arguments changed in 3.10.2 def pthread_sigmask(how: int, mask: Iterable[int]) -> set[_SIGNUM]: ... else: - def pthread_sigmask(__how: int, __mask: Iterable[int]) -> set[_SIGNUM]: ... + def pthread_sigmask(how: int, mask: Iterable[int], /) -> set[_SIGNUM]: ... - def setitimer(__which: int, __seconds: float, __interval: float = 0.0) -> tuple[float, float]: ... - def siginterrupt(__signalnum: int, __flag: bool) -> None: ... + def setitimer(which: int, seconds: float, interval: float = 0.0, /) -> tuple[float, float]: ... + def siginterrupt(signalnum: int, flag: bool, /) -> None: ... def sigpending() -> Any: ... if sys.version_info >= (3, 10): # argument changed in 3.10.2 def sigwait(sigset: Iterable[int]) -> _SIGNUM: ... else: - def sigwait(__sigset: Iterable[int]) -> _SIGNUM: ... + def sigwait(sigset: Iterable[int], /) -> _SIGNUM: ... if sys.platform != "darwin": SIGCLD: Signals SIGPOLL: Signals SIGPWR: Signals SIGRTMAX: Signals SIGRTMIN: Signals + if sys.version_info >= (3, 11): + SIGSTKFLT: Signals + @final class struct_siginfo(structseq[int], tuple[int, int, int, int, int, int, int]): if sys.version_info >= (3, 10): __match_args__: Final = ("si_signo", "si_code", "si_errno", "si_pid", "si_uid", "si_status", "si_band") + @property def si_signo(self) -> int: ... @property @@ -166,16 +175,14 @@ else: @property def si_band(self) -> int: ... - def sigtimedwait(sigset: Iterable[int], timeout: float) -> struct_siginfo | None: ... - def sigwaitinfo(sigset: Iterable[int]) -> struct_siginfo: ... - -if sys.version_info >= (3, 8): - def strsignal(__signalnum: _SIGNUM) -> str | None: ... - def valid_signals() -> set[Signals]: ... - def raise_signal(__signalnum: _SIGNUM) -> None: ... + def sigtimedwait(sigset: Iterable[int], timeout: float, /) -> struct_siginfo | None: ... + def sigwaitinfo(sigset: Iterable[int], /) -> struct_siginfo: ... +def strsignal(signalnum: _SIGNUM, /) -> str | None: ... +def valid_signals() -> set[Signals]: ... +def raise_signal(signalnum: _SIGNUM, /) -> None: ... def set_wakeup_fd(fd: int, *, warn_on_full_buffer: bool = ...) -> int: ... if sys.version_info >= (3, 9): if sys.platform == "linux": - def pidfd_send_signal(__pidfd: int, __sig: int, __siginfo: None = None, __flags: int = ...) -> None: ... + def pidfd_send_signal(pidfd: int, sig: int, siginfo: None = None, flags: int = ..., /) -> None: ... diff --git a/mypy/typeshed/stdlib/smtplib.pyi b/mypy/typeshed/stdlib/smtplib.pyi index 0d7595fc1d6d..a762427bcab3 100644 --- a/mypy/typeshed/stdlib/smtplib.pyi +++ b/mypy/typeshed/stdlib/smtplib.pyi @@ -1,6 +1,6 @@ import sys from _socket import _Address as _SourceAddress -from _typeshed import ReadableBuffer, _BufferWithLen +from _typeshed import ReadableBuffer, SizedBuffer from collections.abc import Sequence from email.message import Message as _Message from re import Pattern @@ -68,9 +68,9 @@ def quotedata(data: str) -> str: ... class _AuthObject(Protocol): @overload - def __call__(self, challenge: None = None) -> str | None: ... + def __call__(self, challenge: None = None, /) -> str | None: ... @overload - def __call__(self, challenge: bytes) -> str: ... + def __call__(self, challenge: bytes, /) -> str: ... class SMTP: debuglevel: int @@ -111,8 +111,8 @@ class SMTP: def help(self, args: str = "") -> bytes: ... def rset(self) -> _Reply: ... def noop(self) -> _Reply: ... - def mail(self, sender: str, options: Sequence[str] = ...) -> _Reply: ... - def rcpt(self, recip: str, options: Sequence[str] = ...) -> _Reply: ... + def mail(self, sender: str, options: Sequence[str] = ()) -> _Reply: ... + def rcpt(self, recip: str, options: Sequence[str] = ()) -> _Reply: ... def data(self, msg: ReadableBuffer | str) -> _Reply: ... def verify(self, address: str) -> _Reply: ... vrfy = verify @@ -128,22 +128,28 @@ class SMTP: def auth_plain(self, challenge: ReadableBuffer | None = None) -> str: ... def auth_login(self, challenge: ReadableBuffer | None = None) -> str: ... def login(self, user: str, password: str, *, initial_response_ok: bool = True) -> _Reply: ... - def starttls(self, keyfile: str | None = None, certfile: str | None = None, context: SSLContext | None = None) -> _Reply: ... + if sys.version_info >= (3, 12): + def starttls(self, *, context: SSLContext | None = None) -> _Reply: ... + else: + def starttls( + self, keyfile: str | None = None, certfile: str | None = None, context: SSLContext | None = None + ) -> _Reply: ... + def sendmail( self, from_addr: str, to_addrs: str | Sequence[str], - msg: _BufferWithLen | str, - mail_options: Sequence[str] = ..., - rcpt_options: Sequence[str] = ..., + msg: SizedBuffer | str, + mail_options: Sequence[str] = (), + rcpt_options: Sequence[str] = (), ) -> _SendErrs: ... def send_message( self, msg: _Message, from_addr: str | None = None, to_addrs: str | Sequence[str] | None = None, - mail_options: Sequence[str] = ..., - rcpt_options: Sequence[str] = ..., + mail_options: Sequence[str] = (), + rcpt_options: Sequence[str] = (), ) -> _SendErrs: ... def close(self) -> None: ... def quit(self) -> _Reply: ... @@ -152,17 +158,29 @@ class SMTP_SSL(SMTP): keyfile: str | None certfile: str | None context: SSLContext - def __init__( - self, - host: str = "", - port: int = 0, - local_hostname: str | None = None, - keyfile: str | None = None, - certfile: str | None = None, - timeout: float = ..., - source_address: _SourceAddress | None = None, - context: SSLContext | None = None, - ) -> None: ... + if sys.version_info >= (3, 12): + def __init__( + self, + host: str = "", + port: int = 0, + local_hostname: str | None = None, + *, + timeout: float = ..., + source_address: _SourceAddress | None = None, + context: SSLContext | None = None, + ) -> None: ... + else: + def __init__( + self, + host: str = "", + port: int = 0, + local_hostname: str | None = None, + keyfile: str | None = None, + certfile: str | None = None, + timeout: float = ..., + source_address: _SourceAddress | None = None, + context: SSLContext | None = None, + ) -> None: ... LMTP_PORT: int diff --git a/mypy/typeshed/stdlib/socket.pyi b/mypy/typeshed/stdlib/socket.pyi index dbc1d46ec1d4..b626409d2dde 100644 --- a/mypy/typeshed/stdlib/socket.pyi +++ b/mypy/typeshed/stdlib/socket.pyi @@ -4,7 +4,7 @@ import _socket import sys from _socket import ( - _FD, + CAPI as CAPI, EAI_AGAIN as EAI_AGAIN, EAI_BADFLAGS as EAI_BADFLAGS, EAI_FAIL as EAI_FAIL, @@ -33,9 +33,28 @@ from _socket import ( IP_TTL as IP_TTL, IPPORT_RESERVED as IPPORT_RESERVED, IPPORT_USERRESERVED as IPPORT_USERRESERVED, + IPPROTO_AH as IPPROTO_AH, + IPPROTO_DSTOPTS as IPPROTO_DSTOPTS, + IPPROTO_EGP as IPPROTO_EGP, + IPPROTO_ESP as IPPROTO_ESP, + IPPROTO_FRAGMENT as IPPROTO_FRAGMENT, + IPPROTO_GGP as IPPROTO_GGP, + IPPROTO_HOPOPTS as IPPROTO_HOPOPTS, IPPROTO_ICMP as IPPROTO_ICMP, + IPPROTO_ICMPV6 as IPPROTO_ICMPV6, + IPPROTO_IDP as IPPROTO_IDP, + IPPROTO_IGMP as IPPROTO_IGMP, IPPROTO_IP as IPPROTO_IP, + IPPROTO_IPV4 as IPPROTO_IPV4, + IPPROTO_IPV6 as IPPROTO_IPV6, + IPPROTO_MAX as IPPROTO_MAX, + IPPROTO_ND as IPPROTO_ND, + IPPROTO_NONE as IPPROTO_NONE, + IPPROTO_PIM as IPPROTO_PIM, + IPPROTO_PUP as IPPROTO_PUP, IPPROTO_RAW as IPPROTO_RAW, + IPPROTO_ROUTING as IPPROTO_ROUTING, + IPPROTO_SCTP as IPPROTO_SCTP, IPPROTO_TCP as IPPROTO_TCP, IPPROTO_UDP as IPPROTO_UDP, IPV6_CHECKSUM as IPV6_CHECKSUM, @@ -82,11 +101,13 @@ from _socket import ( SOMAXCONN as SOMAXCONN, TCP_FASTOPEN as TCP_FASTOPEN, TCP_KEEPCNT as TCP_KEEPCNT, + TCP_KEEPINTVL as TCP_KEEPINTVL, TCP_MAXSEG as TCP_MAXSEG, TCP_NODELAY as TCP_NODELAY, SocketType as SocketType, _Address as _Address, _RetAddress as _RetAddress, + close as close, dup as dup, error as error, gaierror as gaierror, @@ -103,6 +124,9 @@ from _socket import ( herror as herror, htonl as htonl, htons as htons, + if_indextoname as if_indextoname, + if_nameindex as if_nameindex, + if_nametoindex as if_nametoindex, inet_aton as inet_aton, inet_ntoa as inet_ntoa, inet_ntop as inet_ntop, @@ -116,8 +140,20 @@ from _typeshed import ReadableBuffer, Unused, WriteableBuffer from collections.abc import Iterable from enum import IntEnum, IntFlag from io import BufferedReader, BufferedRWPair, BufferedWriter, IOBase, RawIOBase, TextIOWrapper -from typing import Any, Protocol, overload -from typing_extensions import Literal, Self +from typing import Any, Literal, Protocol, SupportsIndex, overload +from typing_extensions import Self + +if sys.platform == "win32": + from _socket import ( + RCVALL_MAX as RCVALL_MAX, + RCVALL_OFF as RCVALL_OFF, + RCVALL_ON as RCVALL_ON, + RCVALL_SOCKETLEVELONLY as RCVALL_SOCKETLEVELONLY, + SIO_KEEPALIVE_VALS as SIO_KEEPALIVE_VALS, + SIO_LOOPBACK_FAST_PATH as SIO_LOOPBACK_FAST_PATH, + SIO_RCVALL as SIO_RCVALL, + SO_EXCLUSIVEADDRUSE as SO_EXCLUSIVEADDRUSE, + ) if sys.platform != "darwin" or sys.version_info >= (3, 9): from _socket import ( @@ -129,54 +165,29 @@ if sys.platform != "darwin" or sys.version_info >= (3, 9): IPV6_RTHDR as IPV6_RTHDR, ) +if sys.platform == "darwin": + from _socket import PF_SYSTEM as PF_SYSTEM, SYSPROTO_CONTROL as SYSPROTO_CONTROL + if sys.platform != "darwin": - from _socket import SO_EXCLUSIVEADDRUSE as SO_EXCLUSIVEADDRUSE + from _socket import ( + IPPROTO_CBT as IPPROTO_CBT, + IPPROTO_ICLFXBM as IPPROTO_ICLFXBM, + IPPROTO_IGP as IPPROTO_IGP, + IPPROTO_L2TP as IPPROTO_L2TP, + IPPROTO_PGM as IPPROTO_PGM, + IPPROTO_RDP as IPPROTO_RDP, + IPPROTO_ST as IPPROTO_ST, + TCP_KEEPIDLE as TCP_KEEPIDLE, + ) if sys.version_info >= (3, 10): from _socket import IP_RECVTOS as IP_RECVTOS -elif sys.platform != "darwin" and sys.platform != "win32": +elif sys.platform != "win32" and sys.platform != "darwin": from _socket import IP_RECVTOS as IP_RECVTOS -from _socket import TCP_KEEPINTVL as TCP_KEEPINTVL, close as close - -if sys.platform != "darwin": - from _socket import TCP_KEEPIDLE as TCP_KEEPIDLE - -if sys.platform != "win32" or sys.version_info >= (3, 8): - from _socket import ( - IPPROTO_AH as IPPROTO_AH, - IPPROTO_DSTOPTS as IPPROTO_DSTOPTS, - IPPROTO_EGP as IPPROTO_EGP, - IPPROTO_ESP as IPPROTO_ESP, - IPPROTO_FRAGMENT as IPPROTO_FRAGMENT, - IPPROTO_GGP as IPPROTO_GGP, - IPPROTO_HOPOPTS as IPPROTO_HOPOPTS, - IPPROTO_ICMPV6 as IPPROTO_ICMPV6, - IPPROTO_IDP as IPPROTO_IDP, - IPPROTO_IGMP as IPPROTO_IGMP, - IPPROTO_IPV4 as IPPROTO_IPV4, - IPPROTO_IPV6 as IPPROTO_IPV6, - IPPROTO_MAX as IPPROTO_MAX, - IPPROTO_ND as IPPROTO_ND, - IPPROTO_NONE as IPPROTO_NONE, - IPPROTO_PIM as IPPROTO_PIM, - IPPROTO_PUP as IPPROTO_PUP, - IPPROTO_ROUTING as IPPROTO_ROUTING, - IPPROTO_SCTP as IPPROTO_SCTP, - ) - - if sys.platform != "darwin": - from _socket import ( - IPPROTO_CBT as IPPROTO_CBT, - IPPROTO_ICLFXBM as IPPROTO_ICLFXBM, - IPPROTO_IGP as IPPROTO_IGP, - IPPROTO_L2TP as IPPROTO_L2TP, - IPPROTO_PGM as IPPROTO_PGM, - IPPROTO_RDP as IPPROTO_RDP, - IPPROTO_ST as IPPROTO_ST, - ) if sys.platform != "win32" and sys.platform != "darwin": from _socket import ( + IP_BIND_ADDRESS_NO_PORT as IP_BIND_ADDRESS_NO_PORT, IP_TRANSPARENT as IP_TRANSPARENT, IPPROTO_BIP as IPPROTO_BIP, IPPROTO_MOBILE as IPPROTO_MOBILE, @@ -184,10 +195,14 @@ if sys.platform != "win32" and sys.platform != "darwin": IPX_TYPE as IPX_TYPE, SCM_CREDENTIALS as SCM_CREDENTIALS, SO_BINDTODEVICE as SO_BINDTODEVICE, + SO_DOMAIN as SO_DOMAIN, SO_MARK as SO_MARK, SO_PASSCRED as SO_PASSCRED, + SO_PASSSEC as SO_PASSSEC, SO_PEERCRED as SO_PEERCRED, + SO_PEERSEC as SO_PEERSEC, SO_PRIORITY as SO_PRIORITY, + SO_PROTOCOL as SO_PROTOCOL, SO_SETFIB as SO_SETFIB, SOL_ATALK as SOL_ATALK, SOL_AX25 as SOL_AX25, @@ -195,14 +210,17 @@ if sys.platform != "win32" and sys.platform != "darwin": SOL_IPX as SOL_IPX, SOL_NETROM as SOL_NETROM, SOL_ROSE as SOL_ROSE, + TCP_CONGESTION as TCP_CONGESTION, TCP_CORK as TCP_CORK, TCP_DEFER_ACCEPT as TCP_DEFER_ACCEPT, TCP_INFO as TCP_INFO, TCP_LINGER2 as TCP_LINGER2, TCP_QUICKACK as TCP_QUICKACK, TCP_SYNCNT as TCP_SYNCNT, + TCP_USER_TIMEOUT as TCP_USER_TIMEOUT, TCP_WINDOW_CLAMP as TCP_WINDOW_CLAMP, ) + if sys.platform != "win32": from _socket import ( CMSG_LEN as CMSG_LEN, @@ -232,6 +250,7 @@ if sys.platform != "win32": SCM_CREDS as SCM_CREDS, SCM_RIGHTS as SCM_RIGHTS, SO_REUSEPORT as SO_REUSEPORT, + TCP_NOTSENT_LOWAT as TCP_NOTSENT_LOWAT, sethostname as sethostname, ) @@ -249,9 +268,6 @@ if sys.platform != "win32": IPV6_USE_MIN_MTU as IPV6_USE_MIN_MTU, ) -if sys.platform != "win32" or sys.version_info >= (3, 8): - from _socket import if_indextoname as if_indextoname, if_nameindex as if_nameindex, if_nametoindex as if_nametoindex - if sys.platform != "darwin": if sys.platform != "win32" or sys.version_info >= (3, 9): from _socket import BDADDR_ANY as BDADDR_ANY, BDADDR_LOCAL as BDADDR_LOCAL, BTPROTO_RFCOMM as BTPROTO_RFCOMM @@ -259,6 +275,9 @@ if sys.platform != "darwin": if sys.platform == "darwin" and sys.version_info >= (3, 10): from _socket import TCP_KEEPALIVE as TCP_KEEPALIVE +if sys.platform == "darwin" and sys.version_info >= (3, 11): + from _socket import TCP_CONNECTION_INFO as TCP_CONNECTION_INFO + if sys.platform == "linux": from _socket import ( ALG_OP_DECRYPT as ALG_OP_DECRYPT, @@ -272,15 +291,27 @@ if sys.platform == "linux": ALG_SET_OP as ALG_SET_OP, ALG_SET_PUBKEY as ALG_SET_PUBKEY, CAN_BCM as CAN_BCM, + CAN_BCM_CAN_FD_FRAME as CAN_BCM_CAN_FD_FRAME, + CAN_BCM_RX_ANNOUNCE_RESUME as CAN_BCM_RX_ANNOUNCE_RESUME, CAN_BCM_RX_CHANGED as CAN_BCM_RX_CHANGED, + CAN_BCM_RX_CHECK_DLC as CAN_BCM_RX_CHECK_DLC, CAN_BCM_RX_DELETE as CAN_BCM_RX_DELETE, + CAN_BCM_RX_FILTER_ID as CAN_BCM_RX_FILTER_ID, + CAN_BCM_RX_NO_AUTOTIMER as CAN_BCM_RX_NO_AUTOTIMER, CAN_BCM_RX_READ as CAN_BCM_RX_READ, + CAN_BCM_RX_RTR_FRAME as CAN_BCM_RX_RTR_FRAME, CAN_BCM_RX_SETUP as CAN_BCM_RX_SETUP, CAN_BCM_RX_STATUS as CAN_BCM_RX_STATUS, CAN_BCM_RX_TIMEOUT as CAN_BCM_RX_TIMEOUT, + CAN_BCM_SETTIMER as CAN_BCM_SETTIMER, + CAN_BCM_STARTTIMER as CAN_BCM_STARTTIMER, + CAN_BCM_TX_ANNOUNCE as CAN_BCM_TX_ANNOUNCE, + CAN_BCM_TX_COUNTEVT as CAN_BCM_TX_COUNTEVT, + CAN_BCM_TX_CP_CAN_ID as CAN_BCM_TX_CP_CAN_ID, CAN_BCM_TX_DELETE as CAN_BCM_TX_DELETE, CAN_BCM_TX_EXPIRED as CAN_BCM_TX_EXPIRED, CAN_BCM_TX_READ as CAN_BCM_TX_READ, + CAN_BCM_TX_RESET_MULTI_IDX as CAN_BCM_TX_RESET_MULTI_IDX, CAN_BCM_TX_SEND as CAN_BCM_TX_SEND, CAN_BCM_TX_SETUP as CAN_BCM_TX_SETUP, CAN_BCM_TX_STATUS as CAN_BCM_TX_STATUS, @@ -288,6 +319,7 @@ if sys.platform == "linux": CAN_EFF_MASK as CAN_EFF_MASK, CAN_ERR_FLAG as CAN_ERR_FLAG, CAN_ERR_MASK as CAN_ERR_MASK, + CAN_ISOTP as CAN_ISOTP, CAN_RAW as CAN_RAW, CAN_RAW_ERR_FILTER as CAN_RAW_ERR_FILTER, CAN_RAW_FD_FRAMES as CAN_RAW_FD_FRAMES, @@ -296,6 +328,7 @@ if sys.platform == "linux": CAN_RAW_RECV_OWN_MSGS as CAN_RAW_RECV_OWN_MSGS, CAN_RTR_FLAG as CAN_RTR_FLAG, CAN_SFF_MASK as CAN_SFF_MASK, + IOCTL_VM_SOCKETS_GET_LOCAL_CID as IOCTL_VM_SOCKETS_GET_LOCAL_CID, NETLINK_ARPD as NETLINK_ARPD, NETLINK_CRYPTO as NETLINK_CRYPTO, NETLINK_DNRTMSG as NETLINK_DNRTMSG, @@ -338,6 +371,9 @@ if sys.platform == "linux": RDS_RDMA_SILENT as RDS_RDMA_SILENT, RDS_RDMA_USE_ONCE as RDS_RDMA_USE_ONCE, RDS_RECVERR as RDS_RECVERR, + SO_VM_SOCKETS_BUFFER_MAX_SIZE as SO_VM_SOCKETS_BUFFER_MAX_SIZE, + SO_VM_SOCKETS_BUFFER_MIN_SIZE as SO_VM_SOCKETS_BUFFER_MIN_SIZE, + SO_VM_SOCKETS_BUFFER_SIZE as SO_VM_SOCKETS_BUFFER_SIZE, SOL_ALG as SOL_ALG, SOL_CAN_BASE as SOL_CAN_BASE, SOL_CAN_RAW as SOL_CAN_RAW, @@ -366,36 +402,12 @@ if sys.platform == "linux": TIPC_WAIT_FOREVER as TIPC_WAIT_FOREVER, TIPC_WITHDRAWN as TIPC_WITHDRAWN, TIPC_ZONE_SCOPE as TIPC_ZONE_SCOPE, - ) -if sys.platform == "linux": - from _socket import ( - CAN_ISOTP as CAN_ISOTP, - IOCTL_VM_SOCKETS_GET_LOCAL_CID as IOCTL_VM_SOCKETS_GET_LOCAL_CID, - SO_VM_SOCKETS_BUFFER_MAX_SIZE as SO_VM_SOCKETS_BUFFER_MAX_SIZE, - SO_VM_SOCKETS_BUFFER_MIN_SIZE as SO_VM_SOCKETS_BUFFER_MIN_SIZE, - SO_VM_SOCKETS_BUFFER_SIZE as SO_VM_SOCKETS_BUFFER_SIZE, VM_SOCKETS_INVALID_VERSION as VM_SOCKETS_INVALID_VERSION, VMADDR_CID_ANY as VMADDR_CID_ANY, VMADDR_CID_HOST as VMADDR_CID_HOST, VMADDR_PORT_ANY as VMADDR_PORT_ANY, ) -if sys.platform != "win32": - from _socket import TCP_NOTSENT_LOWAT as TCP_NOTSENT_LOWAT -if sys.platform == "linux" and sys.version_info >= (3, 8): - from _socket import ( - CAN_BCM_CAN_FD_FRAME as CAN_BCM_CAN_FD_FRAME, - CAN_BCM_RX_ANNOUNCE_RESUME as CAN_BCM_RX_ANNOUNCE_RESUME, - CAN_BCM_RX_CHECK_DLC as CAN_BCM_RX_CHECK_DLC, - CAN_BCM_RX_FILTER_ID as CAN_BCM_RX_FILTER_ID, - CAN_BCM_RX_NO_AUTOTIMER as CAN_BCM_RX_NO_AUTOTIMER, - CAN_BCM_RX_RTR_FRAME as CAN_BCM_RX_RTR_FRAME, - CAN_BCM_SETTIMER as CAN_BCM_SETTIMER, - CAN_BCM_STARTTIMER as CAN_BCM_STARTTIMER, - CAN_BCM_TX_ANNOUNCE as CAN_BCM_TX_ANNOUNCE, - CAN_BCM_TX_COUNTEVT as CAN_BCM_TX_COUNTEVT, - CAN_BCM_TX_CP_CAN_ID as CAN_BCM_TX_CP_CAN_ID, - CAN_BCM_TX_RESET_MULTI_IDX as CAN_BCM_TX_RESET_MULTI_IDX, - ) + if sys.platform == "linux" and sys.version_info >= (3, 9): from _socket import ( CAN_J1939 as CAN_J1939, @@ -423,72 +435,106 @@ if sys.platform == "linux" and sys.version_info >= (3, 9): SO_J1939_FILTER as SO_J1939_FILTER, SO_J1939_PROMISC as SO_J1939_PROMISC, SO_J1939_SEND_PRIO as SO_J1939_SEND_PRIO, + UDPLITE_RECV_CSCOV as UDPLITE_RECV_CSCOV, + UDPLITE_SEND_CSCOV as UDPLITE_SEND_CSCOV, ) if sys.platform == "linux" and sys.version_info >= (3, 10): from _socket import IPPROTO_MPTCP as IPPROTO_MPTCP if sys.platform == "linux" and sys.version_info >= (3, 11): from _socket import SO_INCOMING_CPU as SO_INCOMING_CPU -if sys.platform == "win32": + +if sys.version_info >= (3, 12): from _socket import ( - RCVALL_MAX as RCVALL_MAX, - RCVALL_OFF as RCVALL_OFF, - RCVALL_ON as RCVALL_ON, - RCVALL_SOCKETLEVELONLY as RCVALL_SOCKETLEVELONLY, - SIO_KEEPALIVE_VALS as SIO_KEEPALIVE_VALS, - SIO_LOOPBACK_FAST_PATH as SIO_LOOPBACK_FAST_PATH, - SIO_RCVALL as SIO_RCVALL, + IP_ADD_SOURCE_MEMBERSHIP as IP_ADD_SOURCE_MEMBERSHIP, + IP_BLOCK_SOURCE as IP_BLOCK_SOURCE, + IP_DROP_SOURCE_MEMBERSHIP as IP_DROP_SOURCE_MEMBERSHIP, + IP_PKTINFO as IP_PKTINFO, + IP_UNBLOCK_SOURCE as IP_UNBLOCK_SOURCE, ) + if sys.platform == "win32": + from _socket import ( + HV_GUID_BROADCAST as HV_GUID_BROADCAST, + HV_GUID_CHILDREN as HV_GUID_CHILDREN, + HV_GUID_LOOPBACK as HV_GUID_LOOPBACK, + HV_GUID_PARENT as HV_GUID_PARENT, + HV_GUID_WILDCARD as HV_GUID_WILDCARD, + HV_GUID_ZERO as HV_GUID_ZERO, + HV_PROTOCOL_RAW as HV_PROTOCOL_RAW, + HVSOCKET_ADDRESS_FLAG_PASSTHRU as HVSOCKET_ADDRESS_FLAG_PASSTHRU, + HVSOCKET_CONNECT_TIMEOUT as HVSOCKET_CONNECT_TIMEOUT, + HVSOCKET_CONNECT_TIMEOUT_MAX as HVSOCKET_CONNECT_TIMEOUT_MAX, + HVSOCKET_CONNECTED_SUSPEND as HVSOCKET_CONNECTED_SUSPEND, + ) + else: + from _socket import ( + ETHERTYPE_ARP as ETHERTYPE_ARP, + ETHERTYPE_IP as ETHERTYPE_IP, + ETHERTYPE_IPV6 as ETHERTYPE_IPV6, + ETHERTYPE_VLAN as ETHERTYPE_VLAN, + ) + + if sys.platform == "linux": + from _socket import ETH_P_ALL as ETH_P_ALL + + if sys.platform != "linux" and sys.platform != "win32" and sys.platform != "darwin": + # FreeBSD >= 14.0 + from _socket import PF_DIVERT as PF_DIVERT + # Re-exported from errno EBADF: int EAGAIN: int EWOULDBLOCK: int class AddressFamily(IntEnum): - AF_INET: int - AF_INET6: int - AF_APPLETALK: int - AF_DECnet: int - AF_IPX: int - AF_SNA: int - AF_UNSPEC: int + AF_INET = 2 + AF_INET6 = 10 + AF_APPLETALK = 5 + AF_DECnet = ... + AF_IPX = 4 + AF_SNA = 22 + AF_UNSPEC = 0 if sys.platform != "darwin": - AF_IRDA: int + AF_IRDA = 23 if sys.platform != "win32": - AF_ROUTE: int - AF_SYSTEM: int - AF_UNIX: int - if sys.platform != "darwin" and sys.platform != "win32": - AF_AAL5: int - AF_ASH: int - AF_ATMPVC: int - AF_ATMSVC: int - AF_AX25: int - AF_BRIDGE: int - AF_ECONET: int - AF_KEY: int - AF_LLC: int - AF_NETBEUI: int - AF_NETROM: int - AF_PPPOX: int - AF_ROSE: int - AF_SECURITY: int - AF_WANPIPE: int - AF_X25: int + AF_ROUTE = 16 + AF_SYSTEM = 32 + AF_UNIX = 1 + if sys.platform != "win32" and sys.platform != "darwin": + AF_AAL5 = ... + AF_ASH = 18 + AF_ATMPVC = 8 + AF_ATMSVC = 20 + AF_AX25 = 3 + AF_BRIDGE = 7 + AF_ECONET = 19 + AF_KEY = 15 + AF_LLC = 26 + AF_NETBEUI = 13 + AF_NETROM = 6 + AF_PPPOX = 24 + AF_ROSE = 11 + AF_SECURITY = 14 + AF_WANPIPE = 25 + AF_X25 = 9 if sys.platform == "linux": - AF_CAN: int - AF_PACKET: int - AF_RDS: int - AF_TIPC: int - AF_ALG: int - AF_NETLINK: int - AF_VSOCK: int - if sys.version_info >= (3, 8): - AF_QIPCRTR: int + AF_CAN = 29 + AF_PACKET = 17 + AF_RDS = 21 + AF_TIPC = 30 + AF_ALG = 38 + AF_NETLINK = 16 + AF_VSOCK = 40 + AF_QIPCRTR = 42 if sys.platform != "win32" or sys.version_info >= (3, 9): - AF_LINK: int + AF_LINK = 33 if sys.platform != "darwin": - AF_BLUETOOTH: int + AF_BLUETOOTH = 32 + if sys.platform == "win32" and sys.version_info >= (3, 12): + AF_HYPERV = 34 + if sys.platform != "linux" and sys.platform != "win32" and sys.platform != "darwin" and sys.version_info >= (3, 12): + # FreeBSD >= 14.0 + AF_DIVERT = 44 AF_INET = AddressFamily.AF_INET AF_INET6 = AddressFamily.AF_INET6 @@ -532,23 +578,28 @@ if sys.platform == "linux": AF_ALG = AddressFamily.AF_ALG AF_NETLINK = AddressFamily.AF_NETLINK AF_VSOCK = AddressFamily.AF_VSOCK - if sys.version_info >= (3, 8): - AF_QIPCRTR = AddressFamily.AF_QIPCRTR + AF_QIPCRTR = AddressFamily.AF_QIPCRTR if sys.platform != "win32" or sys.version_info >= (3, 9): AF_LINK = AddressFamily.AF_LINK if sys.platform != "darwin": AF_BLUETOOTH = AddressFamily.AF_BLUETOOTH +if sys.platform == "win32" and sys.version_info >= (3, 12): + AF_HYPERV = AddressFamily.AF_HYPERV +if sys.platform != "linux" and sys.platform != "win32" and sys.platform != "darwin" and sys.version_info >= (3, 12): + # FreeBSD >= 14.0 + AF_DIVERT = AddressFamily.AF_DIVERT + class SocketKind(IntEnum): - SOCK_STREAM: int - SOCK_DGRAM: int - SOCK_RAW: int - SOCK_RDM: int - SOCK_SEQPACKET: int + SOCK_STREAM = 1 + SOCK_DGRAM = 2 + SOCK_RAW = 3 + SOCK_RDM = 4 + SOCK_SEQPACKET = 5 if sys.platform == "linux": - SOCK_CLOEXEC: int - SOCK_NONBLOCK: int + SOCK_CLOEXEC = 524288 + SOCK_NONBLOCK = 2048 SOCK_STREAM = SocketKind.SOCK_STREAM SOCK_DGRAM = SocketKind.SOCK_DGRAM @@ -560,32 +611,32 @@ if sys.platform == "linux": SOCK_NONBLOCK = SocketKind.SOCK_NONBLOCK class MsgFlag(IntFlag): - MSG_CTRUNC: int - MSG_DONTROUTE: int - MSG_OOB: int - MSG_PEEK: int - MSG_TRUNC: int - MSG_WAITALL: int + MSG_CTRUNC = 8 + MSG_DONTROUTE = 4 + MSG_OOB = 1 + MSG_PEEK = 2 + MSG_TRUNC = 32 + MSG_WAITALL = 256 if sys.platform != "darwin": - MSG_BCAST: int - MSG_MCAST: int - MSG_ERRQUEUE: int + MSG_BCAST = 1024 + MSG_MCAST = 2048 + MSG_ERRQUEUE = 8192 if sys.platform != "win32" and sys.platform != "darwin": - MSG_BTAG: int - MSG_CMSG_CLOEXEC: int - MSG_CONFIRM: int - MSG_ETAG: int - MSG_FASTOPEN: int - MSG_MORE: int - MSG_NOTIFICATION: int + MSG_BTAG = ... + MSG_CMSG_CLOEXEC = 1073741821 + MSG_CONFIRM = 2048 + MSG_ETAG = ... + MSG_FASTOPEN = 536870912 + MSG_MORE = 32768 + MSG_NOTIFICATION = ... if sys.platform != "win32": - MSG_DONTWAIT: int - MSG_EOF: int - MSG_EOR: int - MSG_NOSIGNAL: int # sometimes this exists on darwin, sometimes not + MSG_DONTWAIT = 64 + MSG_EOF = 256 + MSG_EOR = 128 + MSG_NOSIGNAL = 16384 # sometimes this exists on darwin, sometimes not MSG_CTRUNC = MsgFlag.MSG_CTRUNC MSG_DONTROUTE = MsgFlag.MSG_DONTROUTE @@ -615,17 +666,17 @@ if sys.platform != "win32" and sys.platform != "darwin": MSG_NOTIFICATION = MsgFlag.MSG_NOTIFICATION class AddressInfo(IntFlag): - AI_ADDRCONFIG: int - AI_ALL: int - AI_CANONNAME: int - AI_NUMERICHOST: int - AI_NUMERICSERV: int - AI_PASSIVE: int - AI_V4MAPPED: int + AI_ADDRCONFIG = 32 + AI_ALL = 16 + AI_CANONNAME = 2 + AI_NUMERICHOST = 4 + AI_NUMERICSERV = 1024 + AI_PASSIVE = 1 + AI_V4MAPPED = 8 if sys.platform != "win32": - AI_DEFAULT: int - AI_MASK: int - AI_V4MAPPED_CFG: int + AI_DEFAULT = 1536 + AI_MASK = 5127 + AI_V4MAPPED_CFG = 512 AI_ADDRCONFIG = AddressInfo.AI_ADDRCONFIG AI_ALL = AddressInfo.AI_ALL @@ -644,8 +695,8 @@ if sys.platform == "win32": errorTab: dict[int, str] # undocumented class _SendableFile(Protocol): - def read(self, __size: int) -> bytes: ... - def seek(self, __offset: int) -> object: ... + def read(self, size: int, /) -> bytes: ... + def seek(self, offset: int, /) -> object: ... # optional fields: # @@ -664,7 +715,7 @@ class socket(_socket.socket): # Note that the makefile's documented windows-specific behavior is not represented # mode strings with duplicates are intentionally excluded @overload - def makefile( # type: ignore[misc] + def makefile( self, mode: Literal["b", "rb", "br", "wb", "bw", "rwb", "rbw", "wrb", "wbr", "brw", "bwr"], buffering: Literal[0], @@ -725,13 +776,13 @@ class socket(_socket.socket): ) -> TextIOWrapper: ... def sendfile(self, file: _SendableFile, offset: int = 0, count: int | None = None) -> int: ... @property - def family(self) -> AddressFamily: ... # type: ignore[override] + def family(self) -> AddressFamily: ... @property - def type(self) -> SocketKind: ... # type: ignore[override] + def type(self) -> SocketKind: ... def get_inheritable(self) -> bool: ... def set_inheritable(self, inheritable: bool) -> None: ... -def fromfd(fd: _FD, family: AddressFamily | int, type: SocketKind | int, proto: int = 0) -> socket: ... +def fromfd(fd: SupportsIndex, family: AddressFamily | int, type: SocketKind | int, proto: int = 0) -> socket: ... if sys.platform != "win32": if sys.version_info >= (3, 9): @@ -765,7 +816,7 @@ def getfqdn(name: str = "") -> str: ... if sys.version_info >= (3, 11): def create_connection( address: tuple[str | None, int], - timeout: float | None = ..., # noqa: F811 + timeout: float | None = ..., source_address: _Address | None = None, *, all_errors: bool = False, @@ -773,19 +824,13 @@ if sys.version_info >= (3, 11): else: def create_connection( - address: tuple[str | None, int], timeout: float | None = ..., source_address: _Address | None = None # noqa: F811 + address: tuple[str | None, int], timeout: float | None = ..., source_address: _Address | None = None ) -> socket: ... -if sys.version_info >= (3, 8): - def has_dualstack_ipv6() -> bool: ... - def create_server( - address: _Address, - *, - family: int = ..., - backlog: int | None = None, - reuse_port: bool = False, - dualstack_ipv6: bool = False, - ) -> socket: ... +def has_dualstack_ipv6() -> bool: ... +def create_server( + address: _Address, *, family: int = ..., backlog: int | None = None, reuse_port: bool = False, dualstack_ipv6: bool = False +) -> socket: ... # the 5th tuple item is an address def getaddrinfo( diff --git a/mypy/typeshed/stdlib/socketserver.pyi b/mypy/typeshed/stdlib/socketserver.pyi index 3f0bb0eea0ce..5753d1d661b9 100644 --- a/mypy/typeshed/stdlib/socketserver.pyi +++ b/mypy/typeshed/stdlib/socketserver.pyi @@ -28,9 +28,11 @@ if sys.platform != "win32": "UnixDatagramServer", "UnixStreamServer", ] + if sys.version_info >= (3, 12): + __all__ += ["ForkingUnixStreamServer", "ForkingUnixDatagramServer"] _RequestType: TypeAlias = _socket | tuple[bytes, _socket] -_AfUnixAddress: TypeAlias = str | ReadableBuffer # adddress acceptable for an AF_UNIX socket +_AfUnixAddress: TypeAlias = str | ReadableBuffer # address acceptable for an AF_UNIX socket _AfInetAddress: TypeAlias = tuple[str | bytes | bytearray, int] # address acceptable for an AF_INET socket # This can possibly be generic at some point: @@ -42,14 +44,10 @@ class BaseServer: request_queue_size: int socket_type: int timeout: float | None + RequestHandlerClass: Callable[[Any, _RetAddress, Self], BaseRequestHandler] def __init__( self, server_address: _Address, RequestHandlerClass: Callable[[Any, _RetAddress, Self], BaseRequestHandler] ) -> None: ... - # It is not actually a `@property`, but we need a `Self` type: - @property - def RequestHandlerClass(self) -> Callable[[Any, _RetAddress, Self], BaseRequestHandler]: ... - @RequestHandlerClass.setter - def RequestHandlerClass(self, val: Callable[[Any, _RetAddress, Self], BaseRequestHandler]) -> None: ... def fileno(self) -> int: ... def handle_request(self) -> None: ... def serve_forever(self, poll_interval: float = 0.5) -> None: ... @@ -74,7 +72,7 @@ class BaseServer: class TCPServer(BaseServer): if sys.version_info >= (3, 11): allow_reuse_port: bool - server_address: _AfInetAddress # type: ignore[assignment] + server_address: _AfInetAddress def __init__( self, server_address: _AfInetAddress, @@ -88,7 +86,7 @@ class UDPServer(TCPServer): def get_request(self) -> tuple[tuple[bytes, _socket], _RetAddress]: ... # type: ignore[override] if sys.platform != "win32": - class UnixStreamServer(BaseServer): + class UnixStreamServer(TCPServer): server_address: _AfUnixAddress # type: ignore[assignment] def __init__( self, @@ -97,7 +95,7 @@ if sys.platform != "win32": bind_and_activate: bool = True, ) -> None: ... - class UnixDatagramServer(BaseServer): + class UnixDatagramServer(UDPServer): server_address: _AfUnixAddress # type: ignore[assignment] def __init__( self, @@ -128,6 +126,9 @@ class ThreadingMixIn: if sys.platform != "win32": class ForkingTCPServer(ForkingMixIn, TCPServer): ... class ForkingUDPServer(ForkingMixIn, UDPServer): ... + if sys.version_info >= (3, 12): + class ForkingUnixStreamServer(ForkingMixIn, UnixStreamServer): ... + class ForkingUnixDatagramServer(ForkingMixIn, UnixDatagramServer): ... class ThreadingTCPServer(ThreadingMixIn, TCPServer): ... class ThreadingUDPServer(ThreadingMixIn, UDPServer): ... diff --git a/mypy/typeshed/stdlib/spwd.pyi b/mypy/typeshed/stdlib/spwd.pyi index 27b1061e1b0e..67ad3bfc751b 100644 --- a/mypy/typeshed/stdlib/spwd.pyi +++ b/mypy/typeshed/stdlib/spwd.pyi @@ -1,7 +1,6 @@ import sys from _typeshed import structseq -from typing import Any -from typing_extensions import Final, final +from typing import Any, Final, final if sys.platform != "win32": @final @@ -18,6 +17,7 @@ if sys.platform != "win32": "sp_expire", "sp_flag", ) + @property def sp_namp(self) -> str: ... @property @@ -38,4 +38,4 @@ if sys.platform != "win32": def sp_flag(self) -> int: ... def getspall() -> list[struct_spwd]: ... - def getspnam(__arg: str) -> struct_spwd: ... + def getspnam(arg: str, /) -> struct_spwd: ... diff --git a/mypy/typeshed/stdlib/sqlite3/dbapi2.pyi b/mypy/typeshed/stdlib/sqlite3/dbapi2.pyi index 26188445547e..068ce1514c3c 100644 --- a/mypy/typeshed/stdlib/sqlite3/dbapi2.pyi +++ b/mypy/typeshed/stdlib/sqlite3/dbapi2.pyi @@ -1,13 +1,14 @@ import sqlite3 import sys -from _typeshed import Incomplete, ReadableBuffer, StrOrBytesPath, SupportsLenAndGetItem, Unused +from _typeshed import ReadableBuffer, StrOrBytesPath, SupportsLenAndGetItem, Unused from collections.abc import Callable, Generator, Iterable, Iterator, Mapping from datetime import date, datetime, time from types import TracebackType -from typing import Any, Protocol, TypeVar, overload -from typing_extensions import Literal, Self, SupportsIndex, TypeAlias, final +from typing import Any, Literal, Protocol, SupportsIndex, TypeVar, final, overload +from typing_extensions import Self, TypeAlias _T = TypeVar("_T") +_ConnectionT = TypeVar("_ConnectionT", bound=Connection) _CursorT = TypeVar("_CursorT", bound=Cursor) _SqliteData: TypeAlias = str | ReadableBuffer | int | float | None # Data that is passed through adapters can be of any type accepted by an adapter. @@ -196,48 +197,128 @@ if sys.version_info >= (3, 11): SQLITE_WARNING: int SQLITE_WARNING_AUTOINDEX: int +if sys.version_info >= (3, 12): + LEGACY_TRANSACTION_CONTROL: int + SQLITE_DBCONFIG_DEFENSIVE: int + SQLITE_DBCONFIG_DQS_DDL: int + SQLITE_DBCONFIG_DQS_DML: int + SQLITE_DBCONFIG_ENABLE_FKEY: int + SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: int + SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: int + SQLITE_DBCONFIG_ENABLE_QPSG: int + SQLITE_DBCONFIG_ENABLE_TRIGGER: int + SQLITE_DBCONFIG_ENABLE_VIEW: int + SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: int + SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: int + SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: int + SQLITE_DBCONFIG_RESET_DATABASE: int + SQLITE_DBCONFIG_TRIGGER_EQP: int + SQLITE_DBCONFIG_TRUSTED_SCHEMA: int + SQLITE_DBCONFIG_WRITABLE_SCHEMA: int + # Can take or return anything depending on what's in the registry. @overload -def adapt(__obj: Any, __proto: Any) -> Any: ... +def adapt(obj: Any, proto: Any, /) -> Any: ... @overload -def adapt(__obj: Any, __proto: Any, __alt: _T) -> Any | _T: ... +def adapt(obj: Any, proto: Any, alt: _T, /) -> Any | _T: ... def complete_statement(statement: str) -> bool: ... -def connect( - database: StrOrBytesPath, - timeout: float = ..., - detect_types: int = ..., - isolation_level: str | None = ..., - check_same_thread: bool = ..., - factory: type[Connection] | None = ..., - cached_statements: int = ..., - uri: bool = ..., -) -> Connection: ... -def enable_callback_tracebacks(__enable: bool) -> None: ... - -# takes a pos-or-keyword argument because there is a C wrapper -def enable_shared_cache(enable: int) -> None: ... -if sys.version_info >= (3, 10): - def register_adapter(__type: type[_T], __adapter: _Adapter[_T]) -> None: ... - def register_converter(__typename: str, __converter: _Converter) -> None: ... +if sys.version_info >= (3, 12): + @overload + def connect( + database: StrOrBytesPath, + timeout: float = 5.0, + detect_types: int = 0, + isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None = "DEFERRED", + check_same_thread: bool = True, + cached_statements: int = 128, + uri: bool = False, + *, + autocommit: bool = ..., + ) -> Connection: ... + @overload + def connect( + database: StrOrBytesPath, + timeout: float, + detect_types: int, + isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None, + check_same_thread: bool, + factory: type[_ConnectionT], + cached_statements: int = 128, + uri: bool = False, + *, + autocommit: bool = ..., + ) -> _ConnectionT: ... + @overload + def connect( + database: StrOrBytesPath, + timeout: float = 5.0, + detect_types: int = 0, + isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None = "DEFERRED", + check_same_thread: bool = True, + *, + factory: type[_ConnectionT], + cached_statements: int = 128, + uri: bool = False, + autocommit: bool = ..., + ) -> _ConnectionT: ... else: - def register_adapter(__type: type[_T], __caster: _Adapter[_T]) -> None: ... - def register_converter(__name: str, __converter: _Converter) -> None: ... + @overload + def connect( + database: StrOrBytesPath, + timeout: float = 5.0, + detect_types: int = 0, + isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None = "DEFERRED", + check_same_thread: bool = True, + cached_statements: int = 128, + uri: bool = False, + ) -> Connection: ... + @overload + def connect( + database: StrOrBytesPath, + timeout: float, + detect_types: int, + isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None, + check_same_thread: bool, + factory: type[_ConnectionT], + cached_statements: int = 128, + uri: bool = False, + ) -> _ConnectionT: ... + @overload + def connect( + database: StrOrBytesPath, + timeout: float = 5.0, + detect_types: int = 0, + isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None = "DEFERRED", + check_same_thread: bool = True, + *, + factory: type[_ConnectionT], + cached_statements: int = 128, + uri: bool = False, + ) -> _ConnectionT: ... + +def enable_callback_tracebacks(enable: bool, /) -> None: ... -if sys.version_info < (3, 8): - class Cache: - def __init__(self, *args: Incomplete, **kwargs: Unused) -> None: ... - def display(self, *args: Incomplete, **kwargs: Incomplete) -> None: ... - def get(self, *args: Incomplete, **kwargs: Incomplete) -> None: ... +if sys.version_info < (3, 12): + # takes a pos-or-keyword argument because there is a C wrapper + def enable_shared_cache(enable: int) -> None: ... + +if sys.version_info >= (3, 10): + def register_adapter(type: type[_T], adapter: _Adapter[_T], /) -> None: ... + def register_converter(typename: str, converter: _Converter, /) -> None: ... + +else: + def register_adapter(type: type[_T], caster: _Adapter[_T], /) -> None: ... + def register_converter(name: str, converter: _Converter, /) -> None: ... class _AggregateProtocol(Protocol): - def step(self, __value: int) -> object: ... + def step(self, value: int, /) -> object: ... def finalize(self) -> int: ... class _SingleParamWindowAggregateClass(Protocol): - def step(self, __param: Any) -> object: ... - def inverse(self, __param: Any) -> object: ... + def step(self, param: Any, /) -> object: ... + def inverse(self, param: Any, /) -> object: ... def value(self) -> _SqliteData: ... def finalize(self) -> _SqliteData: ... @@ -279,22 +360,42 @@ class Connection: isolation_level: str | None # one of '', 'DEFERRED', 'IMMEDIATE' or 'EXCLUSIVE' @property def total_changes(self) -> int: ... + if sys.version_info >= (3, 12): + @property + def autocommit(self) -> int: ... + @autocommit.setter + def autocommit(self, val: int) -> None: ... row_factory: Any text_factory: Any - def __init__( - self, - database: StrOrBytesPath, - timeout: float = ..., - detect_types: int = ..., - isolation_level: str | None = ..., - check_same_thread: bool = ..., - factory: type[Connection] | None = ..., - cached_statements: int = ..., - uri: bool = ..., - ) -> None: ... + if sys.version_info >= (3, 12): + def __init__( + self, + database: StrOrBytesPath, + timeout: float = ..., + detect_types: int = ..., + isolation_level: str | None = ..., + check_same_thread: bool = ..., + factory: type[Connection] | None = ..., + cached_statements: int = ..., + uri: bool = ..., + autocommit: bool = ..., + ) -> None: ... + else: + def __init__( + self, + database: StrOrBytesPath, + timeout: float = ..., + detect_types: int = ..., + isolation_level: str | None = ..., + check_same_thread: bool = ..., + factory: type[Connection] | None = ..., + cached_statements: int = ..., + uri: bool = ..., + ) -> None: ... + def close(self) -> None: ... if sys.version_info >= (3, 11): - def blobopen(self, __table: str, __column: str, __row: int, *, readonly: bool = False, name: str = "main") -> Blob: ... + def blobopen(self, table: str, column: str, row: int, /, *, readonly: bool = False, name: str = "main") -> Blob: ... def commit(self) -> None: ... def create_aggregate(self, name: str, n_arg: int, aggregate_class: Callable[[], _AggregateProtocol]) -> None: ... @@ -303,33 +404,29 @@ class Connection: # for the case where num_params = 1, which is expected to be the common case. @overload def create_window_function( - self, __name: str, __num_params: Literal[1], __aggregate_class: Callable[[], _SingleParamWindowAggregateClass] | None + self, name: str, num_params: Literal[1], aggregate_class: Callable[[], _SingleParamWindowAggregateClass] | None, / ) -> None: ... # And for num_params = -1, which means the aggregate must accept any number of parameters. @overload def create_window_function( - self, __name: str, __num_params: Literal[-1], __aggregate_class: Callable[[], _AnyParamWindowAggregateClass] | None + self, name: str, num_params: Literal[-1], aggregate_class: Callable[[], _AnyParamWindowAggregateClass] | None, / ) -> None: ... @overload def create_window_function( - self, __name: str, __num_params: int, __aggregate_class: Callable[[], _WindowAggregateClass] | None + self, name: str, num_params: int, aggregate_class: Callable[[], _WindowAggregateClass] | None, / ) -> None: ... - def create_collation(self, __name: str, __callback: Callable[[str, str], int | SupportsIndex] | None) -> None: ... - if sys.version_info >= (3, 8): - def create_function( - self, name: str, narg: int, func: Callable[..., _SqliteData] | None, *, deterministic: bool = False - ) -> None: ... - else: - def create_function(self, name: str, num_params: int, func: Callable[..., _SqliteData] | None) -> None: ... - + def create_collation(self, name: str, callback: Callable[[str, str], int | SupportsIndex] | None, /) -> None: ... + def create_function( + self, name: str, narg: int, func: Callable[..., _SqliteData] | None, *, deterministic: bool = False + ) -> None: ... @overload - def cursor(self, cursorClass: None = None) -> Cursor: ... + def cursor(self, factory: None = None) -> Cursor: ... @overload - def cursor(self, cursorClass: Callable[[], _CursorT]) -> _CursorT: ... - def execute(self, sql: str, parameters: _Parameters = ...) -> Cursor: ... - def executemany(self, __sql: str, __parameters: Iterable[_Parameters]) -> Cursor: ... - def executescript(self, __sql_script: str) -> Cursor: ... + def cursor(self, factory: Callable[[Connection], _CursorT]) -> _CursorT: ... + def execute(self, sql: str, parameters: _Parameters = ..., /) -> Cursor: ... + def executemany(self, sql: str, parameters: Iterable[_Parameters], /) -> Cursor: ... + def executescript(self, sql_script: str, /) -> Cursor: ... def interrupt(self) -> None: ... def iterdump(self) -> Generator[str, None, None]: ... def rollback(self) -> None: ... @@ -340,8 +437,8 @@ class Connection: def set_trace_callback(self, trace_callback: Callable[[str], object] | None) -> None: ... # enable_load_extension and load_extension is not available on python distributions compiled # without sqlite3 loadable extension support. see footnotes https://docs.python.org/3/library/sqlite3.html#f1 - def enable_load_extension(self, __enabled: bool) -> None: ... - def load_extension(self, __name: str) -> None: ... + def enable_load_extension(self, enable: bool, /) -> None: ... + def load_extension(self, name: str, /) -> None: ... def backup( self, target: Connection, @@ -352,15 +449,18 @@ class Connection: sleep: float = 0.25, ) -> None: ... if sys.version_info >= (3, 11): - def setlimit(self, __category: int, __limit: int) -> int: ... - def getlimit(self, __category: int) -> int: ... + def setlimit(self, category: int, limit: int, /) -> int: ... + def getlimit(self, category: int, /) -> int: ... def serialize(self, *, name: str = "main") -> bytes: ... - def deserialize(self, __data: ReadableBuffer, *, name: str = "main") -> None: ... + def deserialize(self, data: ReadableBuffer, /, *, name: str = "main") -> None: ... + if sys.version_info >= (3, 12): + def getconfig(self, op: int, /) -> bool: ... + def setconfig(self, op: int, enable: bool = True, /) -> bool: ... - def __call__(self, __sql: str) -> _Statement: ... + def __call__(self, sql: str, /) -> _Statement: ... def __enter__(self) -> Self: ... def __exit__( - self, __type: type[BaseException] | None, __value: BaseException | None, __traceback: TracebackType | None + self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None, / ) -> Literal[False]: ... class Cursor(Iterator[Any]): @@ -375,29 +475,28 @@ class Cursor(Iterator[Any]): row_factory: Callable[[Cursor, Row], object] | None @property def rowcount(self) -> int: ... - def __init__(self, __cursor: Connection) -> None: ... + def __init__(self, cursor: Connection, /) -> None: ... def close(self) -> None: ... - def execute(self, __sql: str, __parameters: _Parameters = ...) -> Self: ... - def executemany(self, __sql: str, __seq_of_parameters: Iterable[_Parameters]) -> Self: ... - def executescript(self, __sql_script: str) -> Cursor: ... + def execute(self, sql: str, parameters: _Parameters = (), /) -> Self: ... + def executemany(self, sql: str, seq_of_parameters: Iterable[_Parameters], /) -> Self: ... + def executescript(self, sql_script: str, /) -> Cursor: ... def fetchall(self) -> list[Any]: ... def fetchmany(self, size: int | None = 1) -> list[Any]: ... # Returns either a row (as created by the row_factory) or None, but # putting None in the return annotation causes annoying false positives. def fetchone(self) -> Any: ... - def setinputsizes(self, __sizes: Unused) -> None: ... # does nothing - def setoutputsize(self, __size: Unused, __column: Unused = None) -> None: ... # does nothing + def setinputsizes(self, sizes: Unused, /) -> None: ... # does nothing + def setoutputsize(self, size: Unused, column: Unused = None, /) -> None: ... # does nothing def __iter__(self) -> Self: ... def __next__(self) -> Any: ... -class DataError(DatabaseError): ... -class DatabaseError(Error): ... - class Error(Exception): if sys.version_info >= (3, 11): sqlite_errorcode: int sqlite_errorname: str +class DatabaseError(Error): ... +class DataError(DatabaseError): ... class IntegrityError(DatabaseError): ... class InterfaceError(Error): ... class InternalError(DatabaseError): ... @@ -414,31 +513,25 @@ class PrepareProtocol: class ProgrammingError(DatabaseError): ... class Row: - def __init__(self, __cursor: Cursor, __data: tuple[Any, ...]) -> None: ... + def __init__(self, cursor: Cursor, data: tuple[Any, ...], /) -> None: ... def keys(self) -> list[str]: ... @overload - def __getitem__(self, __index: int | str) -> Any: ... + def __getitem__(self, key: int | str, /) -> Any: ... @overload - def __getitem__(self, __index: slice) -> tuple[Any, ...]: ... + def __getitem__(self, key: slice, /) -> tuple[Any, ...]: ... + def __hash__(self) -> int: ... def __iter__(self) -> Iterator[Any]: ... def __len__(self) -> int: ... # These return NotImplemented for anything that is not a Row. - def __eq__(self, __other: object) -> bool: ... - def __ge__(self, __other: object) -> bool: ... - def __gt__(self, __other: object) -> bool: ... - def __le__(self, __other: object) -> bool: ... - def __lt__(self, __other: object) -> bool: ... - def __ne__(self, __other: object) -> bool: ... - -if sys.version_info >= (3, 8): - @final - class _Statement: ... + def __eq__(self, value: object, /) -> bool: ... + def __ge__(self, value: object, /) -> bool: ... + def __gt__(self, value: object, /) -> bool: ... + def __le__(self, value: object, /) -> bool: ... + def __lt__(self, value: object, /) -> bool: ... + def __ne__(self, value: object, /) -> bool: ... -else: - @final - class Statement: - def __init__(self, *args: Incomplete, **kwargs: Incomplete) -> None: ... - _Statement: TypeAlias = Statement +@final +class _Statement: ... class Warning(Exception): ... @@ -446,13 +539,13 @@ if sys.version_info >= (3, 11): @final class Blob: def close(self) -> None: ... - def read(self, __length: int = -1) -> bytes: ... - def write(self, __data: ReadableBuffer) -> None: ... + def read(self, length: int = -1, /) -> bytes: ... + def write(self, data: ReadableBuffer, /) -> None: ... def tell(self) -> int: ... # whence must be one of os.SEEK_SET, os.SEEK_CUR, os.SEEK_END - def seek(self, __offset: int, __origin: int = 0) -> None: ... + def seek(self, offset: int, origin: int = 0, /) -> None: ... def __len__(self) -> int: ... def __enter__(self) -> Self: ... - def __exit__(self, __typ: object, __val: object, __tb: object) -> Literal[False]: ... - def __getitem__(self, __item: SupportsIndex | slice) -> int: ... - def __setitem__(self, __item: SupportsIndex | slice, __value: int) -> None: ... + def __exit__(self, type: object, val: object, tb: object, /) -> Literal[False]: ... + def __getitem__(self, key: SupportsIndex | slice, /) -> int: ... + def __setitem__(self, key: SupportsIndex | slice, value: int, /) -> None: ... diff --git a/mypy/typeshed/stdlib/sre_parse.pyi b/mypy/typeshed/stdlib/sre_parse.pyi index 56f10bb41d57..c242bd2a065f 100644 --- a/mypy/typeshed/stdlib/sre_parse.pyi +++ b/mypy/typeshed/stdlib/sre_parse.pyi @@ -19,10 +19,20 @@ FLAGS: dict[str, int] TYPE_FLAGS: int GLOBAL_FLAGS: int +if sys.version_info >= (3, 11): + MAXWIDTH: int + if sys.version_info < (3, 11): class Verbose(Exception): ... -class _State: +_OpSubpatternType: TypeAlias = tuple[int | None, int, int, SubPattern] +_OpGroupRefExistsType: TypeAlias = tuple[int, SubPattern, SubPattern] +_OpInType: TypeAlias = list[tuple[_NIC, int]] +_OpBranchType: TypeAlias = tuple[None, list[SubPattern]] +_AvType: TypeAlias = _OpInType | _OpBranchType | Iterable[SubPattern] | _OpGroupRefExistsType | _OpSubpatternType +_CodeType: TypeAlias = tuple[_NIC, _AvType] + +class State: flags: int groupdict: dict[str, int] groupwidths: list[int | None] @@ -34,29 +44,12 @@ class _State: def checkgroup(self, gid: int) -> bool: ... def checklookbehindgroup(self, gid: int, source: Tokenizer) -> None: ... -if sys.version_info >= (3, 8): - State: TypeAlias = _State -else: - Pattern: TypeAlias = _State - -_OpSubpatternType: TypeAlias = tuple[int | None, int, int, SubPattern] -_OpGroupRefExistsType: TypeAlias = tuple[int, SubPattern, SubPattern] -_OpInType: TypeAlias = list[tuple[_NIC, int]] -_OpBranchType: TypeAlias = tuple[None, list[SubPattern]] -_AvType: TypeAlias = _OpInType | _OpBranchType | Iterable[SubPattern] | _OpGroupRefExistsType | _OpSubpatternType -_CodeType: TypeAlias = tuple[_NIC, _AvType] - class SubPattern: data: list[_CodeType] width: int | None + state: State - if sys.version_info >= (3, 8): - state: State - def __init__(self, state: State, data: list[_CodeType] | None = None) -> None: ... - else: - pattern: Pattern - def __init__(self, pattern: Pattern, data: list[_CodeType] | None = None) -> None: ... - + def __init__(self, state: State, data: list[_CodeType] | None = None) -> None: ... def dump(self, level: int = 0) -> None: ... def __len__(self) -> int: ... def __delitem__(self, index: int | slice) -> None: ... @@ -76,36 +69,36 @@ class Tokenizer: def match(self, char: str) -> bool: ... def get(self) -> str | None: ... def getwhile(self, n: int, charset: Iterable[str]) -> str: ... - if sys.version_info >= (3, 8): - def getuntil(self, terminator: str, name: str) -> str: ... - else: - def getuntil(self, terminator: str) -> str: ... - + def getuntil(self, terminator: str, name: str) -> str: ... @property def pos(self) -> int: ... def tell(self) -> int: ... def seek(self, index: int) -> None: ... def error(self, msg: str, offset: int = 0) -> _Error: ... - if sys.version_info >= (3, 11): + if sys.version_info >= (3, 12): + def checkgroupname(self, name: str, offset: int) -> None: ... + elif sys.version_info >= (3, 11): def checkgroupname(self, name: str, offset: int, nested: int) -> None: ... def fix_flags(src: str | bytes, flags: int) -> int: ... _TemplateType: TypeAlias = tuple[list[tuple[int, int]], list[str | None]] _TemplateByteType: TypeAlias = tuple[list[tuple[int, int]], list[bytes | None]] -if sys.version_info >= (3, 8): - def parse(str: str, flags: int = 0, state: State | None = None) -> SubPattern: ... + +if sys.version_info >= (3, 12): @overload - def parse_template(source: str, state: _Pattern[Any]) -> _TemplateType: ... + def parse_template(source: str, pattern: _Pattern[Any]) -> _TemplateType: ... @overload - def parse_template(source: bytes, state: _Pattern[Any]) -> _TemplateByteType: ... + def parse_template(source: bytes, pattern: _Pattern[Any]) -> _TemplateByteType: ... else: - def parse(str: str, flags: int = 0, pattern: Pattern | None = None) -> SubPattern: ... @overload - def parse_template(source: str, pattern: _Pattern[Any]) -> _TemplateType: ... + def parse_template(source: str, state: _Pattern[Any]) -> _TemplateType: ... @overload - def parse_template(source: bytes, pattern: _Pattern[Any]) -> _TemplateByteType: ... + def parse_template(source: bytes, state: _Pattern[Any]) -> _TemplateByteType: ... + +def parse(str: str, flags: int = 0, state: State | None = None) -> SubPattern: ... -def expand_template(template: _TemplateType, match: Match[Any]) -> str: ... +if sys.version_info < (3, 12): + def expand_template(template: _TemplateType, match: Match[Any]) -> str: ... diff --git a/mypy/typeshed/stdlib/ssl.pyi b/mypy/typeshed/stdlib/ssl.pyi index bbf8a4c6d65a..81c68c69ec4e 100644 --- a/mypy/typeshed/stdlib/ssl.pyi +++ b/mypy/typeshed/stdlib/ssl.pyi @@ -3,8 +3,8 @@ import socket import sys from _typeshed import ReadableBuffer, StrOrBytesPath, WriteableBuffer from collections.abc import Callable, Iterable -from typing import Any, NamedTuple, overload -from typing_extensions import Literal, Self, TypeAlias, TypedDict, final +from typing import Any, Literal, NamedTuple, TypedDict, final, overload +from typing_extensions import Never, Self, TypeAlias _PCTRTT: TypeAlias = tuple[tuple[str, str], ...] _PCTRTTT: TypeAlias = tuple[_PCTRTT, ...] @@ -15,6 +15,8 @@ _PasswordType: TypeAlias = Callable[[], str | bytes | bytearray] | str | bytes | _SrvnmeCbType: TypeAlias = Callable[[SSLSocket | SSLObject, str | None, SSLSocket], int | None] +socket_error = OSError + class _Cipher(TypedDict): aead: bool alg_bits: int @@ -44,18 +46,20 @@ class SSLCertVerificationError(SSLError, ValueError): CertificateError = SSLCertVerificationError -def wrap_socket( - sock: socket.socket, - keyfile: StrOrBytesPath | None = None, - certfile: StrOrBytesPath | None = None, - server_side: bool = False, - cert_reqs: int = ..., - ssl_version: int = ..., - ca_certs: str | None = None, - do_handshake_on_connect: bool = True, - suppress_ragged_eofs: bool = True, - ciphers: str | None = None, -) -> SSLSocket: ... +if sys.version_info < (3, 12): + def wrap_socket( + sock: socket.socket, + keyfile: StrOrBytesPath | None = None, + certfile: StrOrBytesPath | None = None, + server_side: bool = False, + cert_reqs: int = ..., + ssl_version: int = ..., + ca_certs: str | None = None, + do_handshake_on_connect: bool = True, + suppress_ragged_eofs: bool = True, + ciphers: str | None = None, + ) -> SSLSocket: ... + def create_default_context( purpose: Purpose = ..., *, @@ -94,11 +98,14 @@ else: _create_default_https_context: Callable[..., SSLContext] -def RAND_bytes(__num: int) -> bytes: ... -def RAND_pseudo_bytes(__num: int) -> tuple[bytes, bool]: ... +def RAND_bytes(n: int, /) -> bytes: ... + +if sys.version_info < (3, 12): + def RAND_pseudo_bytes(n: int, /) -> tuple[bytes, bool]: ... + def RAND_status() -> bool: ... def RAND_egd(path: str) -> None: ... -def RAND_add(__string: str | ReadableBuffer, __entropy: float) -> None: ... +def RAND_add(string: str | ReadableBuffer, entropy: float, /) -> None: ... if sys.version_info < (3, 12): def match_hostname(cert: _PeerCertRetDictType, hostname: str) -> None: ... @@ -131,23 +138,23 @@ if sys.platform == "win32": def enum_crls(store_name: str) -> _EnumRetType: ... class VerifyMode(enum.IntEnum): - CERT_NONE: int - CERT_OPTIONAL: int - CERT_REQUIRED: int + CERT_NONE = 0 + CERT_OPTIONAL = 1 + CERT_REQUIRED = 2 CERT_NONE: VerifyMode CERT_OPTIONAL: VerifyMode CERT_REQUIRED: VerifyMode class VerifyFlags(enum.IntFlag): - VERIFY_DEFAULT: int - VERIFY_CRL_CHECK_LEAF: int - VERIFY_CRL_CHECK_CHAIN: int - VERIFY_X509_STRICT: int - VERIFY_X509_TRUSTED_FIRST: int + VERIFY_DEFAULT = 0 + VERIFY_CRL_CHECK_LEAF = 4 + VERIFY_CRL_CHECK_CHAIN = 12 + VERIFY_X509_STRICT = 32 + VERIFY_X509_TRUSTED_FIRST = 32768 if sys.version_info >= (3, 10): - VERIFY_ALLOW_PROXY_CERTS: int - VERIFY_X509_PARTIAL_CHAIN: int + VERIFY_ALLOW_PROXY_CERTS = 64 + VERIFY_X509_PARTIAL_CHAIN = 524288 VERIFY_DEFAULT: VerifyFlags VERIFY_CRL_CHECK_LEAF: VerifyFlags @@ -160,15 +167,15 @@ if sys.version_info >= (3, 10): VERIFY_X509_PARTIAL_CHAIN: VerifyFlags class _SSLMethod(enum.IntEnum): - PROTOCOL_SSLv23: int - PROTOCOL_SSLv2: int - PROTOCOL_SSLv3: int - PROTOCOL_TLSv1: int - PROTOCOL_TLSv1_1: int - PROTOCOL_TLSv1_2: int - PROTOCOL_TLS: int - PROTOCOL_TLS_CLIENT: int - PROTOCOL_TLS_SERVER: int + PROTOCOL_SSLv23 = 2 + PROTOCOL_SSLv2 = ... + PROTOCOL_SSLv3 = ... + PROTOCOL_TLSv1 = 3 + PROTOCOL_TLSv1_1 = 4 + PROTOCOL_TLSv1_2 = 5 + PROTOCOL_TLS = 2 + PROTOCOL_TLS_CLIENT = 16 + PROTOCOL_TLS_SERVER = 17 PROTOCOL_SSLv23: _SSLMethod PROTOCOL_SSLv2: _SSLMethod @@ -181,23 +188,25 @@ PROTOCOL_TLS_CLIENT: _SSLMethod PROTOCOL_TLS_SERVER: _SSLMethod class Options(enum.IntFlag): - OP_ALL: int - OP_NO_SSLv2: int - OP_NO_SSLv3: int - OP_NO_TLSv1: int - OP_NO_TLSv1_1: int - OP_NO_TLSv1_2: int - OP_NO_TLSv1_3: int - OP_CIPHER_SERVER_PREFERENCE: int - OP_SINGLE_DH_USE: int - OP_SINGLE_ECDH_USE: int - OP_NO_COMPRESSION: int - OP_NO_TICKET: int - OP_NO_RENEGOTIATION: int - if sys.version_info >= (3, 8): - OP_ENABLE_MIDDLEBOX_COMPAT: int - if sys.platform == "linux": - OP_IGNORE_UNEXPECTED_EOF: int + OP_ALL = 2147483728 + OP_NO_SSLv2 = 0 + OP_NO_SSLv3 = 33554432 + OP_NO_TLSv1 = 67108864 + OP_NO_TLSv1_1 = 268435456 + OP_NO_TLSv1_2 = 134217728 + OP_NO_TLSv1_3 = 536870912 + OP_CIPHER_SERVER_PREFERENCE = 4194304 + OP_SINGLE_DH_USE = 0 + OP_SINGLE_ECDH_USE = 0 + OP_NO_COMPRESSION = 131072 + OP_NO_TICKET = 16384 + OP_NO_RENEGOTIATION = 1073741824 + OP_ENABLE_MIDDLEBOX_COMPAT = 1048576 + if sys.version_info >= (3, 12): + OP_LEGACY_SERVER_CONNECT = 4 + OP_ENABLE_KTLS = 8 + if sys.version_info >= (3, 11) or sys.platform == "linux": + OP_IGNORE_UNEXPECTED_EOF = 128 OP_ALL: Options OP_NO_SSLv2: Options @@ -212,10 +221,12 @@ OP_SINGLE_ECDH_USE: Options OP_NO_COMPRESSION: Options OP_NO_TICKET: Options OP_NO_RENEGOTIATION: Options -if sys.version_info >= (3, 8): - OP_ENABLE_MIDDLEBOX_COMPAT: Options - if sys.platform == "linux": - OP_IGNORE_UNEXPECTED_EOF: Options +OP_ENABLE_MIDDLEBOX_COMPAT: Options +if sys.version_info >= (3, 12): + OP_LEGACY_SERVER_CONNECT: Options + OP_ENABLE_KTLS: Options +if sys.version_info >= (3, 11) or sys.platform == "linux": + OP_IGNORE_UNEXPECTED_EOF: Options HAS_NEVER_CHECK_COMMON_NAME: bool HAS_SSLv2: bool @@ -235,33 +246,33 @@ OPENSSL_VERSION_INFO: tuple[int, int, int, int, int] OPENSSL_VERSION_NUMBER: int class AlertDescription(enum.IntEnum): - ALERT_DESCRIPTION_ACCESS_DENIED: int - ALERT_DESCRIPTION_BAD_CERTIFICATE: int - ALERT_DESCRIPTION_BAD_CERTIFICATE_HASH_VALUE: int - ALERT_DESCRIPTION_BAD_CERTIFICATE_STATUS_RESPONSE: int - ALERT_DESCRIPTION_BAD_RECORD_MAC: int - ALERT_DESCRIPTION_CERTIFICATE_EXPIRED: int - ALERT_DESCRIPTION_CERTIFICATE_REVOKED: int - ALERT_DESCRIPTION_CERTIFICATE_UNKNOWN: int - ALERT_DESCRIPTION_CERTIFICATE_UNOBTAINABLE: int - ALERT_DESCRIPTION_CLOSE_NOTIFY: int - ALERT_DESCRIPTION_DECODE_ERROR: int - ALERT_DESCRIPTION_DECOMPRESSION_FAILURE: int - ALERT_DESCRIPTION_DECRYPT_ERROR: int - ALERT_DESCRIPTION_HANDSHAKE_FAILURE: int - ALERT_DESCRIPTION_ILLEGAL_PARAMETER: int - ALERT_DESCRIPTION_INSUFFICIENT_SECURITY: int - ALERT_DESCRIPTION_INTERNAL_ERROR: int - ALERT_DESCRIPTION_NO_RENEGOTIATION: int - ALERT_DESCRIPTION_PROTOCOL_VERSION: int - ALERT_DESCRIPTION_RECORD_OVERFLOW: int - ALERT_DESCRIPTION_UNEXPECTED_MESSAGE: int - ALERT_DESCRIPTION_UNKNOWN_CA: int - ALERT_DESCRIPTION_UNKNOWN_PSK_IDENTITY: int - ALERT_DESCRIPTION_UNRECOGNIZED_NAME: int - ALERT_DESCRIPTION_UNSUPPORTED_CERTIFICATE: int - ALERT_DESCRIPTION_UNSUPPORTED_EXTENSION: int - ALERT_DESCRIPTION_USER_CANCELLED: int + ALERT_DESCRIPTION_ACCESS_DENIED = 49 + ALERT_DESCRIPTION_BAD_CERTIFICATE = 42 + ALERT_DESCRIPTION_BAD_CERTIFICATE_HASH_VALUE = 114 + ALERT_DESCRIPTION_BAD_CERTIFICATE_STATUS_RESPONSE = 113 + ALERT_DESCRIPTION_BAD_RECORD_MAC = 20 + ALERT_DESCRIPTION_CERTIFICATE_EXPIRED = 45 + ALERT_DESCRIPTION_CERTIFICATE_REVOKED = 44 + ALERT_DESCRIPTION_CERTIFICATE_UNKNOWN = 46 + ALERT_DESCRIPTION_CERTIFICATE_UNOBTAINABLE = 111 + ALERT_DESCRIPTION_CLOSE_NOTIFY = 0 + ALERT_DESCRIPTION_DECODE_ERROR = 50 + ALERT_DESCRIPTION_DECOMPRESSION_FAILURE = 30 + ALERT_DESCRIPTION_DECRYPT_ERROR = 51 + ALERT_DESCRIPTION_HANDSHAKE_FAILURE = 40 + ALERT_DESCRIPTION_ILLEGAL_PARAMETER = 47 + ALERT_DESCRIPTION_INSUFFICIENT_SECURITY = 71 + ALERT_DESCRIPTION_INTERNAL_ERROR = 80 + ALERT_DESCRIPTION_NO_RENEGOTIATION = 100 + ALERT_DESCRIPTION_PROTOCOL_VERSION = 70 + ALERT_DESCRIPTION_RECORD_OVERFLOW = 22 + ALERT_DESCRIPTION_UNEXPECTED_MESSAGE = 10 + ALERT_DESCRIPTION_UNKNOWN_CA = 48 + ALERT_DESCRIPTION_UNKNOWN_PSK_IDENTITY = 115 + ALERT_DESCRIPTION_UNRECOGNIZED_NAME = 112 + ALERT_DESCRIPTION_UNSUPPORTED_CERTIFICATE = 43 + ALERT_DESCRIPTION_UNSUPPORTED_EXTENSION = 110 + ALERT_DESCRIPTION_USER_CANCELLED = 90 ALERT_DESCRIPTION_HANDSHAKE_FAILURE: AlertDescription ALERT_DESCRIPTION_INTERNAL_ERROR: AlertDescription @@ -291,19 +302,22 @@ ALERT_DESCRIPTION_UNSUPPORTED_CERTIFICATE: AlertDescription ALERT_DESCRIPTION_UNSUPPORTED_EXTENSION: AlertDescription ALERT_DESCRIPTION_USER_CANCELLED: AlertDescription -class _ASN1Object(NamedTuple): +class _ASN1ObjectBase(NamedTuple): nid: int shortname: str longname: str oid: str + +class _ASN1Object(_ASN1ObjectBase): + def __new__(cls, oid: str) -> Self: ... @classmethod def fromnid(cls, nid: int) -> Self: ... @classmethod def fromname(cls, name: str) -> Self: ... class Purpose(_ASN1Object, enum.Enum): - SERVER_AUTH: _ASN1Object - CLIENT_AUTH: _ASN1Object + SERVER_AUTH = (129, "serverAuth", "TLS Web Server Authentication", "1.3.6.1.5.5.7.3.2") # pyright: ignore[reportCallIssue] + CLIENT_AUTH = (130, "clientAuth", "TLS Web Client Authentication", "1.3.6.1.5.5.7.3.1") # pyright: ignore[reportCallIssue] class SSLSocket(socket.socket): context: SSLContext @@ -347,17 +361,23 @@ class SSLSocket(socket.socket): def unwrap(self) -> socket.socket: ... def version(self) -> str | None: ... def pending(self) -> int: ... - if sys.version_info >= (3, 8): - def verify_client_post_handshake(self) -> None: ... + def verify_client_post_handshake(self) -> None: ... + # These methods always raise `NotImplementedError`: + def recvmsg(self, *args: Never, **kwargs: Never) -> Never: ... # type: ignore[override] + def recvmsg_into(self, *args: Never, **kwargs: Never) -> Never: ... # type: ignore[override] + def sendmsg(self, *args: Never, **kwargs: Never) -> Never: ... # type: ignore[override] + if sys.version_info >= (3, 13): + def get_verified_chain(self) -> list[bytes]: ... + def get_unverified_chain(self) -> list[bytes]: ... class TLSVersion(enum.IntEnum): - MINIMUM_SUPPORTED: int - MAXIMUM_SUPPORTED: int - SSLv3: int - TLSv1: int - TLSv1_1: int - TLSv1_2: int - TLSv1_3: int + MINIMUM_SUPPORTED = -2 + MAXIMUM_SUPPORTED = -1 + SSLv3 = 768 + TLSv1 = 769 + TLSv1_1 = 770 + TLSv1_2 = 771 + TLSv1_3 = 772 class SSLContext: check_hostname: bool @@ -375,9 +395,8 @@ class SSLContext: # so making these ClassVars wouldn't be appropriate sslobject_class: type[SSLObject] sslsocket_class: type[SSLSocket] - if sys.version_info >= (3, 8): - keylog_filename: str - post_handshake_auth: bool + keylog_filename: str + post_handshake_auth: bool if sys.version_info >= (3, 10): security_level: int if sys.version_info >= (3, 10): @@ -406,19 +425,19 @@ class SSLContext: def get_ca_certs(self, binary_form: bool = False) -> Any: ... def get_ciphers(self) -> list[_Cipher]: ... def set_default_verify_paths(self) -> None: ... - def set_ciphers(self, __cipherlist: str) -> None: ... + def set_ciphers(self, cipherlist: str, /) -> None: ... def set_alpn_protocols(self, alpn_protocols: Iterable[str]) -> None: ... def set_npn_protocols(self, npn_protocols: Iterable[str]) -> None: ... def set_servername_callback(self, server_name_callback: _SrvnmeCbType | None) -> None: ... - def load_dh_params(self, __path: str) -> None: ... - def set_ecdh_curve(self, __name: str) -> None: ... + def load_dh_params(self, path: str, /) -> None: ... + def set_ecdh_curve(self, name: str, /) -> None: ... def wrap_socket( self, sock: socket.socket, server_side: bool = False, do_handshake_on_connect: bool = True, suppress_ragged_eofs: bool = True, - server_hostname: str | None = None, + server_hostname: str | bytes | None = None, session: SSLSession | None = None, ) -> SSLSocket: ... def wrap_bio( @@ -426,7 +445,7 @@ class SSLContext: incoming: MemoryBIO, outgoing: MemoryBIO, server_side: bool = False, - server_hostname: str | None = None, + server_hostname: str | bytes | None = None, session: SSLSession | None = None, ) -> SSLObject: ... def session_stats(self) -> dict[str, int]: ... @@ -459,15 +478,17 @@ class SSLObject: def unwrap(self) -> None: ... def version(self) -> str | None: ... def get_channel_binding(self, cb_type: str = "tls-unique") -> bytes | None: ... - if sys.version_info >= (3, 8): - def verify_client_post_handshake(self) -> None: ... + def verify_client_post_handshake(self) -> None: ... + if sys.version_info >= (3, 13): + def get_verified_chain(self) -> list[bytes]: ... + def get_unverified_chain(self) -> list[bytes]: ... @final class MemoryBIO: pending: int eof: bool - def read(self, __size: int = -1) -> bytes: ... - def write(self, __buf: ReadableBuffer) -> int: ... + def read(self, size: int = -1, /) -> bytes: ... + def write(self, b: ReadableBuffer, /) -> int: ... def write_eof(self) -> None: ... @final @@ -482,17 +503,18 @@ class SSLSession: def time(self) -> int: ... @property def timeout(self) -> int: ... + def __eq__(self, value: object, /) -> bool: ... class SSLErrorNumber(enum.IntEnum): - SSL_ERROR_EOF: int - SSL_ERROR_INVALID_ERROR_CODE: int - SSL_ERROR_SSL: int - SSL_ERROR_SYSCALL: int - SSL_ERROR_WANT_CONNECT: int - SSL_ERROR_WANT_READ: int - SSL_ERROR_WANT_WRITE: int - SSL_ERROR_WANT_X509_LOOKUP: int - SSL_ERROR_ZERO_RETURN: int + SSL_ERROR_EOF = 8 + SSL_ERROR_INVALID_ERROR_CODE = 10 + SSL_ERROR_SSL = 1 + SSL_ERROR_SYSCALL = 5 + SSL_ERROR_WANT_CONNECT = 7 + SSL_ERROR_WANT_READ = 2 + SSL_ERROR_WANT_WRITE = 3 + SSL_ERROR_WANT_X509_LOOKUP = 4 + SSL_ERROR_ZERO_RETURN = 6 SSL_ERROR_EOF: SSLErrorNumber # undocumented SSL_ERROR_INVALID_ERROR_CODE: SSLErrorNumber # undocumented diff --git a/mypy/typeshed/stdlib/statistics.pyi b/mypy/typeshed/stdlib/statistics.pyi index 1358b1f90d7d..c5f5ed64b328 100644 --- a/mypy/typeshed/stdlib/statistics.pyi +++ b/mypy/typeshed/stdlib/statistics.pyi @@ -3,11 +3,15 @@ from _typeshed import SupportsRichComparisonT from collections.abc import Hashable, Iterable, Sequence from decimal import Decimal from fractions import Fraction -from typing import Any, NamedTuple, SupportsFloat, TypeVar -from typing_extensions import Literal, Self, TypeAlias +from typing import Any, Literal, NamedTuple, SupportsFloat, TypeVar +from typing_extensions import Self, TypeAlias __all__ = [ "StatisticsError", + "fmean", + "geometric_mean", + "mean", + "harmonic_mean", "pstdev", "pvariance", "stdev", @@ -16,14 +20,12 @@ __all__ = [ "median_low", "median_high", "median_grouped", - "mean", "mode", - "harmonic_mean", + "multimode", + "NormalDist", + "quantiles", ] -if sys.version_info >= (3, 8): - __all__ += ["geometric_mean", "multimode", "NormalDist", "fmean", "quantiles"] - if sys.version_info >= (3, 10): __all__ += ["covariance", "correlation", "linear_regression"] @@ -39,12 +41,10 @@ class StatisticsError(ValueError): ... if sys.version_info >= (3, 11): def fmean(data: Iterable[SupportsFloat], weights: Iterable[SupportsFloat] | None = None) -> float: ... -elif sys.version_info >= (3, 8): +else: def fmean(data: Iterable[SupportsFloat]) -> float: ... -if sys.version_info >= (3, 8): - def geometric_mean(data: Iterable[SupportsFloat]) -> float: ... - +def geometric_mean(data: Iterable[SupportsFloat]) -> float: ... def mean(data: Iterable[_NumberT]) -> _NumberT: ... if sys.version_info >= (3, 10): @@ -64,59 +64,60 @@ else: def median_grouped(data: Iterable[_NumberT], interval: _NumberT | float = 1) -> _NumberT | float: ... def mode(data: Iterable[_HashableT]) -> _HashableT: ... - -if sys.version_info >= (3, 8): - def multimode(data: Iterable[_HashableT]) -> list[_HashableT]: ... - +def multimode(data: Iterable[_HashableT]) -> list[_HashableT]: ... def pstdev(data: Iterable[_NumberT], mu: _NumberT | None = None) -> _NumberT: ... def pvariance(data: Iterable[_NumberT], mu: _NumberT | None = None) -> _NumberT: ... - -if sys.version_info >= (3, 8): - def quantiles( - data: Iterable[_NumberT], *, n: int = 4, method: Literal["inclusive", "exclusive"] = "exclusive" - ) -> list[_NumberT]: ... - +def quantiles( + data: Iterable[_NumberT], *, n: int = 4, method: Literal["inclusive", "exclusive"] = "exclusive" +) -> list[_NumberT]: ... def stdev(data: Iterable[_NumberT], xbar: _NumberT | None = None) -> _NumberT: ... def variance(data: Iterable[_NumberT], xbar: _NumberT | None = None) -> _NumberT: ... -if sys.version_info >= (3, 8): - class NormalDist: - def __init__(self, mu: float = 0.0, sigma: float = 1.0) -> None: ... - @property - def mean(self) -> float: ... - @property - def median(self) -> float: ... - @property - def mode(self) -> float: ... - @property - def stdev(self) -> float: ... - @property - def variance(self) -> float: ... - @classmethod - def from_samples(cls, data: Iterable[SupportsFloat]) -> Self: ... - def samples(self, n: int, *, seed: Any | None = None) -> list[float]: ... - def pdf(self, x: float) -> float: ... - def cdf(self, x: float) -> float: ... - def inv_cdf(self, p: float) -> float: ... - def overlap(self, other: NormalDist) -> float: ... - def quantiles(self, n: int = 4) -> list[float]: ... - if sys.version_info >= (3, 9): - def zscore(self, x: float) -> float: ... - - def __eq__(self, x2: object) -> bool: ... - def __add__(self, x2: float | NormalDist) -> NormalDist: ... - def __sub__(self, x2: float | NormalDist) -> NormalDist: ... - def __mul__(self, x2: float) -> NormalDist: ... - def __truediv__(self, x2: float) -> NormalDist: ... - def __pos__(self) -> NormalDist: ... - def __neg__(self) -> NormalDist: ... - __radd__ = __add__ - def __rsub__(self, x2: float | NormalDist) -> NormalDist: ... - __rmul__ = __mul__ +class NormalDist: + def __init__(self, mu: float = 0.0, sigma: float = 1.0) -> None: ... + @property + def mean(self) -> float: ... + @property + def median(self) -> float: ... + @property + def mode(self) -> float: ... + @property + def stdev(self) -> float: ... + @property + def variance(self) -> float: ... + @classmethod + def from_samples(cls, data: Iterable[SupportsFloat]) -> Self: ... + def samples(self, n: int, *, seed: Any | None = None) -> list[float]: ... + def pdf(self, x: float) -> float: ... + def cdf(self, x: float) -> float: ... + def inv_cdf(self, p: float) -> float: ... + def overlap(self, other: NormalDist) -> float: ... + def quantiles(self, n: int = 4) -> list[float]: ... + if sys.version_info >= (3, 9): + def zscore(self, x: float) -> float: ... + + def __eq__(self, x2: object) -> bool: ... + def __add__(self, x2: float | NormalDist) -> NormalDist: ... + def __sub__(self, x2: float | NormalDist) -> NormalDist: ... + def __mul__(self, x2: float) -> NormalDist: ... + def __truediv__(self, x2: float) -> NormalDist: ... + def __pos__(self) -> NormalDist: ... + def __neg__(self) -> NormalDist: ... + __radd__ = __add__ + def __rsub__(self, x2: float | NormalDist) -> NormalDist: ... + __rmul__ = __mul__ + def __hash__(self) -> int: ... + +if sys.version_info >= (3, 12): + def correlation( + x: Sequence[_Number], y: Sequence[_Number], /, *, method: Literal["linear", "ranked"] = "linear" + ) -> float: ... + +elif sys.version_info >= (3, 10): + def correlation(x: Sequence[_Number], y: Sequence[_Number], /) -> float: ... if sys.version_info >= (3, 10): - def correlation(__x: Sequence[_Number], __y: Sequence[_Number]) -> float: ... - def covariance(__x: Sequence[_Number], __y: Sequence[_Number]) -> float: ... + def covariance(x: Sequence[_Number], y: Sequence[_Number], /) -> float: ... class LinearRegression(NamedTuple): slope: float @@ -124,8 +125,8 @@ if sys.version_info >= (3, 10): if sys.version_info >= (3, 11): def linear_regression( - __regressor: Sequence[_Number], __dependent_variable: Sequence[_Number], *, proportional: bool = False + regressor: Sequence[_Number], dependent_variable: Sequence[_Number], /, *, proportional: bool = False ) -> LinearRegression: ... elif sys.version_info >= (3, 10): - def linear_regression(__regressor: Sequence[_Number], __dependent_variable: Sequence[_Number]) -> LinearRegression: ... + def linear_regression(regressor: Sequence[_Number], dependent_variable: Sequence[_Number], /) -> LinearRegression: ... diff --git a/mypy/typeshed/stdlib/string.pyi b/mypy/typeshed/stdlib/string.pyi index dc9a449e0e39..35a76e9c8628 100644 --- a/mypy/typeshed/stdlib/string.pyi +++ b/mypy/typeshed/stdlib/string.pyi @@ -47,17 +47,17 @@ class Template(metaclass=_TemplateMetaclass): flags: ClassVar[RegexFlag] pattern: ClassVar[Pattern[str]] def __init__(self, template: str) -> None: ... - def substitute(self, __mapping: Mapping[str, object] = ..., **kwds: object) -> str: ... - def safe_substitute(self, __mapping: Mapping[str, object] = ..., **kwds: object) -> str: ... + def substitute(self, mapping: Mapping[str, object] = {}, /, **kwds: object) -> str: ... + def safe_substitute(self, mapping: Mapping[str, object] = {}, /, **kwds: object) -> str: ... if sys.version_info >= (3, 11): def get_identifiers(self) -> list[str]: ... def is_valid(self) -> bool: ... class Formatter: @overload - def format(self, __format_string: LiteralString, *args: LiteralString, **kwargs: LiteralString) -> LiteralString: ... + def format(self, format_string: LiteralString, /, *args: LiteralString, **kwargs: LiteralString) -> LiteralString: ... @overload - def format(self, __format_string: str, *args: Any, **kwargs: Any) -> str: ... + def format(self, format_string: str, /, *args: Any, **kwargs: Any) -> str: ... @overload def vformat( self, format_string: LiteralString, args: Sequence[LiteralString], kwargs: Mapping[LiteralString, LiteralString] @@ -80,4 +80,4 @@ class Formatter: def get_value(self, key: int | str, args: Sequence[Any], kwargs: Mapping[str, Any]) -> Any: ... def check_unused_args(self, used_args: set[int | str], args: Sequence[Any], kwargs: Mapping[str, Any]) -> None: ... def format_field(self, value: Any, format_spec: str) -> Any: ... - def convert_field(self, value: Any, conversion: str) -> Any: ... + def convert_field(self, value: Any, conversion: str | None) -> Any: ... diff --git a/mypy/typeshed/stdlib/struct.pyi b/mypy/typeshed/stdlib/struct.pyi index 4220cd825b76..e684632489ea 100644 --- a/mypy/typeshed/stdlib/struct.pyi +++ b/mypy/typeshed/stdlib/struct.pyi @@ -6,12 +6,12 @@ __all__ = ["calcsize", "pack", "pack_into", "unpack", "unpack_from", "iter_unpac class error(Exception): ... -def pack(__fmt: str | bytes, *v: Any) -> bytes: ... -def pack_into(__fmt: str | bytes, __buffer: WriteableBuffer, __offset: int, *v: Any) -> None: ... -def unpack(__format: str | bytes, __buffer: ReadableBuffer) -> tuple[Any, ...]: ... -def unpack_from(__format: str | bytes, buffer: ReadableBuffer, offset: int = 0) -> tuple[Any, ...]: ... -def iter_unpack(__format: str | bytes, __buffer: ReadableBuffer) -> Iterator[tuple[Any, ...]]: ... -def calcsize(__format: str | bytes) -> int: ... +def pack(fmt: str | bytes, /, *v: Any) -> bytes: ... +def pack_into(fmt: str | bytes, buffer: WriteableBuffer, offset: int, /, *v: Any) -> None: ... +def unpack(format: str | bytes, buffer: ReadableBuffer, /) -> tuple[Any, ...]: ... +def unpack_from(format: str | bytes, /, buffer: ReadableBuffer, offset: int = 0) -> tuple[Any, ...]: ... +def iter_unpack(format: str | bytes, buffer: ReadableBuffer, /) -> Iterator[tuple[Any, ...]]: ... +def calcsize(format: str | bytes, /) -> int: ... class Struct: @property @@ -21,6 +21,6 @@ class Struct: def __init__(self, format: str | bytes) -> None: ... def pack(self, *v: Any) -> bytes: ... def pack_into(self, buffer: WriteableBuffer, offset: int, *v: Any) -> None: ... - def unpack(self, __buffer: ReadableBuffer) -> tuple[Any, ...]: ... + def unpack(self, buffer: ReadableBuffer, /) -> tuple[Any, ...]: ... def unpack_from(self, buffer: ReadableBuffer, offset: int = 0) -> tuple[Any, ...]: ... - def iter_unpack(self, __buffer: ReadableBuffer) -> Iterator[tuple[Any, ...]]: ... + def iter_unpack(self, buffer: ReadableBuffer, /) -> Iterator[tuple[Any, ...]]: ... diff --git a/mypy/typeshed/stdlib/subprocess.pyi b/mypy/typeshed/stdlib/subprocess.pyi index 3940fad7b915..d3302aba5e10 100644 --- a/mypy/typeshed/stdlib/subprocess.pyi +++ b/mypy/typeshed/stdlib/subprocess.pyi @@ -2,8 +2,8 @@ import sys from _typeshed import ReadableBuffer, StrOrBytesPath from collections.abc import Callable, Collection, Iterable, Mapping, Sequence from types import TracebackType -from typing import IO, Any, AnyStr, Generic, TypeVar, overload -from typing_extensions import Literal, Self, TypeAlias +from typing import IO, Any, AnyStr, Generic, Literal, TypeVar, overload +from typing_extensions import Self, TypeAlias if sys.version_info >= (3, 9): from types import GenericAlias @@ -64,12 +64,7 @@ if sys.platform == "win32": # reveal_type(e.cmd) # Any, but morally is _CMD _FILE: TypeAlias = None | int | IO[Any] _InputString: TypeAlias = ReadableBuffer | str -if sys.version_info >= (3, 8): - _CMD: TypeAlias = StrOrBytesPath | Sequence[StrOrBytesPath] -else: - # Python 3.7 doesn't support _CMD being a single PathLike. - # See: https://bugs.python.org/issue31961 - _CMD: TypeAlias = str | bytes | Sequence[StrOrBytesPath] +_CMD: TypeAlias = StrOrBytesPath | Sequence[StrOrBytesPath] if sys.platform == "win32": _ENV: TypeAlias = Mapping[str, str] else: @@ -80,8 +75,7 @@ _T = TypeVar("_T") # These two are private but documented if sys.version_info >= (3, 11): _USE_VFORK: bool -if sys.version_info >= (3, 8): - _USE_POSIX_SPAWN: bool +_USE_POSIX_SPAWN: bool class CompletedProcess(Generic[_T]): # morally: _CMD @@ -101,158 +95,158 @@ if sys.version_info >= (3, 11): @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, check: bool = False, - encoding: str | None = ..., - errors: str | None = ..., + encoding: str | None = None, + errors: str | None = None, input: str | None = None, text: Literal[True], timeout: float | None = None, - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., - process_group: int | None = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, + process_group: int | None = None, ) -> CompletedProcess[str]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, check: bool = False, encoding: str, - errors: str | None = ..., + errors: str | None = None, input: str | None = None, - text: bool | None = ..., + text: bool | None = None, timeout: float | None = None, - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., - process_group: int | None = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, + process_group: int | None = None, ) -> CompletedProcess[str]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, check: bool = False, - encoding: str | None = ..., + encoding: str | None = None, errors: str, input: str | None = None, - text: bool | None = ..., + text: bool | None = None, timeout: float | None = None, - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., - process_group: int | None = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, + process_group: int | None = None, ) -> CompletedProcess[str]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, *, universal_newlines: Literal[True], - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., # where the *real* keyword only args start capture_output: bool = False, check: bool = False, - encoding: str | None = ..., - errors: str | None = ..., + encoding: str | None = None, + errors: str | None = None, input: str | None = None, - text: bool | None = ..., + text: bool | None = None, timeout: float | None = None, - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., - process_group: int | None = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, + process_group: int | None = None, ) -> CompletedProcess[str]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: Literal[False, None] = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: Literal[False] | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, @@ -260,48 +254,48 @@ if sys.version_info >= (3, 11): encoding: None = None, errors: None = None, input: ReadableBuffer | None = None, - text: Literal[None, False] = ..., + text: Literal[False] | None = None, timeout: float | None = None, - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., - process_group: int | None = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, + process_group: int | None = None, ) -> CompletedProcess[bytes]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, check: bool = False, - encoding: str | None = ..., - errors: str | None = ..., + encoding: str | None = None, + errors: str | None = None, input: _InputString | None = None, - text: bool | None = ..., + text: bool | None = None, timeout: float | None = None, - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., - process_group: int | None = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, + process_group: int | None = None, ) -> CompletedProcess[Any]: ... elif sys.version_info >= (3, 10): @@ -309,154 +303,154 @@ elif sys.version_info >= (3, 10): @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, check: bool = False, - encoding: str | None = ..., - errors: str | None = ..., + encoding: str | None = None, + errors: str | None = None, input: str | None = None, text: Literal[True], timeout: float | None = None, - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, ) -> CompletedProcess[str]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, check: bool = False, encoding: str, - errors: str | None = ..., + errors: str | None = None, input: str | None = None, - text: bool | None = ..., + text: bool | None = None, timeout: float | None = None, - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, ) -> CompletedProcess[str]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, check: bool = False, - encoding: str | None = ..., + encoding: str | None = None, errors: str, input: str | None = None, - text: bool | None = ..., + text: bool | None = None, timeout: float | None = None, - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, ) -> CompletedProcess[str]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, *, universal_newlines: Literal[True], - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., # where the *real* keyword only args start capture_output: bool = False, check: bool = False, - encoding: str | None = ..., - errors: str | None = ..., + encoding: str | None = None, + errors: str | None = None, input: str | None = None, - text: bool | None = ..., + text: bool | None = None, timeout: float | None = None, - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, ) -> CompletedProcess[str]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: Literal[False, None] = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: Literal[False] | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, @@ -464,46 +458,46 @@ elif sys.version_info >= (3, 10): encoding: None = None, errors: None = None, input: ReadableBuffer | None = None, - text: Literal[None, False] = ..., + text: Literal[False] | None = None, timeout: float | None = None, - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, ) -> CompletedProcess[bytes]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, check: bool = False, - encoding: str | None = ..., - errors: str | None = ..., + encoding: str | None = None, + errors: str | None = None, input: _InputString | None = None, - text: bool | None = ..., + text: bool | None = None, timeout: float | None = None, - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, ) -> CompletedProcess[Any]: ... elif sys.version_info >= (3, 9): @@ -511,150 +505,150 @@ elif sys.version_info >= (3, 9): @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, check: bool = False, - encoding: str | None = ..., - errors: str | None = ..., + encoding: str | None = None, + errors: str | None = None, input: str | None = None, text: Literal[True], timeout: float | None = None, - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, ) -> CompletedProcess[str]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, check: bool = False, encoding: str, - errors: str | None = ..., + errors: str | None = None, input: str | None = None, - text: bool | None = ..., + text: bool | None = None, timeout: float | None = None, - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, ) -> CompletedProcess[str]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, check: bool = False, - encoding: str | None = ..., + encoding: str | None = None, errors: str, input: str | None = None, - text: bool | None = ..., + text: bool | None = None, timeout: float | None = None, - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, ) -> CompletedProcess[str]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, *, universal_newlines: Literal[True], - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., # where the *real* keyword only args start capture_output: bool = False, check: bool = False, - encoding: str | None = ..., - errors: str | None = ..., + encoding: str | None = None, + errors: str | None = None, input: str | None = None, - text: bool | None = ..., + text: bool | None = None, timeout: float | None = None, - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, ) -> CompletedProcess[str]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: Literal[False, None] = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: Literal[False] | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, @@ -662,71 +656,71 @@ elif sys.version_info >= (3, 9): encoding: None = None, errors: None = None, input: ReadableBuffer | None = None, - text: Literal[None, False] = ..., + text: Literal[False] | None = None, timeout: float | None = None, - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, ) -> CompletedProcess[bytes]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, check: bool = False, - encoding: str | None = ..., - errors: str | None = ..., + encoding: str | None = None, + errors: str | None = None, input: _InputString | None = None, - text: bool | None = ..., + text: bool | None = None, timeout: float | None = None, - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, ) -> CompletedProcess[Any]: ... else: @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, check: bool = False, - encoding: str | None = ..., - errors: str | None = ..., + encoding: str | None = None, + errors: str | None = None, input: str | None = None, text: Literal[True], timeout: float | None = None, @@ -734,106 +728,106 @@ else: @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, check: bool = False, encoding: str, - errors: str | None = ..., + errors: str | None = None, input: str | None = None, - text: bool | None = ..., + text: bool | None = None, timeout: float | None = None, ) -> CompletedProcess[str]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, check: bool = False, - encoding: str | None = ..., + encoding: str | None = None, errors: str, input: str | None = None, - text: bool | None = ..., + text: bool | None = None, timeout: float | None = None, ) -> CompletedProcess[str]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, *, universal_newlines: Literal[True], - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., # where the *real* keyword only args start capture_output: bool = False, check: bool = False, - encoding: str | None = ..., - errors: str | None = ..., + encoding: str | None = None, + errors: str | None = None, input: str | None = None, - text: bool | None = ..., + text: bool | None = None, timeout: float | None = None, ) -> CompletedProcess[str]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: Literal[False, None] = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: Literal[False] | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, @@ -841,35 +835,35 @@ else: encoding: None = None, errors: None = None, input: ReadableBuffer | None = None, - text: Literal[None, False] = ..., + text: Literal[False] | None = None, timeout: float | None = None, ) -> CompletedProcess[bytes]: ... @overload def run( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, capture_output: bool = False, check: bool = False, - encoding: str | None = ..., - errors: str | None = ..., + encoding: str | None = None, + errors: str | None = None, input: _InputString | None = None, - text: bool | None = ..., + text: bool | None = None, timeout: float | None = None, ) -> CompletedProcess[Any]: ... @@ -878,114 +872,114 @@ if sys.version_info >= (3, 11): # 3.11 adds "process_group" argument def call( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, - text: bool | None = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., - process_group: int | None = ..., + text: bool | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, + process_group: int | None = None, ) -> int: ... elif sys.version_info >= (3, 10): # 3.10 adds "pipesize" argument def call( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, - text: bool | None = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., + text: bool | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, ) -> int: ... elif sys.version_info >= (3, 9): # 3.9 adds arguments "user", "group", "extra_groups" and "umask" def call( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, - text: bool | None = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., + text: bool | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, ) -> int: ... else: def call( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, - text: bool | None = ..., + text: bool | None = None, ) -> int: ... # Same args as Popen.__init__ @@ -993,114 +987,114 @@ if sys.version_info >= (3, 11): # 3.11 adds "process_group" argument def check_call( args: _CMD, - bufsize: int = ..., + bufsize: int = -1, executable: StrOrBytesPath | None = None, - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., timeout: float | None = ..., *, - text: bool | None = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., - process_group: int | None = ..., + text: bool | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, + process_group: int | None = None, ) -> int: ... elif sys.version_info >= (3, 10): # 3.10 adds "pipesize" argument def check_call( args: _CMD, - bufsize: int = ..., + bufsize: int = -1, executable: StrOrBytesPath | None = None, - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., timeout: float | None = ..., *, - text: bool | None = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., + text: bool | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, ) -> int: ... elif sys.version_info >= (3, 9): # 3.9 adds arguments "user", "group", "extra_groups" and "umask" def check_call( args: _CMD, - bufsize: int = ..., + bufsize: int = -1, executable: StrOrBytesPath | None = None, - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., timeout: float | None = ..., *, - text: bool | None = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., + text: bool | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, ) -> int: ... else: def check_call( args: _CMD, - bufsize: int = ..., + bufsize: int = -1, executable: StrOrBytesPath | None = None, - stdin: _FILE = ..., - stdout: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + stdin: _FILE = None, + stdout: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., timeout: float | None = ..., *, - text: bool | None = ..., + text: bool | None = None, ) -> int: ... if sys.version_info >= (3, 11): @@ -1108,189 +1102,189 @@ if sys.version_info >= (3, 11): @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., - encoding: str | None = ..., - errors: str | None = ..., + encoding: str | None = None, + errors: str | None = None, text: Literal[True], - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., - process_group: int | None = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, + process_group: int | None = None, ) -> str: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., encoding: str, - errors: str | None = ..., - text: bool | None = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., - process_group: int | None = ..., + errors: str | None = None, + text: bool | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, + process_group: int | None = None, ) -> str: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., - encoding: str | None = ..., + encoding: str | None = None, errors: str, - text: bool | None = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., - process_group: int | None = ..., + text: bool | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, + process_group: int | None = None, ) -> str: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, *, universal_newlines: Literal[True], - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., # where the real keyword only ones start timeout: float | None = None, input: _InputString | None = ..., - encoding: str | None = ..., - errors: str | None = ..., - text: bool | None = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., - process_group: int | None = ..., + encoding: str | None = None, + errors: str | None = None, + text: bool | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, + process_group: int | None = None, ) -> str: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: Literal[False, None] = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: Literal[False] | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., encoding: None = None, errors: None = None, - text: Literal[None, False] = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., - process_group: int | None = ..., + text: Literal[False] | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, + process_group: int | None = None, ) -> bytes: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., - encoding: str | None = ..., - errors: str | None = ..., - text: bool | None = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., - process_group: int | None = ..., + encoding: str | None = None, + errors: str | None = None, + text: bool | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, + process_group: int | None = None, ) -> Any: ... # morally: -> str | bytes elif sys.version_info >= (3, 10): @@ -1298,183 +1292,183 @@ elif sys.version_info >= (3, 10): @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., - encoding: str | None = ..., - errors: str | None = ..., + encoding: str | None = None, + errors: str | None = None, text: Literal[True], - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, ) -> str: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., encoding: str, - errors: str | None = ..., - text: bool | None = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., + errors: str | None = None, + text: bool | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, ) -> str: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., - encoding: str | None = ..., + encoding: str | None = None, errors: str, - text: bool | None = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., + text: bool | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, ) -> str: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, *, universal_newlines: Literal[True], - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., # where the real keyword only ones start timeout: float | None = None, input: _InputString | None = ..., - encoding: str | None = ..., - errors: str | None = ..., - text: bool | None = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., + encoding: str | None = None, + errors: str | None = None, + text: bool | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, ) -> str: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: Literal[False, None] = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: Literal[False] | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., encoding: None = None, errors: None = None, - text: Literal[None, False] = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., + text: Literal[False] | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, ) -> bytes: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., - encoding: str | None = ..., - errors: str | None = ..., - text: bool | None = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., - pipesize: int = ..., + encoding: str | None = None, + errors: str | None = None, + text: bool | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + pipesize: int = -1, ) -> Any: ... # morally: -> str | bytes elif sys.version_info >= (3, 9): @@ -1482,330 +1476,330 @@ elif sys.version_info >= (3, 9): @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., - encoding: str | None = ..., - errors: str | None = ..., + encoding: str | None = None, + errors: str | None = None, text: Literal[True], - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, ) -> str: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., encoding: str, - errors: str | None = ..., - text: bool | None = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., + errors: str | None = None, + text: bool | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, ) -> str: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., - encoding: str | None = ..., + encoding: str | None = None, errors: str, - text: bool | None = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., + text: bool | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, ) -> str: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, *, universal_newlines: Literal[True], - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., # where the real keyword only ones start timeout: float | None = None, input: _InputString | None = ..., - encoding: str | None = ..., - errors: str | None = ..., - text: bool | None = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., + encoding: str | None = None, + errors: str | None = None, + text: bool | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, ) -> str: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: Literal[False, None] = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: Literal[False] | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., encoding: None = None, errors: None = None, - text: Literal[None, False] = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., + text: Literal[False] | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, ) -> bytes: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., - encoding: str | None = ..., - errors: str | None = ..., - text: bool | None = ..., - user: str | int | None = ..., - group: str | int | None = ..., - extra_groups: Iterable[str | int] | None = ..., - umask: int = ..., + encoding: str | None = None, + errors: str | None = None, + text: bool | None = None, + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, ) -> Any: ... # morally: -> str | bytes else: @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., - encoding: str | None = ..., - errors: str | None = ..., + encoding: str | None = None, + errors: str | None = None, text: Literal[True], ) -> str: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., encoding: str, - errors: str | None = ..., - text: bool | None = ..., + errors: str | None = None, + text: bool | None = None, ) -> str: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., - encoding: str | None = ..., + encoding: str | None = None, errors: str, - text: bool | None = ..., + text: bool | None = None, ) -> str: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, *, universal_newlines: Literal[True], - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., # where the real keyword only ones start timeout: float | None = None, input: _InputString | None = ..., - encoding: str | None = ..., - errors: str | None = ..., - text: bool | None = ..., + encoding: str | None = None, + errors: str | None = None, + text: bool | None = None, ) -> str: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: Literal[False, None] = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: Literal[False] | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., encoding: None = None, errors: None = None, - text: Literal[None, False] = ..., + text: Literal[False] | None = None, ) -> bytes: ... @overload def check_output( args: _CMD, - bufsize: int = ..., - executable: StrOrBytesPath | None = ..., - stdin: _FILE = ..., - stderr: _FILE = ..., - preexec_fn: Callable[[], Any] | None = ..., - close_fds: bool = ..., - shell: bool = ..., - cwd: StrOrBytesPath | None = ..., - env: _ENV | None = ..., - universal_newlines: bool | None = ..., - startupinfo: Any = ..., - creationflags: int = ..., - restore_signals: bool = ..., - start_new_session: bool = ..., + bufsize: int = -1, + executable: StrOrBytesPath | None = None, + stdin: _FILE = None, + stderr: _FILE = None, + preexec_fn: Callable[[], Any] | None = None, + close_fds: bool = True, + shell: bool = False, + cwd: StrOrBytesPath | None = None, + env: _ENV | None = None, + universal_newlines: bool | None = None, + startupinfo: Any = None, + creationflags: int = 0, + restore_signals: bool = True, + start_new_session: bool = False, pass_fds: Collection[int] = ..., *, timeout: float | None = None, input: _InputString | None = ..., - encoding: str | None = ..., - errors: str | None = ..., - text: bool | None = ..., + encoding: str | None = None, + errors: str | None = None, + text: bool | None = None, ) -> Any: ... # morally: -> str | bytes PIPE: int @@ -1870,7 +1864,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, text: bool | None = None, encoding: str, @@ -1901,7 +1895,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, text: bool | None = None, encoding: str | None = None, @@ -1933,7 +1927,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), # where the *real* keyword only args start text: bool | None = None, encoding: str | None = None, @@ -1964,7 +1958,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, text: Literal[True], encoding: str | None = None, @@ -1990,14 +1984,14 @@ class Popen(Generic[AnyStr]): shell: bool = False, cwd: StrOrBytesPath | None = None, env: _ENV | None = None, - universal_newlines: Literal[False, None] = None, + universal_newlines: Literal[False] | None = None, startupinfo: Any | None = None, creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, - text: Literal[None, False] = None, + text: Literal[False] | None = None, encoding: None = None, errors: None = None, user: str | int | None = None, @@ -2026,7 +2020,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, text: bool | None = None, encoding: str | None = None, @@ -2059,7 +2053,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, text: bool | None = None, encoding: str, @@ -2089,7 +2083,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, text: bool | None = None, encoding: str | None = None, @@ -2120,7 +2114,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), # where the *real* keyword only args start text: bool | None = None, encoding: str | None = None, @@ -2150,7 +2144,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, text: Literal[True], encoding: str | None = None, @@ -2175,14 +2169,14 @@ class Popen(Generic[AnyStr]): shell: bool = False, cwd: StrOrBytesPath | None = None, env: _ENV | None = None, - universal_newlines: Literal[False, None] = None, + universal_newlines: Literal[False] | None = None, startupinfo: Any | None = None, creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, - text: Literal[None, False] = None, + text: Literal[False] | None = None, encoding: None = None, errors: None = None, user: str | int | None = None, @@ -2210,7 +2204,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, text: bool | None = None, encoding: str | None = None, @@ -2242,7 +2236,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, text: bool | None = None, encoding: str, @@ -2271,7 +2265,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, text: bool | None = None, encoding: str | None = None, @@ -2301,7 +2295,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), # where the *real* keyword only args start text: bool | None = None, encoding: str | None = None, @@ -2330,7 +2324,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, text: Literal[True], encoding: str | None = None, @@ -2354,14 +2348,14 @@ class Popen(Generic[AnyStr]): shell: bool = False, cwd: StrOrBytesPath | None = None, env: _ENV | None = None, - universal_newlines: Literal[False, None] = None, + universal_newlines: Literal[False] | None = None, startupinfo: Any | None = None, creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, - text: Literal[None, False] = None, + text: Literal[False] | None = None, encoding: None = None, errors: None = None, user: str | int | None = None, @@ -2388,7 +2382,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, text: bool | None = None, encoding: str | None = None, @@ -2418,7 +2412,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, text: bool | None = None, encoding: str, @@ -2443,7 +2437,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, text: bool | None = None, encoding: str | None = None, @@ -2469,7 +2463,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), # where the *real* keyword only args start text: bool | None = None, encoding: str | None = None, @@ -2494,7 +2488,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, text: Literal[True], encoding: str | None = None, @@ -2514,14 +2508,14 @@ class Popen(Generic[AnyStr]): shell: bool = False, cwd: StrOrBytesPath | None = None, env: _ENV | None = None, - universal_newlines: Literal[False, None] = None, + universal_newlines: Literal[False] | None = None, startupinfo: Any | None = None, creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, - text: Literal[None, False] = None, + text: Literal[False] | None = None, encoding: None = None, errors: None = None, ) -> None: ... @@ -2544,7 +2538,7 @@ class Popen(Generic[AnyStr]): creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Collection[int] = ..., + pass_fds: Collection[int] = (), *, text: bool | None = None, encoding: str | None = None, @@ -2564,23 +2558,20 @@ class Popen(Generic[AnyStr]): def __exit__( self, exc_type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None ) -> None: ... + def __del__(self) -> None: ... if sys.version_info >= (3, 9): def __class_getitem__(cls, item: Any) -> GenericAlias: ... # The result really is always a str. if sys.version_info >= (3, 11): - def getstatusoutput(cmd: str | bytes, *, encoding: str | None = None, errors: str | None = None) -> tuple[int, str]: ... - def getoutput(cmd: str | bytes, *, encoding: str | None = None, errors: str | None = None) -> str: ... + def getstatusoutput(cmd: _CMD, *, encoding: str | None = None, errors: str | None = None) -> tuple[int, str]: ... + def getoutput(cmd: _CMD, *, encoding: str | None = None, errors: str | None = None) -> str: ... else: - def getstatusoutput(cmd: str | bytes) -> tuple[int, str]: ... - def getoutput(cmd: str | bytes) -> str: ... + def getstatusoutput(cmd: _CMD) -> tuple[int, str]: ... + def getoutput(cmd: _CMD) -> str: ... -if sys.version_info >= (3, 8): - def list2cmdline(seq: Iterable[StrOrBytesPath]) -> str: ... # undocumented - -else: - def list2cmdline(seq: Iterable[str]) -> str: ... # undocumented +def list2cmdline(seq: Iterable[StrOrBytesPath]) -> str: ... # undocumented if sys.platform == "win32": class STARTUPINFO: @@ -2600,6 +2591,8 @@ if sys.platform == "win32": hStdError: Any | None wShowWindow: int lpAttributeList: Mapping[str, Any] + def copy(self) -> STARTUPINFO: ... + from _winapi import ( ABOVE_NORMAL_PRIORITY_CLASS as ABOVE_NORMAL_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS as BELOW_NORMAL_PRIORITY_CLASS, diff --git a/mypy/typeshed/stdlib/sunau.pyi b/mypy/typeshed/stdlib/sunau.pyi index 6109b368c01a..9b051e82b64b 100644 --- a/mypy/typeshed/stdlib/sunau.pyi +++ b/mypy/typeshed/stdlib/sunau.pyi @@ -1,7 +1,7 @@ import sys from _typeshed import Unused -from typing import IO, Any, NamedTuple, NoReturn, overload -from typing_extensions import Literal, Self, TypeAlias +from typing import IO, Any, Literal, NamedTuple, NoReturn, overload +from typing_extensions import Self, TypeAlias _File: TypeAlias = str | IO[bytes] @@ -34,6 +34,7 @@ class Au_read: def __init__(self, f: _File) -> None: ... def __enter__(self) -> Self: ... def __exit__(self, *args: Unused) -> None: ... + def __del__(self) -> None: ... def getfp(self) -> IO[bytes] | None: ... def rewind(self) -> None: ... def close(self) -> None: ... @@ -54,6 +55,7 @@ class Au_write: def __init__(self, f: _File) -> None: ... def __enter__(self) -> Self: ... def __exit__(self, *args: Unused) -> None: ... + def __del__(self) -> None: ... def setnchannels(self, nchannels: int) -> None: ... def getnchannels(self) -> int: ... def setsampwidth(self, sampwidth: int) -> None: ... diff --git a/mypy/typeshed/stdlib/symbol.pyi b/mypy/typeshed/stdlib/symbol.pyi index bb6660374d16..48ae3567a1a5 100644 --- a/mypy/typeshed/stdlib/symbol.pyi +++ b/mypy/typeshed/stdlib/symbol.pyi @@ -1,5 +1,3 @@ -import sys - single_input: int file_input: int eval_input: int @@ -87,11 +85,9 @@ encoding_decl: int yield_expr: int yield_arg: int sync_comp_for: int -if sys.version_info >= (3, 8): - func_body_suite: int - func_type: int - func_type_input: int - namedexpr_test: int - typelist: int - +func_body_suite: int +func_type: int +func_type_input: int +namedexpr_test: int +typelist: int sym_name: dict[int, str] diff --git a/mypy/typeshed/stdlib/symtable.pyi b/mypy/typeshed/stdlib/symtable.pyi index 304ae8bf8126..0f080954ba2c 100644 --- a/mypy/typeshed/stdlib/symtable.pyi +++ b/mypy/typeshed/stdlib/symtable.pyi @@ -29,21 +29,16 @@ class Function(SymbolTable): def get_locals(self) -> tuple[str, ...]: ... def get_globals(self) -> tuple[str, ...]: ... def get_frees(self) -> tuple[str, ...]: ... - if sys.version_info >= (3, 8): - def get_nonlocals(self) -> tuple[str, ...]: ... + def get_nonlocals(self) -> tuple[str, ...]: ... class Class(SymbolTable): def get_methods(self) -> tuple[str, ...]: ... class Symbol: - if sys.version_info >= (3, 8): - def __init__( - self, name: str, flags: int, namespaces: Sequence[SymbolTable] | None = None, *, module_scope: bool = False - ) -> None: ... - def is_nonlocal(self) -> bool: ... - else: - def __init__(self, name: str, flags: int, namespaces: Sequence[SymbolTable] | None = None) -> None: ... - + def __init__( + self, name: str, flags: int, namespaces: Sequence[SymbolTable] | None = None, *, module_scope: bool = False + ) -> None: ... + def is_nonlocal(self) -> bool: ... def get_name(self) -> str: ... def is_referenced(self) -> bool: ... def is_parameter(self) -> bool: ... diff --git a/mypy/typeshed/stdlib/sys.pyi b/mypy/typeshed/stdlib/sys/__init__.pyi similarity index 73% rename from mypy/typeshed/stdlib/sys.pyi rename to mypy/typeshed/stdlib/sys/__init__.pyi index e12881599b4a..5867c9a9d510 100644 --- a/mypy/typeshed/stdlib/sys.pyi +++ b/mypy/typeshed/stdlib/sys/__init__.pyi @@ -1,13 +1,12 @@ import sys from _typeshed import OptExcInfo, ProfileFunction, TraceFunction, structseq +from _typeshed.importlib import MetaPathFinderProtocol, PathEntryFinderProtocol from builtins import object as _object -from collections.abc import AsyncGenerator, Callable, Coroutine, Sequence -from importlib.abc import PathEntryFinder -from importlib.machinery import ModuleSpec +from collections.abc import AsyncGenerator, Callable, Sequence from io import TextIOWrapper from types import FrameType, ModuleType, TracebackType -from typing import Any, NoReturn, Protocol, TextIO, TypeVar, overload -from typing_extensions import Final, Literal, TypeAlias, final +from typing import Any, Final, Literal, NoReturn, Protocol, TextIO, TypeVar, final +from typing_extensions import TypeAlias _T = TypeVar("_T") @@ -15,10 +14,6 @@ _T = TypeVar("_T") _ExitCode: TypeAlias = str | int | None _OptExcInfo: TypeAlias = OptExcInfo # noqa: Y047 # TODO: obsolete, remove fall 2022 or later -# Intentionally omits one deprecated and one optional method of `importlib.abc.MetaPathFinder` -class _MetaPathFinder(Protocol): - def find_spec(self, fullname: str, path: Sequence[str] | None, target: ModuleType | None = ...) -> ModuleSpec | None: ... - # ----- sys variables ----- if sys.platform != "win32": abiflags: str @@ -40,26 +35,38 @@ hexversion: int last_type: type[BaseException] | None last_value: BaseException | None last_traceback: TracebackType | None +if sys.version_info >= (3, 12): + last_exc: BaseException # or undefined. maxsize: int maxunicode: int -meta_path: list[_MetaPathFinder] +meta_path: list[MetaPathFinderProtocol] modules: dict[str, ModuleType] if sys.version_info >= (3, 10): orig_argv: list[str] path: list[str] -path_hooks: list[Callable[[str], PathEntryFinder]] -path_importer_cache: dict[str, PathEntryFinder | None] +path_hooks: list[Callable[[str], PathEntryFinderProtocol]] +path_importer_cache: dict[str, PathEntryFinderProtocol | None] platform: str if sys.version_info >= (3, 9): platlibdir: str prefix: str -if sys.version_info >= (3, 8): - pycache_prefix: str | None +pycache_prefix: str | None ps1: object ps2: object + +# TextIO is used instead of more specific types for the standard streams, +# since they are often monkeypatched at runtime. At startup, the objects +# are initialized to instances of TextIOWrapper. +# +# To use methods from TextIOWrapper, use an isinstance check to ensure that +# the streams have not been overridden: +# +# if isinstance(sys.stdout, io.TextIOWrapper): +# sys.stdout.reconfigure(...) stdin: TextIO stdout: TextIO stderr: TextIO + if sys.version_info >= (3, 10): stdlib_module_names: frozenset[str] @@ -201,8 +208,23 @@ class _int_info(structseq[int], tuple[int, int, int, int]): @property def str_digits_check_threshold(self) -> int: ... +_ThreadInfoName: TypeAlias = Literal["nt", "pthread", "pthread-stubs", "solaris"] +_ThreadInfoLock: TypeAlias = Literal["semaphore", "mutex+cond"] | None + +@final +class _thread_info(_UninstantiableStructseq, tuple[_ThreadInfoName, _ThreadInfoLock, str | None]): + @property + def name(self) -> _ThreadInfoName: ... + @property + def lock(self) -> _ThreadInfoLock: ... + @property + def version(self) -> str | None: ... + +thread_info: _thread_info +_ReleaseLevel: TypeAlias = Literal["alpha", "beta", "candidate", "final"] + @final -class _version_info(_UninstantiableStructseq, tuple[int, int, int, str, int]): +class _version_info(_UninstantiableStructseq, tuple[int, int, int, _ReleaseLevel, int]): @property def major(self) -> int: ... @property @@ -210,25 +232,25 @@ class _version_info(_UninstantiableStructseq, tuple[int, int, int, str, int]): @property def micro(self) -> int: ... @property - def releaselevel(self) -> str: ... + def releaselevel(self) -> _ReleaseLevel: ... @property def serial(self) -> int: ... version_info: _version_info -def call_tracing(__func: Callable[..., _T], __args: Any) -> _T: ... +def call_tracing(func: Callable[..., _T], args: Any, /) -> _T: ... def _clear_type_cache() -> None: ... def _current_frames() -> dict[int, FrameType]: ... -def _getframe(__depth: int = 0) -> FrameType: ... +def _getframe(depth: int = 0, /) -> FrameType: ... def _debugmallocstats() -> None: ... -def __displayhook__(__value: object) -> None: ... -def __excepthook__(__exctype: type[BaseException], __value: BaseException, __traceback: TracebackType | None) -> None: ... +def __displayhook__(object: object, /) -> None: ... +def __excepthook__(exctype: type[BaseException], value: BaseException, traceback: TracebackType | None, /) -> None: ... def exc_info() -> OptExcInfo: ... if sys.version_info >= (3, 11): def exception() -> BaseException | None: ... -def exit(__status: _ExitCode = None) -> NoReturn: ... +def exit(status: _ExitCode = None, /) -> NoReturn: ... def getallocatedblocks() -> int: ... def getdefaultencoding() -> str: ... @@ -237,12 +259,9 @@ if sys.platform != "win32": def getfilesystemencoding() -> str: ... def getfilesystemencodeerrors() -> str: ... -def getrefcount(__object: Any) -> int: ... +def getrefcount(object: Any, /) -> int: ... def getrecursionlimit() -> int: ... -@overload -def getsizeof(obj: object) -> int: ... -@overload -def getsizeof(obj: object, default: int) -> int: ... +def getsizeof(obj: object, default: int = ...) -> int: ... def getswitchinterval() -> float: ... def getprofile() -> ProfileFunction | None: ... def setprofile(profilefunc: ProfileFunction | None) -> None: ... @@ -276,39 +295,40 @@ if sys.platform == "win32": def getwindowsversion() -> _WinVersion: ... -def intern(__string: str) -> str: ... +def intern(string: str, /) -> str: ... def is_finalizing() -> bool: ... def breakpointhook(*args: Any, **kwargs: Any) -> Any: ... __breakpointhook__ = breakpointhook # Contains the original value of breakpointhook if sys.platform != "win32": - def setdlopenflags(__flags: int) -> None: ... + def setdlopenflags(flags: int, /) -> None: ... -def setrecursionlimit(__limit: int) -> None: ... -def setswitchinterval(__interval: float) -> None: ... +def setrecursionlimit(limit: int, /) -> None: ... +def setswitchinterval(interval: float, /) -> None: ... def gettotalrefcount() -> int: ... # Debug builds only if sys.version_info < (3, 9): def getcheckinterval() -> int: ... # deprecated - def setcheckinterval(__n: int) -> None: ... # deprecated + def setcheckinterval(n: int, /) -> None: ... # deprecated if sys.version_info < (3, 9): # An 11-tuple or None def callstats() -> tuple[int, int, int, int, int, int, int, int, int, int, int] | None: ... -if sys.version_info >= (3, 8): - # Doesn't exist at runtime, but exported in the stubs so pytest etc. can annotate their code more easily. - class UnraisableHookArgs: - exc_type: type[BaseException] - exc_value: BaseException | None - exc_traceback: TracebackType | None - err_msg: str | None - object: _object - unraisablehook: Callable[[UnraisableHookArgs], Any] - def __unraisablehook__(__unraisable: UnraisableHookArgs) -> Any: ... - def addaudithook(hook: Callable[[str, tuple[Any, ...]], Any]) -> None: ... - def audit(__event: str, *args: Any) -> None: ... +# Doesn't exist at runtime, but exported in the stubs so pytest etc. can annotate their code more easily. +class UnraisableHookArgs(Protocol): + exc_type: type[BaseException] + exc_value: BaseException | None + exc_traceback: TracebackType | None + err_msg: str | None + object: _object + +unraisablehook: Callable[[UnraisableHookArgs], Any] + +def __unraisablehook__(unraisable: UnraisableHookArgs, /) -> Any: ... +def addaudithook(hook: Callable[[str, tuple[Any, ...]], Any]) -> None: ... +def audit(event: str, /, *args: Any) -> None: ... _AsyncgenHook: TypeAlias = Callable[[AsyncGenerator[Any, Any]], None] | None @@ -328,12 +348,21 @@ if sys.platform == "win32": def get_coroutine_origin_tracking_depth() -> int: ... def set_coroutine_origin_tracking_depth(depth: int) -> None: ... -if sys.version_info < (3, 8): - _CoroWrapper: TypeAlias = Callable[[Coroutine[Any, Any, Any]], Any] - def set_coroutine_wrapper(__wrapper: _CoroWrapper) -> None: ... - def get_coroutine_wrapper() -> _CoroWrapper: ... - -# The following two functions were added in 3.11.0, 3.10.7, 3.9.14, 3.8.14, & 3.7.14, +# The following two functions were added in 3.11.0, 3.10.7, 3.9.14, and 3.8.14, # as part of the response to CVE-2020-10735 def set_int_max_str_digits(maxdigits: int) -> None: ... def get_int_max_str_digits() -> int: ... + +if sys.version_info >= (3, 12): + def getunicodeinternedsize() -> int: ... + def deactivate_stack_trampoline() -> None: ... + def is_stack_trampoline_active() -> bool: ... + # It always exists, but raises on non-linux platforms: + if sys.platform == "linux": + def activate_stack_trampoline(backend: str, /) -> None: ... + else: + def activate_stack_trampoline(backend: str, /) -> NoReturn: ... + + from . import _monitoring + + monitoring = _monitoring diff --git a/mypy/typeshed/stdlib/sys/_monitoring.pyi b/mypy/typeshed/stdlib/sys/_monitoring.pyi new file mode 100644 index 000000000000..0507eeedc26d --- /dev/null +++ b/mypy/typeshed/stdlib/sys/_monitoring.pyi @@ -0,0 +1,52 @@ +# This py312+ module provides annotations for `sys.monitoring`. +# It's named `sys._monitoring` in typeshed, +# because trying to import `sys.monitoring` will fail at runtime! +# At runtime, `sys.monitoring` has the unique status +# of being a `types.ModuleType` instance that cannot be directly imported, +# and exists in the `sys`-module namespace despite `sys` not being a package. + +from collections.abc import Callable +from types import CodeType +from typing import Any + +DEBUGGER_ID: int +COVERAGE_ID: int +PROFILER_ID: int +OPTIMIZER_ID: int + +def use_tool_id(tool_id: int, name: str, /) -> None: ... +def free_tool_id(tool_id: int, /) -> None: ... +def get_tool(tool_id: int, /) -> str | None: ... + +events: _events + +class _events: + BRANCH: int + CALL: int + C_RAISE: int + C_RETURN: int + EXCEPTION_HANDLED: int + INSTRUCTION: int + JUMP: int + LINE: int + NO_EVENTS: int + PY_RESUME: int + PY_RETURN: int + PY_START: int + PY_THROW: int + PY_UNWIND: int + PY_YIELD: int + RAISE: int + RERAISE: int + STOP_ITERATION: int + +def get_events(tool_id: int, /) -> int: ... +def set_events(tool_id: int, event_set: int, /) -> None: ... +def get_local_events(tool_id: int, code: CodeType, /) -> int: ... +def set_local_events(tool_id: int, code: CodeType, event_set: int, /) -> int: ... +def restart_events() -> None: ... + +DISABLE: object +MISSING: object + +def register_callback(tool_id: int, event: int, func: Callable[..., Any] | None, /) -> Callable[..., Any] | None: ... diff --git a/mypy/typeshed/stdlib/sysconfig.pyi b/mypy/typeshed/stdlib/sysconfig.pyi index 7e29cf1326d6..807a979050e8 100644 --- a/mypy/typeshed/stdlib/sysconfig.pyi +++ b/mypy/typeshed/stdlib/sysconfig.pyi @@ -1,6 +1,6 @@ import sys -from typing import IO, Any, overload -from typing_extensions import Literal +from typing import IO, Any, Literal, overload +from typing_extensions import deprecated __all__ = [ "get_config_h_filename", @@ -16,11 +16,15 @@ __all__ = [ "parse_config_h", ] +@overload +@deprecated("SO is deprecated, use EXT_SUFFIX. Support is removed in Python 3.11") +def get_config_var(name: Literal["SO"]) -> Any: ... +@overload def get_config_var(name: str) -> Any: ... @overload def get_config_vars() -> dict[str, Any]: ... @overload -def get_config_vars(arg: str, *args: str) -> list[Any]: ... +def get_config_vars(arg: str, /, *args: str) -> list[Any]: ... def get_scheme_names() -> tuple[str, ...]: ... if sys.version_info >= (3, 10): diff --git a/mypy/typeshed/stdlib/syslog.pyi b/mypy/typeshed/stdlib/syslog.pyi index cfa8df887c1b..02876e0b7e85 100644 --- a/mypy/typeshed/stdlib/syslog.pyi +++ b/mypy/typeshed/stdlib/syslog.pyi @@ -1,6 +1,5 @@ import sys -from typing import overload -from typing_extensions import Literal +from typing import Literal, overload if sys.platform != "win32": LOG_ALERT: Literal[1] @@ -36,11 +35,11 @@ if sys.platform != "win32": LOG_USER: Literal[8] LOG_UUCP: Literal[64] LOG_WARNING: Literal[4] - def LOG_MASK(a: int) -> int: ... - def LOG_UPTO(a: int) -> int: ... + def LOG_MASK(pri: int, /) -> int: ... + def LOG_UPTO(pri: int, /) -> int: ... def closelog() -> None: ... def openlog(ident: str = ..., logoption: int = ..., facility: int = ...) -> None: ... - def setlogmask(x: int) -> int: ... + def setlogmask(maskpri: int, /) -> int: ... @overload def syslog(priority: int, message: str) -> None: ... @overload diff --git a/mypy/typeshed/stdlib/tarfile.pyi b/mypy/typeshed/stdlib/tarfile.pyi index 5cf1d55cac63..b6fe454eff78 100644 --- a/mypy/typeshed/stdlib/tarfile.pyi +++ b/mypy/typeshed/stdlib/tarfile.pyi @@ -6,8 +6,8 @@ from builtins import list as _list # aliases to avoid name clashes with fields from collections.abc import Callable, Iterable, Iterator, Mapping from gzip import _ReadableFileobj as _GzipReadableFileobj, _WritableFileobj as _GzipWritableFileobj from types import TracebackType -from typing import IO, ClassVar, Protocol, overload -from typing_extensions import Literal, Self +from typing import IO, ClassVar, Literal, Protocol, overload +from typing_extensions import Self, TypeAlias __all__ = [ "TarFile", @@ -26,12 +26,27 @@ __all__ = [ "DEFAULT_FORMAT", "open", ] +if sys.version_info >= (3, 12): + __all__ += [ + "fully_trusted_filter", + "data_filter", + "tar_filter", + "FilterError", + "AbsoluteLinkError", + "OutsideDestinationError", + "SpecialFileError", + "AbsolutePathError", + "LinkOutsideDestinationError", + ] + +_FilterFunction: TypeAlias = Callable[[TarInfo, str], TarInfo | None] +_TarfileFilter: TypeAlias = Literal["fully_trusted", "tar", "data"] | _FilterFunction class _Fileobj(Protocol): - def read(self, __size: int) -> bytes: ... - def write(self, __b: bytes) -> object: ... + def read(self, size: int, /) -> bytes: ... + def write(self, b: bytes, /) -> object: ... def tell(self) -> int: ... - def seek(self, __pos: int) -> object: ... + def seek(self, pos: int, /) -> object: ... def close(self) -> object: ... # Optional fields: # name: str | bytes @@ -104,6 +119,7 @@ def open( debug: int | None = ..., errorlevel: int | None = ..., compresslevel: int | None = ..., + preset: Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] | None = ..., ) -> TarFile: ... class ExFileObject(io.BufferedReader): @@ -125,6 +141,7 @@ class TarFile: debug: int | None errorlevel: int | None offset: int # undocumented + extraction_filter: _FilterFunction | None def __init__( self, name: StrOrBytesPath | None = None, @@ -275,11 +292,25 @@ class TarFile: def getnames(self) -> _list[str]: ... def list(self, verbose: bool = True, *, members: _list[TarInfo] | None = None) -> None: ... def next(self) -> TarInfo | None: ... + # Calling this method without `filter` is deprecated, but it may be set either on the class or in an + # individual call, so we can't mark it as @deprecated here. def extractall( - self, path: StrOrBytesPath = ".", members: Iterable[TarInfo] | None = None, *, numeric_owner: bool = False + self, + path: StrOrBytesPath = ".", + members: Iterable[TarInfo] | None = None, + *, + numeric_owner: bool = False, + filter: _TarfileFilter | None = ..., ) -> None: ... + # Same situation as for `extractall`. def extract( - self, member: str | TarInfo, path: StrOrBytesPath = "", set_attrs: bool = True, *, numeric_owner: bool = False + self, + member: str | TarInfo, + path: StrOrBytesPath = "", + set_attrs: bool = True, + *, + numeric_owner: bool = False, + filter: _TarfileFilter | None = ..., ) -> None: ... def _extract_member( self, tarinfo: TarInfo, targetpath: str, set_attrs: bool = True, numeric_owner: bool = False @@ -314,9 +345,6 @@ if sys.version_info >= (3, 9): else: def is_tarfile(name: StrOrBytesPath) -> bool: ... -if sys.version_info < (3, 8): - def filemode(mode: int) -> str: ... # undocumented - class TarError(Exception): ... class ReadError(TarError): ... class CompressionError(TarError): ... @@ -324,6 +352,30 @@ class StreamError(TarError): ... class ExtractError(TarError): ... class HeaderError(TarError): ... +class FilterError(TarError): + # This attribute is only set directly on the subclasses, but the documentation guarantees + # that it is always present on FilterError. + tarinfo: TarInfo + +class AbsolutePathError(FilterError): + def __init__(self, tarinfo: TarInfo) -> None: ... + +class OutsideDestinationError(FilterError): + def __init__(self, tarinfo: TarInfo, path: str) -> None: ... + +class SpecialFileError(FilterError): + def __init__(self, tarinfo: TarInfo) -> None: ... + +class AbsoluteLinkError(FilterError): + def __init__(self, tarinfo: TarInfo) -> None: ... + +class LinkOutsideDestinationError(FilterError): + def __init__(self, tarinfo: TarInfo, path: str) -> None: ... + +def fully_trusted_filter(member: TarInfo, dest_path: str) -> TarInfo: ... +def tar_filter(member: TarInfo, dest_path: str) -> TarInfo: ... +def data_filter(member: TarInfo, dest_path: str) -> TarInfo: ... + class TarInfo: name: str path: str @@ -353,12 +405,21 @@ class TarInfo: def linkpath(self) -> str: ... @linkpath.setter def linkpath(self, linkname: str) -> None: ... + def replace( + self, + *, + name: str = ..., + mtime: int = ..., + mode: int = ..., + linkname: str = ..., + uid: int = ..., + gid: int = ..., + uname: str = ..., + gname: str = ..., + deep: bool = True, + ) -> Self: ... def get_info(self) -> Mapping[str, str | int | bytes | Mapping[str, str]]: ... - if sys.version_info >= (3, 8): - def tobuf(self, format: int | None = 2, encoding: str | None = "utf-8", errors: str = "surrogateescape") -> bytes: ... - else: - def tobuf(self, format: int | None = 1, encoding: str | None = "utf-8", errors: str = "surrogateescape") -> bytes: ... - + def tobuf(self, format: int | None = 2, encoding: str | None = "utf-8", errors: str = "surrogateescape") -> bytes: ... def create_ustar_header( self, info: Mapping[str, str | int | bytes | Mapping[str, str]], encoding: str, errors: str ) -> bytes: ... diff --git a/mypy/typeshed/stdlib/telnetlib.pyi b/mypy/typeshed/stdlib/telnetlib.pyi index 10f6e4930f75..d244d54f2fbf 100644 --- a/mypy/typeshed/stdlib/telnetlib.pyi +++ b/mypy/typeshed/stdlib/telnetlib.pyi @@ -119,3 +119,4 @@ class Telnet: def __exit__( self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None ) -> None: ... + def __del__(self) -> None: ... diff --git a/mypy/typeshed/stdlib/tempfile.pyi b/mypy/typeshed/stdlib/tempfile.pyi index dbff6d632d02..b66369926404 100644 --- a/mypy/typeshed/stdlib/tempfile.pyi +++ b/mypy/typeshed/stdlib/tempfile.pyi @@ -1,10 +1,21 @@ import io import sys -from _typeshed import BytesPath, GenericPath, StrPath, WriteableBuffer +from _typeshed import ( + BytesPath, + GenericPath, + OpenBinaryMode, + OpenBinaryModeReading, + OpenBinaryModeUpdating, + OpenBinaryModeWriting, + OpenTextMode, + ReadableBuffer, + StrPath, + WriteableBuffer, +) from collections.abc import Iterable, Iterator from types import TracebackType -from typing import IO, Any, AnyStr, Generic, overload -from typing_extensions import Literal, Self, TypeAlias +from typing import IO, Any, AnyStr, Generic, Literal, overload +from typing_extensions import Self if sys.version_info >= (3, 9): from types import GenericAlias @@ -30,13 +41,10 @@ TMP_MAX: int tempdir: str | None template: str -_StrMode: TypeAlias = Literal["r", "w", "a", "x", "r+", "w+", "a+", "x+", "rt", "wt", "at", "xt", "r+t", "w+t", "a+t", "x+t"] -_BytesMode: TypeAlias = Literal["rb", "wb", "ab", "xb", "r+b", "w+b", "a+b", "x+b"] - -if sys.version_info >= (3, 8): +if sys.version_info >= (3, 12): @overload def NamedTemporaryFile( - mode: _StrMode, + mode: OpenTextMode, buffering: int = -1, encoding: str | None = None, newline: str | None = None, @@ -46,10 +54,11 @@ if sys.version_info >= (3, 8): delete: bool = True, *, errors: str | None = None, + delete_on_close: bool = True, ) -> _TemporaryFileWrapper[str]: ... @overload def NamedTemporaryFile( - mode: _BytesMode = "w+b", + mode: OpenBinaryMode = "w+b", buffering: int = -1, encoding: str | None = None, newline: str | None = None, @@ -59,6 +68,7 @@ if sys.version_info >= (3, 8): delete: bool = True, *, errors: str | None = None, + delete_on_close: bool = True, ) -> _TemporaryFileWrapper[bytes]: ... @overload def NamedTemporaryFile( @@ -72,12 +82,13 @@ if sys.version_info >= (3, 8): delete: bool = True, *, errors: str | None = None, + delete_on_close: bool = True, ) -> _TemporaryFileWrapper[Any]: ... else: @overload def NamedTemporaryFile( - mode: _StrMode, + mode: OpenTextMode, buffering: int = -1, encoding: str | None = None, newline: str | None = None, @@ -85,10 +96,12 @@ else: prefix: AnyStr | None = None, dir: GenericPath[AnyStr] | None = None, delete: bool = True, + *, + errors: str | None = None, ) -> _TemporaryFileWrapper[str]: ... @overload def NamedTemporaryFile( - mode: _BytesMode = "w+b", + mode: OpenBinaryMode = "w+b", buffering: int = -1, encoding: str | None = None, newline: str | None = None, @@ -96,6 +109,8 @@ else: prefix: AnyStr | None = None, dir: GenericPath[AnyStr] | None = None, delete: bool = True, + *, + errors: str | None = None, ) -> _TemporaryFileWrapper[bytes]: ... @overload def NamedTemporaryFile( @@ -107,85 +122,107 @@ else: prefix: AnyStr | None = None, dir: GenericPath[AnyStr] | None = None, delete: bool = True, + *, + errors: str | None = None, ) -> _TemporaryFileWrapper[Any]: ... if sys.platform == "win32": TemporaryFile = NamedTemporaryFile else: - if sys.version_info >= (3, 8): - @overload - def TemporaryFile( - mode: _StrMode, - buffering: int = -1, - encoding: str | None = None, - newline: str | None = None, - suffix: AnyStr | None = None, - prefix: AnyStr | None = None, - dir: GenericPath[AnyStr] | None = None, - *, - errors: str | None = None, - ) -> IO[str]: ... - @overload - def TemporaryFile( - mode: _BytesMode = "w+b", - buffering: int = -1, - encoding: str | None = None, - newline: str | None = None, - suffix: AnyStr | None = None, - prefix: AnyStr | None = None, - dir: GenericPath[AnyStr] | None = None, - *, - errors: str | None = None, - ) -> IO[bytes]: ... - @overload - def TemporaryFile( - mode: str = "w+b", - buffering: int = -1, - encoding: str | None = None, - newline: str | None = None, - suffix: AnyStr | None = None, - prefix: AnyStr | None = None, - dir: GenericPath[AnyStr] | None = None, - *, - errors: str | None = None, - ) -> IO[Any]: ... - else: - @overload - def TemporaryFile( - mode: _StrMode, - buffering: int = ..., - encoding: str | None = ..., - newline: str | None = ..., - suffix: AnyStr | None = ..., - prefix: AnyStr | None = ..., - dir: GenericPath[AnyStr] | None = ..., - ) -> IO[str]: ... - @overload - def TemporaryFile( - mode: _BytesMode = ..., - buffering: int = ..., - encoding: str | None = ..., - newline: str | None = ..., - suffix: AnyStr | None = ..., - prefix: AnyStr | None = ..., - dir: GenericPath[AnyStr] | None = ..., - ) -> IO[bytes]: ... - @overload - def TemporaryFile( - mode: str = ..., - buffering: int = ..., - encoding: str | None = ..., - newline: str | None = ..., - suffix: AnyStr | None = ..., - prefix: AnyStr | None = ..., - dir: GenericPath[AnyStr] | None = ..., - ) -> IO[Any]: ... + # See the comments for builtins.open() for an explanation of the overloads. + @overload + def TemporaryFile( + mode: OpenTextMode, + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: GenericPath[AnyStr] | None = None, + *, + errors: str | None = None, + ) -> io.TextIOWrapper: ... + @overload + def TemporaryFile( + mode: OpenBinaryMode, + buffering: Literal[0], + encoding: str | None = None, + newline: str | None = None, + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: GenericPath[AnyStr] | None = None, + *, + errors: str | None = None, + ) -> io.FileIO: ... + @overload + def TemporaryFile( + *, + buffering: Literal[0], + encoding: str | None = None, + newline: str | None = None, + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: GenericPath[AnyStr] | None = None, + errors: str | None = None, + ) -> io.FileIO: ... + @overload + def TemporaryFile( + mode: OpenBinaryModeWriting, + buffering: Literal[-1, 1] = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: GenericPath[AnyStr] | None = None, + *, + errors: str | None = None, + ) -> io.BufferedWriter: ... + @overload + def TemporaryFile( + mode: OpenBinaryModeReading, + buffering: Literal[-1, 1] = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: GenericPath[AnyStr] | None = None, + *, + errors: str | None = None, + ) -> io.BufferedReader: ... + @overload + def TemporaryFile( + mode: OpenBinaryModeUpdating = "w+b", + buffering: Literal[-1, 1] = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: GenericPath[AnyStr] | None = None, + *, + errors: str | None = None, + ) -> io.BufferedRandom: ... + @overload + def TemporaryFile( + mode: str = "w+b", + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: GenericPath[AnyStr] | None = None, + *, + errors: str | None = None, + ) -> IO[Any]: ... -class _TemporaryFileWrapper(Generic[AnyStr], IO[AnyStr]): +class _TemporaryFileWrapper(IO[AnyStr]): file: IO[AnyStr] # io.TextIOWrapper, io.BufferedReader or io.BufferedWriter name: str delete: bool - def __init__(self, file: IO[AnyStr], name: str, delete: bool = True) -> None: ... + if sys.version_info >= (3, 12): + def __init__(self, file: IO[AnyStr], name: str, delete: bool = True, delete_on_close: bool = True) -> None: ... + else: + def __init__(self, file: IO[AnyStr], name: str, delete: bool = True) -> None: ... + def __enter__(self) -> Self: ... def __exit__(self, exc: type[BaseException] | None, value: BaseException | None, tb: TracebackType | None) -> None: ... def __getattr__(self, name: str) -> Any: ... @@ -215,7 +252,17 @@ class _TemporaryFileWrapper(Generic[AnyStr], IO[AnyStr]): def tell(self) -> int: ... def truncate(self, size: int | None = ...) -> int: ... def writable(self) -> bool: ... + @overload + def write(self: _TemporaryFileWrapper[str], s: str) -> int: ... + @overload + def write(self: _TemporaryFileWrapper[bytes], s: ReadableBuffer) -> int: ... + @overload def write(self, s: AnyStr) -> int: ... + @overload + def writelines(self: _TemporaryFileWrapper[str], lines: Iterable[str]) -> None: ... + @overload + def writelines(self: _TemporaryFileWrapper[bytes], lines: Iterable[ReadableBuffer]) -> None: ... + @overload def writelines(self, lines: Iterable[AnyStr]) -> None: ... if sys.version_info >= (3, 11): @@ -226,148 +273,84 @@ else: # It does not actually derive from IO[AnyStr], but it does mostly behave # like one. class SpooledTemporaryFile(IO[AnyStr], _SpooledTemporaryFileBase): + _file: IO[AnyStr] @property def encoding(self) -> str: ... # undocumented @property def newlines(self) -> str | tuple[str, ...] | None: ... # undocumented # bytes needs to go first, as default mode is to open as bytes - if sys.version_info >= (3, 8): - @overload - def __init__( - self: SpooledTemporaryFile[bytes], - max_size: int = 0, - mode: _BytesMode = "w+b", - buffering: int = -1, - encoding: str | None = None, - newline: str | None = None, - suffix: str | None = None, - prefix: str | None = None, - dir: str | None = None, - *, - errors: str | None = None, - ) -> None: ... - @overload - def __init__( - self: SpooledTemporaryFile[str], - max_size: int, - mode: _StrMode, - buffering: int = -1, - encoding: str | None = None, - newline: str | None = None, - suffix: str | None = None, - prefix: str | None = None, - dir: str | None = None, - *, - errors: str | None = None, - ) -> None: ... - @overload - def __init__( - self: SpooledTemporaryFile[str], - max_size: int = 0, - *, - mode: _StrMode, - buffering: int = -1, - encoding: str | None = None, - newline: str | None = None, - suffix: str | None = None, - prefix: str | None = None, - dir: str | None = None, - errors: str | None = None, - ) -> None: ... - @overload - def __init__( - self, - max_size: int, - mode: str, - buffering: int = -1, - encoding: str | None = None, - newline: str | None = None, - suffix: str | None = None, - prefix: str | None = None, - dir: str | None = None, - *, - errors: str | None = None, - ) -> None: ... - @overload - def __init__( - self, - max_size: int = 0, - *, - mode: str, - buffering: int = -1, - encoding: str | None = None, - newline: str | None = None, - suffix: str | None = None, - prefix: str | None = None, - dir: str | None = None, - errors: str | None = None, - ) -> None: ... - @property - def errors(self) -> str | None: ... - else: - @overload - def __init__( - self: SpooledTemporaryFile[bytes], - max_size: int = 0, - mode: _BytesMode = "w+b", - buffering: int = -1, - encoding: str | None = None, - newline: str | None = None, - suffix: str | None = None, - prefix: str | None = None, - dir: str | None = None, - ) -> None: ... - @overload - def __init__( - self: SpooledTemporaryFile[str], - max_size: int, - mode: _StrMode, - buffering: int = -1, - encoding: str | None = None, - newline: str | None = None, - suffix: str | None = None, - prefix: str | None = None, - dir: str | None = None, - ) -> None: ... - @overload - def __init__( - self: SpooledTemporaryFile[str], - max_size: int = 0, - *, - mode: _StrMode, - buffering: int = -1, - encoding: str | None = None, - newline: str | None = None, - suffix: str | None = None, - prefix: str | None = None, - dir: str | None = None, - ) -> None: ... - @overload - def __init__( - self, - max_size: int, - mode: str, - buffering: int = -1, - encoding: str | None = None, - newline: str | None = None, - suffix: str | None = None, - prefix: str | None = None, - dir: str | None = None, - ) -> None: ... - @overload - def __init__( - self, - max_size: int = 0, - *, - mode: str, - buffering: int = -1, - encoding: str | None = None, - newline: str | None = None, - suffix: str | None = None, - prefix: str | None = None, - dir: str | None = None, - ) -> None: ... - + @overload + def __init__( + self: SpooledTemporaryFile[bytes], + max_size: int = 0, + mode: OpenBinaryMode = "w+b", + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + *, + errors: str | None = None, + ) -> None: ... + @overload + def __init__( + self: SpooledTemporaryFile[str], + max_size: int, + mode: OpenTextMode, + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + *, + errors: str | None = None, + ) -> None: ... + @overload + def __init__( + self: SpooledTemporaryFile[str], + max_size: int = 0, + *, + mode: OpenTextMode, + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + errors: str | None = None, + ) -> None: ... + @overload + def __init__( + self, + max_size: int, + mode: str, + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + *, + errors: str | None = None, + ) -> None: ... + @overload + def __init__( + self, + max_size: int = 0, + *, + mode: str, + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + errors: str | None = None, + ) -> None: ... + @property + def errors(self) -> str | None: ... def rollover(self) -> None: ... def __enter__(self) -> Self: ... def __exit__(self, exc: type[BaseException] | None, value: BaseException | None, tb: TracebackType | None) -> None: ... @@ -381,19 +364,33 @@ class SpooledTemporaryFile(IO[AnyStr], _SpooledTemporaryFileBase): if sys.version_info >= (3, 11): # These three work only if the SpooledTemporaryFile is opened in binary mode, # because the underlying object in text mode does not have these methods. - def read1(self, __size: int = ...) -> AnyStr: ... + def read1(self, size: int = ..., /) -> AnyStr: ... def readinto(self, b: WriteableBuffer) -> int: ... def readinto1(self, b: WriteableBuffer) -> int: ... def detach(self) -> io.RawIOBase: ... - def read(self, __n: int = ...) -> AnyStr: ... - def readline(self, __limit: int | None = ...) -> AnyStr: ... # type: ignore[override] - def readlines(self, __hint: int = ...) -> list[AnyStr]: ... # type: ignore[override] + def read(self, n: int = ..., /) -> AnyStr: ... + def readline(self, limit: int | None = ..., /) -> AnyStr: ... # type: ignore[override] + def readlines(self, hint: int = ..., /) -> list[AnyStr]: ... # type: ignore[override] def seek(self, offset: int, whence: int = ...) -> int: ... def tell(self) -> int: ... - def truncate(self, size: int | None = None) -> None: ... # type: ignore[override] + if sys.version_info >= (3, 11): + def truncate(self, size: int | None = None) -> int: ... + else: + def truncate(self, size: int | None = None) -> None: ... # type: ignore[override] + + @overload + def write(self: SpooledTemporaryFile[str], s: str) -> int: ... + @overload + def write(self: SpooledTemporaryFile[bytes], s: ReadableBuffer) -> int: ... + @overload def write(self, s: AnyStr) -> int: ... - def writelines(self, iterable: Iterable[AnyStr]) -> None: ... # type: ignore[override] + @overload + def writelines(self: SpooledTemporaryFile[str], iterable: Iterable[str]) -> None: ... + @overload + def writelines(self: SpooledTemporaryFile[bytes], iterable: Iterable[ReadableBuffer]) -> None: ... + @overload + def writelines(self, iterable: Iterable[AnyStr]) -> None: ... def __iter__(self) -> Iterator[AnyStr]: ... # type: ignore[override] # These exist at runtime only on 3.11+. def readable(self) -> bool: ... @@ -405,7 +402,28 @@ class SpooledTemporaryFile(IO[AnyStr], _SpooledTemporaryFileBase): class TemporaryDirectory(Generic[AnyStr]): name: AnyStr - if sys.version_info >= (3, 10): + if sys.version_info >= (3, 12): + @overload + def __init__( + self: TemporaryDirectory[str], + suffix: str | None = None, + prefix: str | None = None, + dir: StrPath | None = None, + ignore_cleanup_errors: bool = False, + *, + delete: bool = True, + ) -> None: ... + @overload + def __init__( + self: TemporaryDirectory[bytes], + suffix: bytes | None = None, + prefix: bytes | None = None, + dir: BytesPath | None = None, + ignore_cleanup_errors: bool = False, + *, + delete: bool = True, + ) -> None: ... + elif sys.version_info >= (3, 10): @overload def __init__( self: TemporaryDirectory[str], @@ -443,7 +461,7 @@ class TemporaryDirectory(Generic[AnyStr]): # The overloads overlap, but they should still work fine. @overload -def mkstemp( # type: ignore[misc] +def mkstemp( # type: ignore[overload-overlap] suffix: str | None = None, prefix: str | None = None, dir: StrPath | None = None, text: bool = False ) -> tuple[int, str]: ... @overload @@ -453,7 +471,7 @@ def mkstemp( # The overloads overlap, but they should still work fine. @overload -def mkdtemp(suffix: str | None = None, prefix: str | None = None, dir: StrPath | None = None) -> str: ... # type: ignore[misc] +def mkdtemp(suffix: str | None = None, prefix: str | None = None, dir: StrPath | None = None) -> str: ... # type: ignore[overload-overlap] @overload def mkdtemp(suffix: bytes | None = None, prefix: bytes | None = None, dir: BytesPath | None = None) -> bytes: ... def mktemp(suffix: str = "", prefix: str = "tmp", dir: StrPath | None = None) -> str: ... diff --git a/mypy/typeshed/stdlib/termios.pyi b/mypy/typeshed/stdlib/termios.pyi index bf8d7bee2473..a5378e40fdf2 100644 --- a/mypy/typeshed/stdlib/termios.pyi +++ b/mypy/typeshed/stdlib/termios.pyi @@ -3,10 +3,12 @@ from _typeshed import FileDescriptorLike from typing import Any from typing_extensions import TypeAlias -if sys.platform != "win32": - # Must be a list of length 7, containing 6 ints and a list of NCCS 1-character bytes or ints. - _Attr: TypeAlias = list[int | list[bytes | int]] +# Must be a list of length 7, containing 6 ints and a list of NCCS 1-character bytes or ints. +_Attr: TypeAlias = list[int | list[bytes | int]] | list[int | list[bytes]] | list[int | list[int]] +# Same as _Attr for return types; we use Any to avoid a union. +_AttrReturn: TypeAlias = list[Any] +if sys.platform != "win32": B0: int B1000000: int B110: int @@ -252,14 +254,14 @@ if sys.platform != "win32": XCASE: int XTABS: int - def tcgetattr(__fd: FileDescriptorLike) -> list[Any]: ... # Returns _Attr; we use Any to avoid a union in the return type - def tcsetattr(__fd: FileDescriptorLike, __when: int, __attributes: _Attr) -> None: ... - def tcsendbreak(__fd: FileDescriptorLike, __duration: int) -> None: ... - def tcdrain(__fd: FileDescriptorLike) -> None: ... - def tcflush(__fd: FileDescriptorLike, __queue: int) -> None: ... - def tcflow(__fd: FileDescriptorLike, __action: int) -> None: ... + def tcgetattr(fd: FileDescriptorLike, /) -> _AttrReturn: ... + def tcsetattr(fd: FileDescriptorLike, when: int, attributes: _Attr, /) -> None: ... + def tcsendbreak(fd: FileDescriptorLike, duration: int, /) -> None: ... + def tcdrain(fd: FileDescriptorLike, /) -> None: ... + def tcflush(fd: FileDescriptorLike, queue: int, /) -> None: ... + def tcflow(fd: FileDescriptorLike, action: int, /) -> None: ... if sys.version_info >= (3, 11): - def tcgetwinsize(__fd: FileDescriptorLike) -> tuple[int, int]: ... - def tcsetwinsize(__fd: FileDescriptorLike, __winsize: tuple[int, int]) -> None: ... + def tcgetwinsize(fd: FileDescriptorLike, /) -> tuple[int, int]: ... + def tcsetwinsize(fd: FileDescriptorLike, winsize: tuple[int, int], /) -> None: ... class error(Exception): ... diff --git a/mypy/typeshed/stdlib/textwrap.pyi b/mypy/typeshed/stdlib/textwrap.pyi index e4a5b7899e8e..c00cce3c2d57 100644 --- a/mypy/typeshed/stdlib/textwrap.pyi +++ b/mypy/typeshed/stdlib/textwrap.pyi @@ -55,49 +55,49 @@ def wrap( text: str, width: int = 70, *, - initial_indent: str = ..., - subsequent_indent: str = ..., - expand_tabs: bool = ..., - tabsize: int = ..., - replace_whitespace: bool = ..., - fix_sentence_endings: bool = ..., - break_long_words: bool = ..., - break_on_hyphens: bool = ..., - drop_whitespace: bool = ..., - max_lines: int = ..., - placeholder: str = ..., + initial_indent: str = "", + subsequent_indent: str = "", + expand_tabs: bool = True, + tabsize: int = 8, + replace_whitespace: bool = True, + fix_sentence_endings: bool = False, + break_long_words: bool = True, + break_on_hyphens: bool = True, + drop_whitespace: bool = True, + max_lines: int | None = None, + placeholder: str = " [...]", ) -> list[str]: ... def fill( text: str, width: int = 70, *, - initial_indent: str = ..., - subsequent_indent: str = ..., - expand_tabs: bool = ..., - tabsize: int = ..., - replace_whitespace: bool = ..., - fix_sentence_endings: bool = ..., - break_long_words: bool = ..., - break_on_hyphens: bool = ..., - drop_whitespace: bool = ..., - max_lines: int = ..., - placeholder: str = ..., + initial_indent: str = "", + subsequent_indent: str = "", + expand_tabs: bool = True, + tabsize: int = 8, + replace_whitespace: bool = True, + fix_sentence_endings: bool = False, + break_long_words: bool = True, + break_on_hyphens: bool = True, + drop_whitespace: bool = True, + max_lines: int | None = None, + placeholder: str = " [...]", ) -> str: ... def shorten( text: str, width: int, *, - initial_indent: str = ..., - subsequent_indent: str = ..., - expand_tabs: bool = ..., - tabsize: int = ..., - replace_whitespace: bool = ..., - fix_sentence_endings: bool = ..., - break_long_words: bool = ..., - break_on_hyphens: bool = ..., - drop_whitespace: bool = ..., + initial_indent: str = "", + subsequent_indent: str = "", + expand_tabs: bool = True, + tabsize: int = 8, + replace_whitespace: bool = True, + fix_sentence_endings: bool = False, + break_long_words: bool = True, + break_on_hyphens: bool = True, + drop_whitespace: bool = True, # Omit `max_lines: int = None`, it is forced to 1 here. - placeholder: str = ..., + placeholder: str = " [...]", ) -> str: ... def dedent(text: str) -> str: ... def indent(text: str, prefix: str, predicate: Callable[[str], bool] | None = None) -> str: ... diff --git a/mypy/typeshed/stdlib/threading.pyi b/mypy/typeshed/stdlib/threading.pyi index c0b344fe757d..90b6cabb5237 100644 --- a/mypy/typeshed/stdlib/threading.pyi +++ b/mypy/typeshed/stdlib/threading.pyi @@ -1,8 +1,10 @@ +import _thread import sys +from _thread import _excepthook, _ExceptHookArgs, get_native_id as get_native_id from _typeshed import ProfileFunction, TraceFunction from collections.abc import Callable, Iterable, Mapping from types import TracebackType -from typing import Any, TypeVar +from typing import Any, TypeVar, final _T = TypeVar("_T") @@ -24,18 +26,21 @@ __all__ = [ "BrokenBarrierError", "Timer", "ThreadError", + "ExceptHookArgs", "setprofile", "settrace", "local", "stack_size", + "excepthook", + "get_native_id", ] -if sys.version_info >= (3, 8): - __all__ += ["ExceptHookArgs", "excepthook", "get_native_id"] - if sys.version_info >= (3, 10): __all__ += ["getprofile", "gettrace"] +if sys.version_info >= (3, 12): + __all__ += ["setprofile_all_threads", "settrace_all_threads"] + _profile_hook: ProfileFunction | None def active_count() -> int: ... @@ -45,13 +50,13 @@ def currentThread() -> Thread: ... # deprecated alias for current_thread() def get_ident() -> int: ... def enumerate() -> list[Thread]: ... def main_thread() -> Thread: ... - -if sys.version_info >= (3, 8): - from _thread import get_native_id as get_native_id - def settrace(func: TraceFunction) -> None: ... def setprofile(func: ProfileFunction | None) -> None: ... +if sys.version_info >= (3, 12): + def setprofile_all_threads(func: ProfileFunction | None) -> None: ... + def settrace_all_threads(func: TraceFunction) -> None: ... + if sys.version_info >= (3, 10): def gettrace() -> TraceFunction | None: ... def getprofile() -> ProfileFunction | None: ... @@ -60,12 +65,8 @@ def stack_size(size: int = ...) -> int: ... TIMEOUT_MAX: float -class ThreadError(Exception): ... - -class local: - def __getattribute__(self, __name: str) -> Any: ... - def __setattr__(self, __name: str, __value: Any) -> None: ... - def __delattr__(self, __name: str) -> None: ... +ThreadError = _thread.error +local = _thread._local class Thread: name: str @@ -77,7 +78,7 @@ class Thread: group: None = None, target: Callable[..., object] | None = None, name: str | None = None, - args: Iterable[Any] = ..., + args: Iterable[Any] = (), kwargs: Mapping[str, Any] | None = None, *, daemon: bool | None = None, @@ -85,10 +86,8 @@ class Thread: def start(self) -> None: ... def run(self) -> None: ... def join(self, timeout: float | None = None) -> None: ... - if sys.version_info >= (3, 8): - @property - def native_id(self) -> int | None: ... # only available on some platforms - + @property + def native_id(self) -> int | None: ... # only available on some platforms def is_alive(self) -> bool: ... if sys.version_info < (3, 9): def isAlive(self) -> bool: ... @@ -101,6 +100,7 @@ class Thread: class _DummyThread(Thread): def __init__(self) -> None: ... +@final class Lock: def __enter__(self) -> bool: ... def __exit__( @@ -110,6 +110,7 @@ class Lock: def release(self) -> None: ... def locked(self) -> bool: ... +@final class _RLock: def acquire(self, blocking: bool = True, timeout: float = -1) -> bool: ... def release(self) -> None: ... @@ -152,11 +153,8 @@ class Event: def clear(self) -> None: ... def wait(self, timeout: float | None = None) -> bool: ... -if sys.version_info >= (3, 8): - from _thread import _excepthook, _ExceptHookArgs - - excepthook = _excepthook - ExceptHookArgs = _ExceptHookArgs +excepthook = _excepthook +ExceptHookArgs = _ExceptHookArgs class Timer(Thread): args: Iterable[Any] # undocumented diff --git a/mypy/typeshed/stdlib/time.pyi b/mypy/typeshed/stdlib/time.pyi index 035d78934f3a..b7962f0751d6 100644 --- a/mypy/typeshed/stdlib/time.pyi +++ b/mypy/typeshed/stdlib/time.pyi @@ -1,7 +1,7 @@ import sys from _typeshed import structseq -from typing import Any, Protocol -from typing_extensions import Final, Literal, TypeAlias, final +from typing import Any, Final, Literal, Protocol, final +from typing_extensions import TypeAlias _TimeTuple: TypeAlias = tuple[int, int, int, int, int, int, int, int, int] @@ -25,7 +25,7 @@ if sys.platform != "win32": if sys.platform != "linux" and sys.platform != "darwin": CLOCK_HIGHRES: int # Solaris only -if sys.version_info >= (3, 8) and sys.platform == "darwin": +if sys.platform == "darwin": CLOCK_UPTIME_RAW: int if sys.version_info >= (3, 9) and sys.platform == "linux": @@ -39,6 +39,7 @@ if sys.version_info >= (3, 9) and sys.platform == "linux": class struct_time(structseq[Any | int], _TimeTuple): if sys.version_info >= (3, 10): __match_args__: Final = ("tm_year", "tm_mon", "tm_mday", "tm_hour", "tm_min", "tm_sec", "tm_wday", "tm_yday", "tm_isdst") + @property def tm_year(self) -> int: ... @property @@ -63,18 +64,14 @@ class struct_time(structseq[Any | int], _TimeTuple): @property def tm_gmtoff(self) -> int: ... -def asctime(t: _TimeTuple | struct_time = ...) -> str: ... - -if sys.version_info < (3, 8): - def clock() -> float: ... - -def ctime(secs: float | None = ...) -> str: ... -def gmtime(secs: float | None = ...) -> struct_time: ... -def localtime(secs: float | None = ...) -> struct_time: ... -def mktime(t: _TimeTuple | struct_time) -> float: ... -def sleep(secs: float) -> None: ... -def strftime(format: str, t: _TimeTuple | struct_time = ...) -> str: ... -def strptime(string: str, format: str = ...) -> struct_time: ... +def asctime(time_tuple: _TimeTuple | struct_time = ..., /) -> str: ... +def ctime(seconds: float | None = None, /) -> str: ... +def gmtime(seconds: float | None = None, /) -> struct_time: ... +def localtime(seconds: float | None = None, /) -> struct_time: ... +def mktime(time_tuple: _TimeTuple | struct_time, /) -> float: ... +def sleep(seconds: float, /) -> None: ... +def strftime(format: str, time_tuple: _TimeTuple | struct_time = ..., /) -> str: ... +def strptime(data_string: str, format: str = "%a %b %d %H:%M:%S %Y", /) -> struct_time: ... def time() -> float: ... if sys.platform != "win32": @@ -86,22 +83,22 @@ class _ClockInfo(Protocol): monotonic: bool resolution: float -def get_clock_info(name: Literal["monotonic", "perf_counter", "process_time", "time", "thread_time"]) -> _ClockInfo: ... +def get_clock_info(name: Literal["monotonic", "perf_counter", "process_time", "time", "thread_time"], /) -> _ClockInfo: ... def monotonic() -> float: ... def perf_counter() -> float: ... def process_time() -> float: ... if sys.platform != "win32": - def clock_getres(clk_id: int) -> float: ... # Unix only - def clock_gettime(clk_id: int) -> float: ... # Unix only - def clock_settime(clk_id: int, time: float) -> None: ... # Unix only + def clock_getres(clk_id: int, /) -> float: ... # Unix only + def clock_gettime(clk_id: int, /) -> float: ... # Unix only + def clock_settime(clk_id: int, time: float, /) -> None: ... # Unix only if sys.platform != "win32": - def clock_gettime_ns(clock_id: int) -> int: ... - def clock_settime_ns(clock_id: int, time: int) -> int: ... + def clock_gettime_ns(clock_id: int, /) -> int: ... + def clock_settime_ns(clock_id: int, time: int, /) -> int: ... if sys.platform == "linux": - def pthread_getcpuclockid(thread_id: int) -> int: ... + def pthread_getcpuclockid(thread_id: int, /) -> int: ... def monotonic_ns() -> int: ... def perf_counter_ns() -> int: ... diff --git a/mypy/typeshed/stdlib/tkinter/__init__.pyi b/mypy/typeshed/stdlib/tkinter/__init__.pyi index 1d30e4b73c23..d8ce17535eab 100644 --- a/mypy/typeshed/stdlib/tkinter/__init__.pyi +++ b/mypy/typeshed/stdlib/tkinter/__init__.pyi @@ -1,13 +1,12 @@ import _tkinter import sys -from _typeshed import Incomplete, StrOrBytesPath +from _typeshed import Incomplete, StrEnum, StrOrBytesPath from collections.abc import Callable, Mapping, Sequence -from enum import Enum from tkinter.constants import * from tkinter.font import _FontDescription from types import TracebackType -from typing import Any, Generic, NamedTuple, Protocol, TypeVar, overload -from typing_extensions import Literal, TypeAlias, TypedDict +from typing import Any, Generic, Literal, NamedTuple, TypedDict, TypeVar, overload, type_check_only +from typing_extensions import TypeAlias, TypeVarTuple, Unpack, deprecated if sys.version_info >= (3, 9): __all__ = [ @@ -173,22 +172,18 @@ EXCEPTION = _tkinter.EXCEPTION # Some widgets have an option named -compound that accepts different values # than the _Compound defined here. Many other options have similar things. _Anchor: TypeAlias = Literal["nw", "n", "ne", "w", "center", "e", "sw", "s", "se"] # manual page: Tk_GetAnchor -_Bitmap: TypeAlias = str # manual page: Tk_GetBitmap _ButtonCommand: TypeAlias = str | Callable[[], Any] # accepts string of tcl code, return value is returned from Button.invoke() -_CanvasItemId: TypeAlias = int -_Color: TypeAlias = str # typically '#rrggbb', '#rgb' or color names. _Compound: TypeAlias = Literal["top", "left", "center", "right", "bottom", "none"] # -compound in manual page named 'options' # manual page: Tk_GetCursor _Cursor: TypeAlias = str | tuple[str] | tuple[str, str] | tuple[str, str, str] | tuple[str, str, str, str] # example when it's sequence: entry['invalidcommand'] = [entry.register(print), '%P'] _EntryValidateCommand: TypeAlias = str | list[str] | tuple[str, ...] | Callable[[], bool] -_GridIndex: TypeAlias = int | str _ImageSpec: TypeAlias = _Image | str # str can be from e.g. tkinter.image_names() _Relief: TypeAlias = Literal["raised", "sunken", "flat", "ridge", "solid", "groove"] # manual page: Tk_GetRelief _ScreenUnits: TypeAlias = str | float # Often the right type instead of int. Manual page: Tk_GetPixels # -xscrollcommand and -yscrollcommand in 'options' manual page _XYScrollCommand: TypeAlias = str | Callable[[float, float], object] -_TakeFocusValue: TypeAlias = int | Literal[""] | Callable[[str], bool | None] # -takefocus in manual page named 'options' +_TakeFocusValue: TypeAlias = bool | Literal[0, 1, ""] | Callable[[str], bool | None] # -takefocus in manual page named 'options' if sys.version_info >= (3, 11): class _VersionInfoType(NamedTuple): @@ -198,46 +193,46 @@ if sys.version_info >= (3, 11): releaselevel: str serial: int -class EventType(str, Enum): - Activate: str - ButtonPress: str +class EventType(StrEnum): + Activate = "36" + ButtonPress = "4" Button = ButtonPress - ButtonRelease: str - Circulate: str - CirculateRequest: str - ClientMessage: str - Colormap: str - Configure: str - ConfigureRequest: str - Create: str - Deactivate: str - Destroy: str - Enter: str - Expose: str - FocusIn: str - FocusOut: str - GraphicsExpose: str - Gravity: str - KeyPress: str - Key = KeyPress - KeyRelease: str - Keymap: str - Leave: str - Map: str - MapRequest: str - Mapping: str - Motion: str - MouseWheel: str - NoExpose: str - Property: str - Reparent: str - ResizeRequest: str - Selection: str - SelectionClear: str - SelectionRequest: str - Unmap: str - VirtualEvent: str - Visibility: str + ButtonRelease = "5" + Circulate = "26" + CirculateRequest = "27" + ClientMessage = "33" + Colormap = "32" + Configure = "22" + ConfigureRequest = "23" + Create = "16" + Deactivate = "37" + Destroy = "17" + Enter = "7" + Expose = "12" + FocusIn = "9" + FocusOut = "10" + GraphicsExpose = "13" + Gravity = "24" + KeyPress = "2" + Key = "2" + KeyRelease = "3" + Keymap = "11" + Leave = "8" + Map = "19" + MapRequest = "20" + Mapping = "34" + Motion = "6" + MouseWheel = "38" + NoExpose = "14" + Property = "28" + Reparent = "21" + ResizeRequest = "25" + Selection = "31" + SelectionClear = "29" + SelectionRequest = "30" + Unmap = "18" + VirtualEvent = "35" + Visibility = "15" _W = TypeVar("_W", bound=Misc) # Events considered covariant because you should never assign to event.widget. @@ -266,21 +261,24 @@ class Event(Generic[_W_co]): def NoDefaultRoot() -> None: ... -_TraceMode: TypeAlias = Literal["array", "read", "write", "unset"] - class Variable: def __init__(self, master: Misc | None = None, value: Incomplete | None = None, name: str | None = None) -> None: ... def set(self, value) -> None: ... initialize = set def get(self): ... - def trace_add(self, mode: _TraceMode, callback: Callable[[str, str, str], object]) -> str: ... - def trace_remove(self, mode: _TraceMode, cbname: str) -> None: ... - def trace_info(self) -> list[tuple[tuple[_TraceMode, ...], str]]: ... - def trace_variable(self, mode, callback): ... # deprecated - def trace_vdelete(self, mode, cbname) -> None: ... # deprecated - def trace_vinfo(self): ... # deprecated - trace = trace_variable # deprecated + def trace_add(self, mode: Literal["array", "read", "write", "unset"], callback: Callable[[str, str, str], object]) -> str: ... + def trace_remove(self, mode: Literal["array", "read", "write", "unset"], cbname: str) -> None: ... + def trace_info(self) -> list[tuple[tuple[Literal["array", "read", "write", "unset"], ...], str]]: ... + @deprecated("use trace_add() instead of trace()") + def trace(self, mode, callback): ... + @deprecated("use trace_add() instead of trace_variable()") + def trace_variable(self, mode, callback): ... + @deprecated("use trace_remove() instead of trace_vdelete()") + def trace_vdelete(self, mode, cbname) -> None: ... + @deprecated("use trace_info() instead of trace_vinfo()") + def trace_vinfo(self): ... def __eq__(self, other: object) -> bool: ... + def __del__(self) -> None: ... class StringVar(Variable): def __init__(self, master: Misc | None = None, value: str | None = None, name: str | None = None) -> None: ... @@ -313,6 +311,8 @@ getdouble: Incomplete def getboolean(s): ... +_Ts = TypeVarTuple("_Ts") + class _GridIndexInfo(TypedDict, total=False): minsize: _ScreenUnits pad: _ScreenUnits @@ -346,12 +346,11 @@ class Misc: def tk_focusFollowsMouse(self) -> None: ... def tk_focusNext(self) -> Misc | None: ... def tk_focusPrev(self) -> Misc | None: ... - @overload - def after(self, ms: int, func: None = None) -> None: ... - @overload - def after(self, ms: int | Literal["idle"], func: Callable[..., object], *args: Any) -> str: ... + # .after() can be called without the "func" argument, but it is basically never what you want. + # It behaves like time.sleep() and freezes the GUI app. + def after(self, ms: int | Literal["idle"], func: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts]) -> str: ... # after_idle is essentially partialmethod(after, "idle") - def after_idle(self, func: Callable[..., object], *args: Any) -> str: ... + def after_idle(self, func: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts]) -> str: ... def after_cancel(self, id: str) -> None: ... def bell(self, displayof: Literal[0] | Misc | None = 0) -> None: ... def clipboard_get(self, *, displayof: Misc = ..., type: str = ...) -> str: ... @@ -405,7 +404,7 @@ class Misc: def winfo_pointery(self) -> int: ... def winfo_reqheight(self) -> int: ... def winfo_reqwidth(self) -> int: ... - def winfo_rgb(self, color: _Color) -> tuple[int, int, int]: ... + def winfo_rgb(self, color: str) -> tuple[int, int, int]: ... def winfo_rootx(self) -> int: ... def winfo_rooty(self) -> int: ... def winfo_screen(self) -> str: ... @@ -503,8 +502,8 @@ class Misc: bbox = grid_bbox def grid_columnconfigure( self, - index: _GridIndex, - cnf: _GridIndexInfo = ..., + index: int | str | list[int] | tuple[int, ...], + cnf: _GridIndexInfo = {}, *, minsize: _ScreenUnits = ..., pad: _ScreenUnits = ..., @@ -513,8 +512,8 @@ class Misc: ) -> _GridIndexInfo | Any: ... # can be None but annoying to check def grid_rowconfigure( self, - index: _GridIndex, - cnf: _GridIndexInfo = ..., + index: int | str | list[int] | tuple[int, ...], + cnf: _GridIndexInfo = {}, *, minsize: _ScreenUnits = ..., pad: _ScreenUnits = ..., @@ -619,18 +618,18 @@ class Wm: @overload def wm_attributes(self) -> tuple[Any, ...]: ... @overload - def wm_attributes(self, __option: str): ... + def wm_attributes(self, option: str, /): ... @overload - def wm_attributes(self, __option: str, __value, *__other_option_value_pairs: Any) -> None: ... + def wm_attributes(self, option: str, value, /, *__other_option_value_pairs: Any) -> None: ... attributes = wm_attributes def wm_client(self, name: str | None = None) -> str: ... client = wm_client @overload def wm_colormapwindows(self) -> list[Misc]: ... @overload - def wm_colormapwindows(self, __wlist: list[Misc] | tuple[Misc, ...]) -> None: ... + def wm_colormapwindows(self, wlist: list[Misc] | tuple[Misc, ...], /) -> None: ... @overload - def wm_colormapwindows(self, __first_wlist_item: Misc, *other_wlist_items: Misc) -> None: ... + def wm_colormapwindows(self, first_wlist_item: Misc, /, *other_wlist_items: Misc) -> None: ... colormapwindows = wm_colormapwindows def wm_command(self, value: str | None = None) -> str: ... command = wm_command @@ -666,7 +665,7 @@ class Wm: iconmask = wm_iconmask def wm_iconname(self, newName: Incomplete | None = None) -> str: ... iconname = wm_iconname - def wm_iconphoto(self, default: bool, __image1: Image, *args: Image) -> None: ... + def wm_iconphoto(self, default: bool, image1: _PhotoImageLike | str, /, *args: _PhotoImageLike | str) -> None: ... iconphoto = wm_iconphoto def wm_iconposition(self, x: int | None = None, y: int | None = None) -> tuple[int, int] | None: ... iconposition = wm_iconposition @@ -723,9 +722,6 @@ class Wm: def wm_withdraw(self) -> None: ... withdraw = wm_withdraw -class _ExceptionReportingCallback(Protocol): - def __call__(self, __exc: type[BaseException], __val: BaseException, __tb: TracebackType | None) -> object: ... - class Tk(Misc, Wm): master: None def __init__( @@ -745,15 +741,15 @@ class Tk(Misc, Wm): self, cnf: dict[str, Any] | None = None, *, - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., cursor: _Cursor = ..., height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., menu: Menu = ..., padx: _ScreenUnits = ..., @@ -767,35 +763,35 @@ class Tk(Misc, Wm): config = configure def destroy(self) -> None: ... def readprofile(self, baseName: str, className: str) -> None: ... - report_callback_exception: _ExceptionReportingCallback + report_callback_exception: Callable[[type[BaseException], BaseException, TracebackType | None], object] # Tk has __getattr__ so that tk_instance.foo falls back to tk_instance.tk.foo # Please keep in sync with _tkinter.TkappType. # Some methods are intentionally missing because they are inherited from Misc instead. - def adderrorinfo(self, __msg): ... - def call(self, __command: Any, *args: Any) -> Any: ... - def createcommand(self, __name, __func): ... + def adderrorinfo(self, msg, /): ... + def call(self, command: Any, /, *args: Any) -> Any: ... + def createcommand(self, name, func, /): ... if sys.platform != "win32": - def createfilehandler(self, __file, __mask, __func): ... - def deletefilehandler(self, __file): ... - - def createtimerhandler(self, __milliseconds, __func): ... - def dooneevent(self, __flags: int = ...): ... - def eval(self, __script: str) -> str: ... - def evalfile(self, __fileName): ... - def exprboolean(self, __s): ... - def exprdouble(self, __s): ... - def exprlong(self, __s): ... - def exprstring(self, __s): ... + def createfilehandler(self, file, mask, func, /): ... + def deletefilehandler(self, file, /): ... + + def createtimerhandler(self, milliseconds, func, /): ... + def dooneevent(self, flags: int = ..., /): ... + def eval(self, script: str, /) -> str: ... + def evalfile(self, fileName, /): ... + def exprboolean(self, s, /): ... + def exprdouble(self, s, /): ... + def exprlong(self, s, /): ... + def exprstring(self, s, /): ... def globalgetvar(self, *args, **kwargs): ... def globalsetvar(self, *args, **kwargs): ... def globalunsetvar(self, *args, **kwargs): ... def interpaddr(self): ... def loadtk(self) -> None: ... - def record(self, __script): ... + def record(self, script, /): ... if sys.version_info < (3, 11): - def split(self, __arg): ... + def split(self, arg, /): ... - def splitlist(self, __arg): ... + def splitlist(self, arg, /): ... def unsetvar(self, *args, **kwargs): ... def wantobjects(self, *args, **kwargs): ... def willdispatch(self): ... @@ -825,12 +821,12 @@ class Pack: # replaced by **kwargs. def pack_configure( self, - cnf: Mapping[str, Any] | None = ..., + cnf: Mapping[str, Any] | None = {}, *, after: Misc = ..., anchor: _Anchor = ..., before: Misc = ..., - expand: int = ..., + expand: bool | Literal[0, 1] = 0, fill: Literal["none", "x", "y", "both"] = ..., side: Literal["left", "right", "top", "bottom"] = ..., ipadx: _ScreenUnits = ..., @@ -861,7 +857,7 @@ class _PlaceInfo(_InMiscNonTotal): # empty dict if widget hasn't been placed class Place: def place_configure( self, - cnf: Mapping[str, Any] | None = ..., + cnf: Mapping[str, Any] | None = {}, *, anchor: _Anchor = ..., bordermode: Literal["inside", "outside", "ignore"] = ..., @@ -896,7 +892,7 @@ class _GridInfo(_InMiscNonTotal): # empty dict if widget hasn't been gridded class Grid: def grid_configure( self, - cnf: Mapping[str, Any] | None = ..., + cnf: Mapping[str, Any] | None = {}, *, column: int = ..., columnspan: int = ..., @@ -920,7 +916,7 @@ class Grid: class BaseWidget(Misc): master: Misc widgetName: Incomplete - def __init__(self, master, widgetName, cnf=..., kw=..., extra=...) -> None: ... + def __init__(self, master, widgetName, cnf={}, kw={}, extra=()) -> None: ... def destroy(self) -> None: ... # This class represents any widget except Toplevel or Tk. @@ -947,46 +943,46 @@ class Toplevel(BaseWidget, Wm): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., - *, - background: _Color = ..., - bd: _ScreenUnits = ..., - bg: _Color = ..., - border: _ScreenUnits = ..., - borderwidth: _ScreenUnits = ..., - class_: str = ..., - colormap: Literal["new", ""] | Misc = ..., - container: bool = ..., - cursor: _Cursor = ..., - height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., - highlightthickness: _ScreenUnits = ..., + cnf: dict[str, Any] | None = {}, + *, + background: str = ..., + bd: _ScreenUnits = 0, + bg: str = ..., + border: _ScreenUnits = 0, + borderwidth: _ScreenUnits = 0, + class_: str = "Toplevel", + colormap: Literal["new", ""] | Misc = "", + container: bool = False, + cursor: _Cursor = "", + height: _ScreenUnits = 0, + highlightbackground: str = ..., + highlightcolor: str = ..., + highlightthickness: _ScreenUnits = 0, menu: Menu = ..., name: str = ..., - padx: _ScreenUnits = ..., - pady: _ScreenUnits = ..., - relief: _Relief = ..., - screen: str = ..., # can't be changed after creating widget - takefocus: _TakeFocusValue = ..., + padx: _ScreenUnits = 0, + pady: _ScreenUnits = 0, + relief: _Relief = "flat", + screen: str = "", # can't be changed after creating widget + takefocus: _TakeFocusValue = 0, use: int = ..., - visual: str | tuple[str, int] = ..., - width: _ScreenUnits = ..., + visual: str | tuple[str, int] = "", + width: _ScreenUnits = 0, ) -> None: ... @overload def configure( self, cnf: dict[str, Any] | None = None, *, - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., cursor: _Cursor = ..., height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., menu: Menu = ..., padx: _ScreenUnits = ..., @@ -1003,80 +999,80 @@ class Button(Widget): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, - activebackground: _Color = ..., - activeforeground: _Color = ..., - anchor: _Anchor = ..., - background: _Color = ..., + activebackground: str = ..., + activeforeground: str = ..., + anchor: _Anchor = "center", + background: str = ..., bd: _ScreenUnits = ..., # same as borderwidth - bg: _Color = ..., # same as background - bitmap: _Bitmap = ..., + bg: str = ..., # same as background + bitmap: str = "", border: _ScreenUnits = ..., # same as borderwidth borderwidth: _ScreenUnits = ..., - command: _ButtonCommand = ..., - compound: _Compound = ..., - cursor: _Cursor = ..., - default: Literal["normal", "active", "disabled"] = ..., - disabledforeground: _Color = ..., - fg: _Color = ..., # same as foreground - font: _FontDescription = ..., - foreground: _Color = ..., + command: _ButtonCommand = "", + compound: _Compound = "none", + cursor: _Cursor = "", + default: Literal["normal", "active", "disabled"] = "disabled", + disabledforeground: str = ..., + fg: str = ..., # same as foreground + font: _FontDescription = "TkDefaultFont", + foreground: str = ..., # width and height must be int for buttons containing just text, but # ints are also valid _ScreenUnits - height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., - highlightthickness: _ScreenUnits = ..., - image: _ImageSpec = ..., - justify: Literal["left", "center", "right"] = ..., + height: _ScreenUnits = 0, + highlightbackground: str = ..., + highlightcolor: str = ..., + highlightthickness: _ScreenUnits = 1, + image: _ImageSpec = "", + justify: Literal["left", "center", "right"] = "center", name: str = ..., - overrelief: _Relief = ..., + overrelief: _Relief | Literal[""] = "", padx: _ScreenUnits = ..., pady: _ScreenUnits = ..., relief: _Relief = ..., repeatdelay: int = ..., repeatinterval: int = ..., - state: Literal["normal", "active", "disabled"] = ..., - takefocus: _TakeFocusValue = ..., - text: float | str = ..., + state: Literal["normal", "active", "disabled"] = "normal", + takefocus: _TakeFocusValue = "", + text: float | str = "", # We allow the textvariable to be any Variable, not necessarily # StringVar. This is useful for e.g. a button that displays the value # of an IntVar. textvariable: Variable = ..., - underline: int = ..., - width: _ScreenUnits = ..., - wraplength: _ScreenUnits = ..., + underline: int = -1, + width: _ScreenUnits = 0, + wraplength: _ScreenUnits = 0, ) -> None: ... @overload def configure( self, cnf: dict[str, Any] | None = None, *, - activebackground: _Color = ..., - activeforeground: _Color = ..., + activebackground: str = ..., + activeforeground: str = ..., anchor: _Anchor = ..., - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., - bitmap: _Bitmap = ..., + bg: str = ..., + bitmap: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., command: _ButtonCommand = ..., compound: _Compound = ..., cursor: _Cursor = ..., default: Literal["normal", "active", "disabled"] = ..., - disabledforeground: _Color = ..., - fg: _Color = ..., + disabledforeground: str = ..., + fg: str = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., image: _ImageSpec = ..., justify: Literal["left", "center", "right"] = ..., - overrelief: _Relief = ..., + overrelief: _Relief | Literal[""] = ..., padx: _ScreenUnits = ..., pady: _ScreenUnits = ..., relief: _Relief = ..., @@ -1100,63 +1096,63 @@ class Canvas(Widget, XView, YView): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., - *, - background: _Color = ..., - bd: _ScreenUnits = ..., - bg: _Color = ..., - border: _ScreenUnits = ..., - borderwidth: _ScreenUnits = ..., - closeenough: float = ..., - confine: bool = ..., - cursor: _Cursor = ..., + cnf: dict[str, Any] | None = {}, + *, + background: str = ..., + bd: _ScreenUnits = 0, + bg: str = ..., + border: _ScreenUnits = 0, + borderwidth: _ScreenUnits = 0, + closeenough: float = 1.0, + confine: bool = True, + cursor: _Cursor = "", # canvas manual page has a section named COORDINATES, and the first # part of it describes _ScreenUnits. height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., - insertbackground: _Color = ..., - insertborderwidth: _ScreenUnits = ..., - insertofftime: int = ..., - insertontime: int = ..., - insertwidth: _ScreenUnits = ..., + insertbackground: str = ..., + insertborderwidth: _ScreenUnits = 0, + insertofftime: int = 300, + insertontime: int = 600, + insertwidth: _ScreenUnits = 2, name: str = ..., offset=..., # undocumented - relief: _Relief = ..., + relief: _Relief = "flat", # Setting scrollregion to None doesn't reset it back to empty, # but setting it to () does. - scrollregion: tuple[_ScreenUnits, _ScreenUnits, _ScreenUnits, _ScreenUnits] | tuple[()] = ..., - selectbackground: _Color = ..., - selectborderwidth: _ScreenUnits = ..., - selectforeground: _Color = ..., + scrollregion: tuple[_ScreenUnits, _ScreenUnits, _ScreenUnits, _ScreenUnits] | tuple[()] = (), + selectbackground: str = ..., + selectborderwidth: _ScreenUnits = 1, + selectforeground: str = ..., # man page says that state can be 'hidden', but it can't - state: Literal["normal", "disabled"] = ..., - takefocus: _TakeFocusValue = ..., + state: Literal["normal", "disabled"] = "normal", + takefocus: _TakeFocusValue = "", width: _ScreenUnits = ..., - xscrollcommand: _XYScrollCommand = ..., - xscrollincrement: _ScreenUnits = ..., - yscrollcommand: _XYScrollCommand = ..., - yscrollincrement: _ScreenUnits = ..., + xscrollcommand: _XYScrollCommand = "", + xscrollincrement: _ScreenUnits = 0, + yscrollcommand: _XYScrollCommand = "", + yscrollincrement: _ScreenUnits = 0, ) -> None: ... @overload def configure( self, cnf: dict[str, Any] | None = None, *, - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., closeenough: float = ..., confine: bool = ..., cursor: _Cursor = ..., height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., - insertbackground: _Color = ..., + insertbackground: str = ..., insertborderwidth: _ScreenUnits = ..., insertofftime: int = ..., insertontime: int = ..., @@ -1164,9 +1160,9 @@ class Canvas(Widget, XView, YView): offset=..., # undocumented relief: _Relief = ..., scrollregion: tuple[_ScreenUnits, _ScreenUnits, _ScreenUnits, _ScreenUnits] | tuple[()] = ..., - selectbackground: _Color = ..., + selectbackground: str = ..., selectborderwidth: _ScreenUnits = ..., - selectforeground: _Color = ..., + selectforeground: str = ..., state: Literal["normal", "disabled"] = ..., takefocus: _TakeFocusValue = ..., width: _ScreenUnits = ..., @@ -1179,38 +1175,31 @@ class Canvas(Widget, XView, YView): def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... config = configure def addtag(self, *args): ... # internal method - def addtag_above(self, newtag: str, tagOrId: str | _CanvasItemId) -> None: ... + def addtag_above(self, newtag: str, tagOrId: str | int) -> None: ... def addtag_all(self, newtag: str) -> None: ... - def addtag_below(self, newtag: str, tagOrId: str | _CanvasItemId) -> None: ... + def addtag_below(self, newtag: str, tagOrId: str | int) -> None: ... def addtag_closest( - self, - newtag: str, - x: _ScreenUnits, - y: _ScreenUnits, - halo: _ScreenUnits | None = None, - start: str | _CanvasItemId | None = None, + self, newtag: str, x: _ScreenUnits, y: _ScreenUnits, halo: _ScreenUnits | None = None, start: str | int | None = None ) -> None: ... def addtag_enclosed(self, newtag: str, x1: _ScreenUnits, y1: _ScreenUnits, x2: _ScreenUnits, y2: _ScreenUnits) -> None: ... def addtag_overlapping(self, newtag: str, x1: _ScreenUnits, y1: _ScreenUnits, x2: _ScreenUnits, y2: _ScreenUnits) -> None: ... - def addtag_withtag(self, newtag: str, tagOrId: str | _CanvasItemId) -> None: ... + def addtag_withtag(self, newtag: str, tagOrId: str | int) -> None: ... def find(self, *args): ... # internal method - def find_above(self, tagOrId: str | _CanvasItemId) -> tuple[_CanvasItemId, ...]: ... - def find_all(self) -> tuple[_CanvasItemId, ...]: ... - def find_below(self, tagOrId: str | _CanvasItemId) -> tuple[_CanvasItemId, ...]: ... + def find_above(self, tagOrId: str | int) -> tuple[int, ...]: ... + def find_all(self) -> tuple[int, ...]: ... + def find_below(self, tagOrId: str | int) -> tuple[int, ...]: ... def find_closest( - self, x: _ScreenUnits, y: _ScreenUnits, halo: _ScreenUnits | None = None, start: str | _CanvasItemId | None = None - ) -> tuple[_CanvasItemId, ...]: ... - def find_enclosed( - self, x1: _ScreenUnits, y1: _ScreenUnits, x2: _ScreenUnits, y2: _ScreenUnits - ) -> tuple[_CanvasItemId, ...]: ... - def find_overlapping(self, x1: _ScreenUnits, y1: _ScreenUnits, x2: _ScreenUnits, y2: float) -> tuple[_CanvasItemId, ...]: ... - def find_withtag(self, tagOrId: str | _CanvasItemId) -> tuple[_CanvasItemId, ...]: ... + self, x: _ScreenUnits, y: _ScreenUnits, halo: _ScreenUnits | None = None, start: str | int | None = None + ) -> tuple[int, ...]: ... + def find_enclosed(self, x1: _ScreenUnits, y1: _ScreenUnits, x2: _ScreenUnits, y2: _ScreenUnits) -> tuple[int, ...]: ... + def find_overlapping(self, x1: _ScreenUnits, y1: _ScreenUnits, x2: _ScreenUnits, y2: float) -> tuple[int, ...]: ... + def find_withtag(self, tagOrId: str | int) -> tuple[int, ...]: ... # Incompatible with Misc.bbox(), tkinter violates LSP - def bbox(self, *args: str | _CanvasItemId) -> tuple[int, int, int, int]: ... # type: ignore[override] + def bbox(self, *args: str | int) -> tuple[int, int, int, int]: ... # type: ignore[override] @overload def tag_bind( self, - tagOrId: str | _CanvasItemId, + tagOrId: str | int, sequence: str | None = None, func: Callable[[Event[Canvas]], object] | None = None, add: Literal["", "+"] | bool | None = None, @@ -1220,86 +1209,88 @@ class Canvas(Widget, XView, YView): self, tagOrId: str | int, sequence: str | None, func: str, add: Literal["", "+"] | bool | None = None ) -> None: ... @overload - def tag_bind(self, tagOrId: str | _CanvasItemId, *, func: str, add: Literal["", "+"] | bool | None = None) -> None: ... - def tag_unbind(self, tagOrId: str | _CanvasItemId, sequence: str, funcid: str | None = None) -> None: ... + def tag_bind(self, tagOrId: str | int, *, func: str, add: Literal["", "+"] | bool | None = None) -> None: ... + def tag_unbind(self, tagOrId: str | int, sequence: str, funcid: str | None = None) -> None: ... def canvasx(self, screenx, gridspacing: Incomplete | None = None): ... def canvasy(self, screeny, gridspacing: Incomplete | None = None): ... @overload - def coords(self, __tagOrId: str | _CanvasItemId) -> list[float]: ... + def coords(self, tagOrId: str | int, /) -> list[float]: ... @overload - def coords(self, __tagOrId: str | _CanvasItemId, __args: list[int] | list[float] | tuple[float, ...]) -> None: ... + def coords(self, tagOrId: str | int, args: list[int] | list[float] | tuple[float, ...], /) -> None: ... @overload - def coords(self, __tagOrId: str | _CanvasItemId, __x1: float, __y1: float, *args: float) -> None: ... + def coords(self, tagOrId: str | int, x1: float, y1: float, /, *args: float) -> None: ... # create_foo() methods accept coords as a list or tuple, or as separate arguments. # Lists and tuples can be flat as in [1, 2, 3, 4], or nested as in [(1, 2), (3, 4)]. # Keyword arguments should be the same in all overloads of each method. - def create_arc(self, *args, **kw) -> _CanvasItemId: ... - def create_bitmap(self, *args, **kw) -> _CanvasItemId: ... - def create_image(self, *args, **kw) -> _CanvasItemId: ... + def create_arc(self, *args, **kw) -> int: ... + def create_bitmap(self, *args, **kw) -> int: ... + def create_image(self, *args, **kw) -> int: ... @overload def create_line( self, - __x0: float, - __y0: float, - __x1: float, - __y1: float, + x0: float, + y0: float, + x1: float, + y1: float, + /, *, - activedash: str | list[int] | tuple[int, ...] = ..., - activefill: _Color = ..., + activedash: str | int | list[int] | tuple[int, ...] = ..., + activefill: str = ..., activestipple: str = ..., activewidth: _ScreenUnits = ..., arrow: Literal["first", "last", "both"] = ..., arrowshape: tuple[float, float, float] = ..., capstyle: Literal["round", "projecting", "butt"] = ..., - dash: str | list[int] | tuple[int, ...] = ..., + dash: str | int | list[int] | tuple[int, ...] = ..., dashoffset: _ScreenUnits = ..., - disableddash: str | list[int] | tuple[int, ...] = ..., - disabledfill: _Color = ..., - disabledstipple: _Bitmap = ..., + disableddash: str | int | list[int] | tuple[int, ...] = ..., + disabledfill: str = ..., + disabledstipple: str = ..., disabledwidth: _ScreenUnits = ..., - fill: _Color = ..., + fill: str = ..., joinstyle: Literal["round", "bevel", "miter"] = ..., offset: _ScreenUnits = ..., smooth: bool = ..., splinesteps: float = ..., - state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + state: Literal["normal", "hidden", "disabled"] = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_line( self, - __xy_pair_0: tuple[float, float], - __xy_pair_1: tuple[float, float], + xy_pair_0: tuple[float, float], + xy_pair_1: tuple[float, float], + /, *, - activedash: str | list[int] | tuple[int, ...] = ..., - activefill: _Color = ..., + activedash: str | int | list[int] | tuple[int, ...] = ..., + activefill: str = ..., activestipple: str = ..., activewidth: _ScreenUnits = ..., arrow: Literal["first", "last", "both"] = ..., arrowshape: tuple[float, float, float] = ..., capstyle: Literal["round", "projecting", "butt"] = ..., - dash: str | list[int] | tuple[int, ...] = ..., + dash: str | int | list[int] | tuple[int, ...] = ..., dashoffset: _ScreenUnits = ..., - disableddash: str | list[int] | tuple[int, ...] = ..., - disabledfill: _Color = ..., - disabledstipple: _Bitmap = ..., + disableddash: str | int | list[int] | tuple[int, ...] = ..., + disabledfill: str = ..., + disabledstipple: str = ..., disabledwidth: _ScreenUnits = ..., - fill: _Color = ..., + fill: str = ..., joinstyle: Literal["round", "bevel", "miter"] = ..., offset: _ScreenUnits = ..., smooth: bool = ..., splinesteps: float = ..., - state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + state: Literal["normal", "hidden", "disabled"] = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_line( self, - __coords: ( + coords: ( tuple[float, float, float, float] | tuple[tuple[float, float], tuple[float, float]] | list[int] @@ -1307,96 +1298,99 @@ class Canvas(Widget, XView, YView): | list[tuple[int, int]] | list[tuple[float, float]] ), + /, *, - activedash: str | list[int] | tuple[int, ...] = ..., - activefill: _Color = ..., + activedash: str | int | list[int] | tuple[int, ...] = ..., + activefill: str = ..., activestipple: str = ..., activewidth: _ScreenUnits = ..., arrow: Literal["first", "last", "both"] = ..., arrowshape: tuple[float, float, float] = ..., capstyle: Literal["round", "projecting", "butt"] = ..., - dash: str | list[int] | tuple[int, ...] = ..., + dash: str | int | list[int] | tuple[int, ...] = ..., dashoffset: _ScreenUnits = ..., - disableddash: str | list[int] | tuple[int, ...] = ..., - disabledfill: _Color = ..., - disabledstipple: _Bitmap = ..., + disableddash: str | int | list[int] | tuple[int, ...] = ..., + disabledfill: str = ..., + disabledstipple: str = ..., disabledwidth: _ScreenUnits = ..., - fill: _Color = ..., + fill: str = ..., joinstyle: Literal["round", "bevel", "miter"] = ..., offset: _ScreenUnits = ..., smooth: bool = ..., splinesteps: float = ..., - state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + state: Literal["normal", "hidden", "disabled"] = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_oval( self, - __x0: float, - __y0: float, - __x1: float, - __y1: float, + x0: float, + y0: float, + x1: float, + y1: float, + /, *, - activedash: str | list[int] | tuple[int, ...] = ..., - activefill: _Color = ..., - activeoutline: _Color = ..., - activeoutlinestipple: _Color = ..., + activedash: str | int | list[int] | tuple[int, ...] = ..., + activefill: str = ..., + activeoutline: str = ..., + activeoutlinestipple: str = ..., activestipple: str = ..., activewidth: _ScreenUnits = ..., - dash: str | list[int] | tuple[int, ...] = ..., + dash: str | int | list[int] | tuple[int, ...] = ..., dashoffset: _ScreenUnits = ..., - disableddash: str | list[int] | tuple[int, ...] = ..., - disabledfill: _Color = ..., - disabledoutline: _Color = ..., - disabledoutlinestipple: _Color = ..., - disabledstipple: _Bitmap = ..., + disableddash: str | int | list[int] | tuple[int, ...] = ..., + disabledfill: str = ..., + disabledoutline: str = ..., + disabledoutlinestipple: str = ..., + disabledstipple: str = ..., disabledwidth: _ScreenUnits = ..., - fill: _Color = ..., + fill: str = ..., offset: _ScreenUnits = ..., - outline: _Color = ..., + outline: str = ..., outlineoffset: _ScreenUnits = ..., - outlinestipple: _Bitmap = ..., - state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + outlinestipple: str = ..., + state: Literal["normal", "hidden", "disabled"] = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_oval( self, - __xy_pair_0: tuple[float, float], - __xy_pair_1: tuple[float, float], + xy_pair_0: tuple[float, float], + xy_pair_1: tuple[float, float], + /, *, - activedash: str | list[int] | tuple[int, ...] = ..., - activefill: _Color = ..., - activeoutline: _Color = ..., - activeoutlinestipple: _Color = ..., + activedash: str | int | list[int] | tuple[int, ...] = ..., + activefill: str = ..., + activeoutline: str = ..., + activeoutlinestipple: str = ..., activestipple: str = ..., activewidth: _ScreenUnits = ..., - dash: str | list[int] | tuple[int, ...] = ..., + dash: str | int | list[int] | tuple[int, ...] = ..., dashoffset: _ScreenUnits = ..., - disableddash: str | list[int] | tuple[int, ...] = ..., - disabledfill: _Color = ..., - disabledoutline: _Color = ..., - disabledoutlinestipple: _Color = ..., - disabledstipple: _Bitmap = ..., + disableddash: str | int | list[int] | tuple[int, ...] = ..., + disabledfill: str = ..., + disabledoutline: str = ..., + disabledoutlinestipple: str = ..., + disabledstipple: str = ..., disabledwidth: _ScreenUnits = ..., - fill: _Color = ..., + fill: str = ..., offset: _ScreenUnits = ..., - outline: _Color = ..., + outline: str = ..., outlineoffset: _ScreenUnits = ..., - outlinestipple: _Bitmap = ..., - state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + outlinestipple: str = ..., + state: Literal["normal", "hidden", "disabled"] = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_oval( self, - __coords: ( + coords: ( tuple[float, float, float, float] | tuple[tuple[float, float], tuple[float, float]] | list[int] @@ -1404,103 +1398,106 @@ class Canvas(Widget, XView, YView): | list[tuple[int, int]] | list[tuple[float, float]] ), + /, *, - activedash: str | list[int] | tuple[int, ...] = ..., - activefill: _Color = ..., - activeoutline: _Color = ..., - activeoutlinestipple: _Color = ..., + activedash: str | int | list[int] | tuple[int, ...] = ..., + activefill: str = ..., + activeoutline: str = ..., + activeoutlinestipple: str = ..., activestipple: str = ..., activewidth: _ScreenUnits = ..., - dash: str | list[int] | tuple[int, ...] = ..., + dash: str | int | list[int] | tuple[int, ...] = ..., dashoffset: _ScreenUnits = ..., - disableddash: str | list[int] | tuple[int, ...] = ..., - disabledfill: _Color = ..., - disabledoutline: _Color = ..., - disabledoutlinestipple: _Color = ..., - disabledstipple: _Bitmap = ..., + disableddash: str | int | list[int] | tuple[int, ...] = ..., + disabledfill: str = ..., + disabledoutline: str = ..., + disabledoutlinestipple: str = ..., + disabledstipple: str = ..., disabledwidth: _ScreenUnits = ..., - fill: _Color = ..., + fill: str = ..., offset: _ScreenUnits = ..., - outline: _Color = ..., + outline: str = ..., outlineoffset: _ScreenUnits = ..., - outlinestipple: _Bitmap = ..., - state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + outlinestipple: str = ..., + state: Literal["normal", "hidden", "disabled"] = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_polygon( self, - __x0: float, - __y0: float, - __x1: float, - __y1: float, + x0: float, + y0: float, + x1: float, + y1: float, + /, *xy_pairs: float, - activedash: str | list[int] | tuple[int, ...] = ..., - activefill: _Color = ..., - activeoutline: _Color = ..., - activeoutlinestipple: _Color = ..., + activedash: str | int | list[int] | tuple[int, ...] = ..., + activefill: str = ..., + activeoutline: str = ..., + activeoutlinestipple: str = ..., activestipple: str = ..., activewidth: _ScreenUnits = ..., - dash: str | list[int] | tuple[int, ...] = ..., + dash: str | int | list[int] | tuple[int, ...] = ..., dashoffset: _ScreenUnits = ..., - disableddash: str | list[int] | tuple[int, ...] = ..., - disabledfill: _Color = ..., - disabledoutline: _Color = ..., - disabledoutlinestipple: _Color = ..., - disabledstipple: _Bitmap = ..., + disableddash: str | int | list[int] | tuple[int, ...] = ..., + disabledfill: str = ..., + disabledoutline: str = ..., + disabledoutlinestipple: str = ..., + disabledstipple: str = ..., disabledwidth: _ScreenUnits = ..., - fill: _Color = ..., + fill: str = ..., joinstyle: Literal["round", "bevel", "miter"] = ..., offset: _ScreenUnits = ..., - outline: _Color = ..., + outline: str = ..., outlineoffset: _ScreenUnits = ..., - outlinestipple: _Bitmap = ..., + outlinestipple: str = ..., smooth: bool = ..., splinesteps: float = ..., - state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + state: Literal["normal", "hidden", "disabled"] = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_polygon( self, - __xy_pair_0: tuple[float, float], - __xy_pair_1: tuple[float, float], + xy_pair_0: tuple[float, float], + xy_pair_1: tuple[float, float], + /, *xy_pairs: tuple[float, float], - activedash: str | list[int] | tuple[int, ...] = ..., - activefill: _Color = ..., - activeoutline: _Color = ..., - activeoutlinestipple: _Color = ..., + activedash: str | int | list[int] | tuple[int, ...] = ..., + activefill: str = ..., + activeoutline: str = ..., + activeoutlinestipple: str = ..., activestipple: str = ..., activewidth: _ScreenUnits = ..., - dash: str | list[int] | tuple[int, ...] = ..., + dash: str | int | list[int] | tuple[int, ...] = ..., dashoffset: _ScreenUnits = ..., - disableddash: str | list[int] | tuple[int, ...] = ..., - disabledfill: _Color = ..., - disabledoutline: _Color = ..., - disabledoutlinestipple: _Color = ..., - disabledstipple: _Bitmap = ..., + disableddash: str | int | list[int] | tuple[int, ...] = ..., + disabledfill: str = ..., + disabledoutline: str = ..., + disabledoutlinestipple: str = ..., + disabledstipple: str = ..., disabledwidth: _ScreenUnits = ..., - fill: _Color = ..., + fill: str = ..., joinstyle: Literal["round", "bevel", "miter"] = ..., offset: _ScreenUnits = ..., - outline: _Color = ..., + outline: str = ..., outlineoffset: _ScreenUnits = ..., - outlinestipple: _Bitmap = ..., + outlinestipple: str = ..., smooth: bool = ..., splinesteps: float = ..., - state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + state: Literal["normal", "hidden", "disabled"] = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_polygon( self, - __coords: ( + coords: ( tuple[float, ...] | tuple[tuple[float, float], ...] | list[int] @@ -1508,100 +1505,103 @@ class Canvas(Widget, XView, YView): | list[tuple[int, int]] | list[tuple[float, float]] ), + /, *, - activedash: str | list[int] | tuple[int, ...] = ..., - activefill: _Color = ..., - activeoutline: _Color = ..., - activeoutlinestipple: _Color = ..., + activedash: str | int | list[int] | tuple[int, ...] = ..., + activefill: str = ..., + activeoutline: str = ..., + activeoutlinestipple: str = ..., activestipple: str = ..., activewidth: _ScreenUnits = ..., - dash: str | list[int] | tuple[int, ...] = ..., + dash: str | int | list[int] | tuple[int, ...] = ..., dashoffset: _ScreenUnits = ..., - disableddash: str | list[int] | tuple[int, ...] = ..., - disabledfill: _Color = ..., - disabledoutline: _Color = ..., - disabledoutlinestipple: _Color = ..., - disabledstipple: _Bitmap = ..., + disableddash: str | int | list[int] | tuple[int, ...] = ..., + disabledfill: str = ..., + disabledoutline: str = ..., + disabledoutlinestipple: str = ..., + disabledstipple: str = ..., disabledwidth: _ScreenUnits = ..., - fill: _Color = ..., + fill: str = ..., joinstyle: Literal["round", "bevel", "miter"] = ..., offset: _ScreenUnits = ..., - outline: _Color = ..., + outline: str = ..., outlineoffset: _ScreenUnits = ..., - outlinestipple: _Bitmap = ..., + outlinestipple: str = ..., smooth: bool = ..., splinesteps: float = ..., - state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + state: Literal["normal", "hidden", "disabled"] = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_rectangle( self, - __x0: float, - __y0: float, - __x1: float, - __y1: float, + x0: float, + y0: float, + x1: float, + y1: float, + /, *, - activedash: str | list[int] | tuple[int, ...] = ..., - activefill: _Color = ..., - activeoutline: _Color = ..., - activeoutlinestipple: _Color = ..., + activedash: str | int | list[int] | tuple[int, ...] = ..., + activefill: str = ..., + activeoutline: str = ..., + activeoutlinestipple: str = ..., activestipple: str = ..., activewidth: _ScreenUnits = ..., - dash: str | list[int] | tuple[int, ...] = ..., + dash: str | int | list[int] | tuple[int, ...] = ..., dashoffset: _ScreenUnits = ..., - disableddash: str | list[int] | tuple[int, ...] = ..., - disabledfill: _Color = ..., - disabledoutline: _Color = ..., - disabledoutlinestipple: _Color = ..., - disabledstipple: _Bitmap = ..., + disableddash: str | int | list[int] | tuple[int, ...] = ..., + disabledfill: str = ..., + disabledoutline: str = ..., + disabledoutlinestipple: str = ..., + disabledstipple: str = ..., disabledwidth: _ScreenUnits = ..., - fill: _Color = ..., + fill: str = ..., offset: _ScreenUnits = ..., - outline: _Color = ..., + outline: str = ..., outlineoffset: _ScreenUnits = ..., - outlinestipple: _Bitmap = ..., - state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + outlinestipple: str = ..., + state: Literal["normal", "hidden", "disabled"] = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_rectangle( self, - __xy_pair_0: tuple[float, float], - __xy_pair_1: tuple[float, float], + xy_pair_0: tuple[float, float], + xy_pair_1: tuple[float, float], + /, *, - activedash: str | list[int] | tuple[int, ...] = ..., - activefill: _Color = ..., - activeoutline: _Color = ..., - activeoutlinestipple: _Color = ..., + activedash: str | int | list[int] | tuple[int, ...] = ..., + activefill: str = ..., + activeoutline: str = ..., + activeoutlinestipple: str = ..., activestipple: str = ..., activewidth: _ScreenUnits = ..., - dash: str | list[int] | tuple[int, ...] = ..., + dash: str | int | list[int] | tuple[int, ...] = ..., dashoffset: _ScreenUnits = ..., - disableddash: str | list[int] | tuple[int, ...] = ..., - disabledfill: _Color = ..., - disabledoutline: _Color = ..., - disabledoutlinestipple: _Color = ..., - disabledstipple: _Bitmap = ..., + disableddash: str | int | list[int] | tuple[int, ...] = ..., + disabledfill: str = ..., + disabledoutline: str = ..., + disabledoutlinestipple: str = ..., + disabledstipple: str = ..., disabledwidth: _ScreenUnits = ..., - fill: _Color = ..., + fill: str = ..., offset: _ScreenUnits = ..., - outline: _Color = ..., + outline: str = ..., outlineoffset: _ScreenUnits = ..., - outlinestipple: _Bitmap = ..., - state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + outlinestipple: str = ..., + state: Literal["normal", "hidden", "disabled"] = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_rectangle( self, - __coords: ( + coords: ( tuple[float, float, float, float] | tuple[tuple[float, float], tuple[float, float]] | list[int] @@ -1609,130 +1609,137 @@ class Canvas(Widget, XView, YView): | list[tuple[int, int]] | list[tuple[float, float]] ), + /, *, - activedash: str | list[int] | tuple[int, ...] = ..., - activefill: _Color = ..., - activeoutline: _Color = ..., - activeoutlinestipple: _Color = ..., + activedash: str | int | list[int] | tuple[int, ...] = ..., + activefill: str = ..., + activeoutline: str = ..., + activeoutlinestipple: str = ..., activestipple: str = ..., activewidth: _ScreenUnits = ..., - dash: str | list[int] | tuple[int, ...] = ..., + dash: str | int | list[int] | tuple[int, ...] = ..., dashoffset: _ScreenUnits = ..., - disableddash: str | list[int] | tuple[int, ...] = ..., - disabledfill: _Color = ..., - disabledoutline: _Color = ..., - disabledoutlinestipple: _Color = ..., - disabledstipple: _Bitmap = ..., + disableddash: str | int | list[int] | tuple[int, ...] = ..., + disabledfill: str = ..., + disabledoutline: str = ..., + disabledoutlinestipple: str = ..., + disabledstipple: str = ..., disabledwidth: _ScreenUnits = ..., - fill: _Color = ..., + fill: str = ..., offset: _ScreenUnits = ..., - outline: _Color = ..., + outline: str = ..., outlineoffset: _ScreenUnits = ..., - outlinestipple: _Bitmap = ..., - state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + outlinestipple: str = ..., + state: Literal["normal", "hidden", "disabled"] = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_text( self, - __x: float, - __y: float, + x: float, + y: float, + /, *, - activefill: _Color = ..., + activefill: str = ..., activestipple: str = ..., anchor: _Anchor = ..., - disabledfill: _Color = ..., - disabledstipple: _Bitmap = ..., - fill: _Color = ..., + angle: float | str = ..., + disabledfill: str = ..., + disabledstipple: str = ..., + fill: str = ..., font: _FontDescription = ..., justify: Literal["left", "center", "right"] = ..., offset: _ScreenUnits = ..., - state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + state: Literal["normal", "hidden", "disabled"] = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., text: float | str = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_text( self, - __coords: tuple[float, float] | list[int] | list[float], + coords: tuple[float, float] | list[int] | list[float], + /, *, - activefill: _Color = ..., + activefill: str = ..., activestipple: str = ..., anchor: _Anchor = ..., - disabledfill: _Color = ..., - disabledstipple: _Bitmap = ..., - fill: _Color = ..., + angle: float | str = ..., + disabledfill: str = ..., + disabledstipple: str = ..., + fill: str = ..., font: _FontDescription = ..., justify: Literal["left", "center", "right"] = ..., offset: _ScreenUnits = ..., - state: Literal["normal", "active", "disabled"] = ..., - stipple: _Bitmap = ..., + state: Literal["normal", "hidden", "disabled"] = ..., + stipple: str = ..., tags: str | list[str] | tuple[str, ...] = ..., text: float | str = ..., width: _ScreenUnits = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_window( self, - __x: float, - __y: float, + x: float, + y: float, + /, *, anchor: _Anchor = ..., height: _ScreenUnits = ..., - state: Literal["normal", "active", "disabled"] = ..., + state: Literal["normal", "hidden", "disabled"] = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., window: Widget = ..., - ) -> _CanvasItemId: ... + ) -> int: ... @overload def create_window( self, - __coords: tuple[float, float] | list[int] | list[float], + coords: tuple[float, float] | list[int] | list[float], + /, *, anchor: _Anchor = ..., height: _ScreenUnits = ..., - state: Literal["normal", "active", "disabled"] = ..., + state: Literal["normal", "hidden", "disabled"] = ..., tags: str | list[str] | tuple[str, ...] = ..., width: _ScreenUnits = ..., window: Widget = ..., - ) -> _CanvasItemId: ... + ) -> int: ... def dchars(self, *args) -> None: ... - def delete(self, *tagsOrCanvasIds: str | _CanvasItemId) -> None: ... + def delete(self, *tagsOrCanvasIds: str | int) -> None: ... @overload - def dtag(self, __tag: str, __tag_to_delete: str | None = ...) -> None: ... + def dtag(self, tag: str, tag_to_delete: str | None = ..., /) -> None: ... @overload - def dtag(self, __id: _CanvasItemId, __tag_to_delete: str) -> None: ... + def dtag(self, id: int, tag_to_delete: str, /) -> None: ... def focus(self, *args): ... - def gettags(self, __tagOrId: str | _CanvasItemId) -> tuple[str, ...]: ... + def gettags(self, tagOrId: str | int, /) -> tuple[str, ...]: ... def icursor(self, *args) -> None: ... def index(self, *args): ... def insert(self, *args) -> None: ... def itemcget(self, tagOrId, option): ... # itemconfigure kwargs depend on item type, which is not known when type checking def itemconfigure( - self, tagOrId: str | _CanvasItemId, cnf: dict[str, Any] | None = None, **kw: Any + self, tagOrId: str | int, cnf: dict[str, Any] | None = None, **kw: Any ) -> dict[str, tuple[str, str, str, str, str]] | None: ... itemconfig = itemconfigure def move(self, *args) -> None: ... - if sys.version_info >= (3, 8): - def moveto(self, tagOrId: str | _CanvasItemId, x: Literal[""] | float = "", y: Literal[""] | float = "") -> None: ... - - def postscript(self, cnf=..., **kw): ... + def moveto(self, tagOrId: str | int, x: Literal[""] | float = "", y: Literal[""] | float = "") -> None: ... + def postscript(self, cnf={}, **kw): ... # tkinter does: # lower = tag_lower # lift = tkraise = tag_raise # # But mypy doesn't like aliasing here (maybe because Misc defines the same names) - def tag_lower(self, __first: str | _CanvasItemId, __second: str | _CanvasItemId | None = ...) -> None: ... - def lower(self, __first: str | _CanvasItemId, __second: str | _CanvasItemId | None = ...) -> None: ... # type: ignore[override] - def tag_raise(self, __first: str | _CanvasItemId, __second: str | _CanvasItemId | None = ...) -> None: ... - def tkraise(self, __first: str | _CanvasItemId, __second: str | _CanvasItemId | None = ...) -> None: ... # type: ignore[override] - def lift(self, __first: str | _CanvasItemId, __second: str | _CanvasItemId | None = ...) -> None: ... # type: ignore[override] - def scale(self, *args) -> None: ... + def tag_lower(self, first: str | int, second: str | int | None = ..., /) -> None: ... + def lower(self, first: str | int, second: str | int | None = ..., /) -> None: ... # type: ignore[override] + def tag_raise(self, first: str | int, second: str | int | None = ..., /) -> None: ... + def tkraise(self, first: str | int, second: str | int | None = ..., /) -> None: ... # type: ignore[override] + def lift(self, first: str | int, second: str | int | None = ..., /) -> None: ... # type: ignore[override] + def scale( + self, tagOrId: str | int, xOrigin: _ScreenUnits, yOrigin: _ScreenUnits, xScale: float, yScale: float, / + ) -> None: ... def scan_mark(self, x, y) -> None: ... def scan_dragto(self, x, y, gain: int = 10) -> None: ... def select_adjust(self, tagOrId, index) -> None: ... @@ -1740,37 +1747,37 @@ class Canvas(Widget, XView, YView): def select_from(self, tagOrId, index) -> None: ... def select_item(self): ... def select_to(self, tagOrId, index) -> None: ... - def type(self, tagOrId): ... + def type(self, tagOrId: str | int) -> int | None: ... class Checkbutton(Widget): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, - activebackground: _Color = ..., - activeforeground: _Color = ..., - anchor: _Anchor = ..., - background: _Color = ..., + activebackground: str = ..., + activeforeground: str = ..., + anchor: _Anchor = "center", + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., - bitmap: _Bitmap = ..., + bg: str = ..., + bitmap: str = "", border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., - command: _ButtonCommand = ..., - compound: _Compound = ..., - cursor: _Cursor = ..., - disabledforeground: _Color = ..., - fg: _Color = ..., - font: _FontDescription = ..., - foreground: _Color = ..., - height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., - highlightthickness: _ScreenUnits = ..., - image: _ImageSpec = ..., - indicatoron: bool = ..., - justify: Literal["left", "center", "right"] = ..., + command: _ButtonCommand = "", + compound: _Compound = "none", + cursor: _Cursor = "", + disabledforeground: str = ..., + fg: str = ..., + font: _FontDescription = "TkDefaultFont", + foreground: str = ..., + height: _ScreenUnits = 0, + highlightbackground: str = ..., + highlightcolor: str = ..., + highlightthickness: _ScreenUnits = 1, + image: _ImageSpec = "", + indicatoron: bool = True, + justify: Literal["left", "center", "right"] = "center", name: str = ..., offrelief: _Relief = ..., # The checkbutton puts a value to its variable when it's checked or @@ -1783,49 +1790,49 @@ class Checkbutton(Widget): # and list[int] are incompatible. Also, we would need a way to # specify "Checkbutton not associated with any variable", which is # done by setting variable to empty string (the default). - offvalue: Any = ..., - onvalue: Any = ..., - overrelief: _Relief = ..., - padx: _ScreenUnits = ..., - pady: _ScreenUnits = ..., - relief: _Relief = ..., - selectcolor: _Color = ..., - selectimage: _ImageSpec = ..., - state: Literal["normal", "active", "disabled"] = ..., - takefocus: _TakeFocusValue = ..., - text: float | str = ..., + offvalue: Any = 0, + onvalue: Any = 1, + overrelief: _Relief | Literal[""] = "", + padx: _ScreenUnits = 1, + pady: _ScreenUnits = 1, + relief: _Relief = "flat", + selectcolor: str = ..., + selectimage: _ImageSpec = "", + state: Literal["normal", "active", "disabled"] = "normal", + takefocus: _TakeFocusValue = "", + text: float | str = "", textvariable: Variable = ..., - tristateimage: _ImageSpec = ..., - tristatevalue: Any = ..., - underline: int = ..., + tristateimage: _ImageSpec = "", + tristatevalue: Any = "", + underline: int = -1, variable: Variable | Literal[""] = ..., - width: _ScreenUnits = ..., - wraplength: _ScreenUnits = ..., + width: _ScreenUnits = 0, + wraplength: _ScreenUnits = 0, ) -> None: ... @overload def configure( self, cnf: dict[str, Any] | None = None, *, - activebackground: _Color = ..., - activeforeground: _Color = ..., + activebackground: str = ..., + activeforeground: str = ..., anchor: _Anchor = ..., - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., - bitmap: _Bitmap = ..., + bg: str = ..., + bitmap: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., command: _ButtonCommand = ..., compound: _Compound = ..., cursor: _Cursor = ..., - disabledforeground: _Color = ..., - fg: _Color = ..., + disabledforeground: str = ..., + fg: str = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., image: _ImageSpec = ..., indicatoron: bool = ..., @@ -1833,11 +1840,11 @@ class Checkbutton(Widget): offrelief: _Relief = ..., offvalue: Any = ..., onvalue: Any = ..., - overrelief: _Relief = ..., + overrelief: _Relief | Literal[""] = ..., padx: _ScreenUnits = ..., pady: _ScreenUnits = ..., relief: _Relief = ..., - selectcolor: _Color = ..., + selectcolor: str = ..., selectimage: _ImageSpec = ..., state: Literal["normal", "active", "disabled"] = ..., takefocus: _TakeFocusValue = ..., @@ -1859,74 +1866,72 @@ class Checkbutton(Widget): def select(self) -> None: ... def toggle(self) -> None: ... -_EntryIndex: TypeAlias = str | int # "INDICES" in manual page - class Entry(Widget, XView): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., - cursor: _Cursor = ..., - disabledbackground: _Color = ..., - disabledforeground: _Color = ..., - exportselection: bool = ..., - fg: _Color = ..., - font: _FontDescription = ..., - foreground: _Color = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + cursor: _Cursor = "xterm", + disabledbackground: str = ..., + disabledforeground: str = ..., + exportselection: bool = True, + fg: str = ..., + font: _FontDescription = "TkTextFont", + foreground: str = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., - insertbackground: _Color = ..., - insertborderwidth: _ScreenUnits = ..., - insertofftime: int = ..., - insertontime: int = ..., + insertbackground: str = ..., + insertborderwidth: _ScreenUnits = 0, + insertofftime: int = 300, + insertontime: int = 600, insertwidth: _ScreenUnits = ..., - invalidcommand: _EntryValidateCommand = ..., - invcmd: _EntryValidateCommand = ..., # same as invalidcommand - justify: Literal["left", "center", "right"] = ..., + invalidcommand: _EntryValidateCommand = "", + invcmd: _EntryValidateCommand = "", # same as invalidcommand + justify: Literal["left", "center", "right"] = "left", name: str = ..., - readonlybackground: _Color = ..., - relief: _Relief = ..., - selectbackground: _Color = ..., + readonlybackground: str = ..., + relief: _Relief = "sunken", + selectbackground: str = ..., selectborderwidth: _ScreenUnits = ..., - selectforeground: _Color = ..., - show: str = ..., - state: Literal["normal", "disabled", "readonly"] = ..., - takefocus: _TakeFocusValue = ..., + selectforeground: str = ..., + show: str = "", + state: Literal["normal", "disabled", "readonly"] = "normal", + takefocus: _TakeFocusValue = "", textvariable: Variable = ..., - validate: Literal["none", "focus", "focusin", "focusout", "key", "all"] = ..., - validatecommand: _EntryValidateCommand = ..., - vcmd: _EntryValidateCommand = ..., # same as validatecommand - width: int = ..., - xscrollcommand: _XYScrollCommand = ..., + validate: Literal["none", "focus", "focusin", "focusout", "key", "all"] = "none", + validatecommand: _EntryValidateCommand = "", + vcmd: _EntryValidateCommand = "", # same as validatecommand + width: int = 20, + xscrollcommand: _XYScrollCommand = "", ) -> None: ... @overload def configure( self, cnf: dict[str, Any] | None = None, *, - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., cursor: _Cursor = ..., - disabledbackground: _Color = ..., - disabledforeground: _Color = ..., + disabledbackground: str = ..., + disabledforeground: str = ..., exportselection: bool = ..., - fg: _Color = ..., + fg: str = ..., font: _FontDescription = ..., - foreground: _Color = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + foreground: str = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., - insertbackground: _Color = ..., + insertbackground: str = ..., insertborderwidth: _ScreenUnits = ..., insertofftime: int = ..., insertontime: int = ..., @@ -1934,11 +1939,11 @@ class Entry(Widget, XView): invalidcommand: _EntryValidateCommand = ..., invcmd: _EntryValidateCommand = ..., justify: Literal["left", "center", "right"] = ..., - readonlybackground: _Color = ..., + readonlybackground: str = ..., relief: _Relief = ..., - selectbackground: _Color = ..., + selectbackground: str = ..., selectborderwidth: _ScreenUnits = ..., - selectforeground: _Color = ..., + selectforeground: str = ..., show: str = ..., state: Literal["normal", "disabled", "readonly"] = ..., takefocus: _TakeFocusValue = ..., @@ -1952,19 +1957,19 @@ class Entry(Widget, XView): @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... config = configure - def delete(self, first: _EntryIndex, last: _EntryIndex | None = None) -> None: ... + def delete(self, first: str | int, last: str | int | None = None) -> None: ... def get(self) -> str: ... - def icursor(self, index: _EntryIndex) -> None: ... - def index(self, index: _EntryIndex) -> int: ... - def insert(self, index: _EntryIndex, string: str) -> None: ... + def icursor(self, index: str | int) -> None: ... + def index(self, index: str | int) -> int: ... + def insert(self, index: str | int, string: str) -> None: ... def scan_mark(self, x) -> None: ... def scan_dragto(self, x) -> None: ... - def selection_adjust(self, index: _EntryIndex) -> None: ... + def selection_adjust(self, index: str | int) -> None: ... def selection_clear(self) -> None: ... # type: ignore[override] - def selection_from(self, index: _EntryIndex) -> None: ... + def selection_from(self, index: str | int) -> None: ... def selection_present(self) -> bool: ... - def selection_range(self, start: _EntryIndex, end: _EntryIndex) -> None: ... - def selection_to(self, index: _EntryIndex) -> None: ... + def selection_range(self, start: str | int, end: str | int) -> None: ... + def selection_to(self, index: str | int) -> None: ... select_adjust = selection_adjust select_clear = selection_clear select_from = selection_from @@ -1976,43 +1981,43 @@ class Frame(Widget): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., - *, - background: _Color = ..., - bd: _ScreenUnits = ..., - bg: _Color = ..., - border: _ScreenUnits = ..., - borderwidth: _ScreenUnits = ..., - class_: str = ..., # can't be changed with configure() - colormap: Literal["new", ""] | Misc = ..., # can't be changed with configure() - container: bool = ..., # can't be changed with configure() - cursor: _Cursor = ..., - height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., - highlightthickness: _ScreenUnits = ..., + cnf: dict[str, Any] | None = {}, + *, + background: str = ..., + bd: _ScreenUnits = 0, + bg: str = ..., + border: _ScreenUnits = 0, + borderwidth: _ScreenUnits = 0, + class_: str = "Frame", # can't be changed with configure() + colormap: Literal["new", ""] | Misc = "", # can't be changed with configure() + container: bool = False, # can't be changed with configure() + cursor: _Cursor = "", + height: _ScreenUnits = 0, + highlightbackground: str = ..., + highlightcolor: str = ..., + highlightthickness: _ScreenUnits = 0, name: str = ..., - padx: _ScreenUnits = ..., - pady: _ScreenUnits = ..., - relief: _Relief = ..., - takefocus: _TakeFocusValue = ..., - visual: str | tuple[str, int] = ..., # can't be changed with configure() - width: _ScreenUnits = ..., + padx: _ScreenUnits = 0, + pady: _ScreenUnits = 0, + relief: _Relief = "flat", + takefocus: _TakeFocusValue = 0, + visual: str | tuple[str, int] = "", # can't be changed with configure() + width: _ScreenUnits = 0, ) -> None: ... @overload def configure( self, cnf: dict[str, Any] | None = None, *, - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., cursor: _Cursor = ..., height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., padx: _ScreenUnits = ..., pady: _ScreenUnits = ..., @@ -2028,64 +2033,64 @@ class Label(Widget): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, - activebackground: _Color = ..., - activeforeground: _Color = ..., - anchor: _Anchor = ..., - background: _Color = ..., + activebackground: str = ..., + activeforeground: str = ..., + anchor: _Anchor = "center", + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., - bitmap: _Bitmap = ..., + bg: str = ..., + bitmap: str = "", border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., - compound: _Compound = ..., - cursor: _Cursor = ..., - disabledforeground: _Color = ..., - fg: _Color = ..., - font: _FontDescription = ..., - foreground: _Color = ..., - height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., - highlightthickness: _ScreenUnits = ..., - image: _ImageSpec = ..., - justify: Literal["left", "center", "right"] = ..., + compound: _Compound = "none", + cursor: _Cursor = "", + disabledforeground: str = ..., + fg: str = ..., + font: _FontDescription = "TkDefaultFont", + foreground: str = ..., + height: _ScreenUnits = 0, + highlightbackground: str = ..., + highlightcolor: str = ..., + highlightthickness: _ScreenUnits = 0, + image: _ImageSpec = "", + justify: Literal["left", "center", "right"] = "center", name: str = ..., - padx: _ScreenUnits = ..., - pady: _ScreenUnits = ..., - relief: _Relief = ..., - state: Literal["normal", "active", "disabled"] = ..., - takefocus: _TakeFocusValue = ..., - text: float | str = ..., + padx: _ScreenUnits = 1, + pady: _ScreenUnits = 1, + relief: _Relief = "flat", + state: Literal["normal", "active", "disabled"] = "normal", + takefocus: _TakeFocusValue = 0, + text: float | str = "", textvariable: Variable = ..., - underline: int = ..., - width: _ScreenUnits = ..., - wraplength: _ScreenUnits = ..., + underline: int = -1, + width: _ScreenUnits = 0, + wraplength: _ScreenUnits = 0, ) -> None: ... @overload def configure( self, cnf: dict[str, Any] | None = None, *, - activebackground: _Color = ..., - activeforeground: _Color = ..., + activebackground: str = ..., + activeforeground: str = ..., anchor: _Anchor = ..., - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., - bitmap: _Bitmap = ..., + bg: str = ..., + bitmap: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., compound: _Compound = ..., cursor: _Cursor = ..., - disabledforeground: _Color = ..., - fg: _Color = ..., + disabledforeground: str = ..., + fg: str = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., image: _ImageSpec = ..., justify: Literal["left", "center", "right"] = ..., @@ -2108,25 +2113,25 @@ class Listbox(Widget, XView, YView): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, activestyle: Literal["dotbox", "none", "underline"] = ..., - background: _Color = ..., - bd: _ScreenUnits = ..., - bg: _Color = ..., - border: _ScreenUnits = ..., - borderwidth: _ScreenUnits = ..., - cursor: _Cursor = ..., - disabledforeground: _Color = ..., - exportselection: int = ..., - fg: _Color = ..., + background: str = ..., + bd: _ScreenUnits = 1, + bg: str = ..., + border: _ScreenUnits = 1, + borderwidth: _ScreenUnits = 1, + cursor: _Cursor = "", + disabledforeground: str = ..., + exportselection: bool | Literal[0, 1] = 1, + fg: str = ..., font: _FontDescription = ..., - foreground: _Color = ..., - height: int = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + foreground: str = ..., + height: int = 10, + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., - justify: Literal["left", "center", "right"] = ..., + justify: Literal["left", "center", "right"] = "left", # There's no tkinter.ListVar, but seems like bare tkinter.Variable # actually works for this: # @@ -2139,21 +2144,21 @@ class Listbox(Widget, XView, YView): listvariable: Variable = ..., name: str = ..., relief: _Relief = ..., - selectbackground: _Color = ..., - selectborderwidth: _ScreenUnits = ..., - selectforeground: _Color = ..., + selectbackground: str = ..., + selectborderwidth: _ScreenUnits = 0, + selectforeground: str = ..., # from listbox man page: "The value of the [selectmode] option may be # arbitrary, but the default bindings expect it to be ..." # # I have never seen anyone setting this to something else than what # "the default bindings expect", but let's support it anyway. - selectmode: str = ..., - setgrid: bool = ..., - state: Literal["normal", "disabled"] = ..., - takefocus: _TakeFocusValue = ..., - width: int = ..., - xscrollcommand: _XYScrollCommand = ..., - yscrollcommand: _XYScrollCommand = ..., + selectmode: str = "browse", + setgrid: bool = False, + state: Literal["normal", "disabled"] = "normal", + takefocus: _TakeFocusValue = "", + width: int = 20, + xscrollcommand: _XYScrollCommand = "", + yscrollcommand: _XYScrollCommand = "", ) -> None: ... @overload def configure( @@ -2161,27 +2166,27 @@ class Listbox(Widget, XView, YView): cnf: dict[str, Any] | None = None, *, activestyle: Literal["dotbox", "none", "underline"] = ..., - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., cursor: _Cursor = ..., - disabledforeground: _Color = ..., + disabledforeground: str = ..., exportselection: bool = ..., - fg: _Color = ..., + fg: str = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., height: int = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., justify: Literal["left", "center", "right"] = ..., listvariable: Variable = ..., relief: _Relief = ..., - selectbackground: _Color = ..., + selectbackground: str = ..., selectborderwidth: _ScreenUnits = ..., - selectforeground: _Color = ..., + selectforeground: str = ..., selectmode: str = ..., setgrid: bool = ..., state: Literal["normal", "disabled"] = ..., @@ -2221,55 +2226,55 @@ class Menu(Widget): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, - activebackground: _Color = ..., + activebackground: str = ..., activeborderwidth: _ScreenUnits = ..., - activeforeground: _Color = ..., - background: _Color = ..., + activeforeground: str = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., - cursor: _Cursor = ..., - disabledforeground: _Color = ..., - fg: _Color = ..., + cursor: _Cursor = "arrow", + disabledforeground: str = ..., + fg: str = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., name: str = ..., - postcommand: Callable[[], object] | str = ..., + postcommand: Callable[[], object] | str = "", relief: _Relief = ..., - selectcolor: _Color = ..., - takefocus: _TakeFocusValue = ..., - tearoff: int = ..., + selectcolor: str = ..., + takefocus: _TakeFocusValue = 0, + tearoff: bool | Literal[0, 1] = 1, # I guess tearoffcommand arguments are supposed to be widget objects, # but they are widget name strings. Use nametowidget() to handle the # arguments of tearoffcommand. - tearoffcommand: Callable[[str, str], object] | str = ..., - title: str = ..., - type: Literal["menubar", "tearoff", "normal"] = ..., + tearoffcommand: Callable[[str, str], object] | str = "", + title: str = "", + type: Literal["menubar", "tearoff", "normal"] = "normal", ) -> None: ... @overload def configure( self, cnf: dict[str, Any] | None = None, *, - activebackground: _Color = ..., + activebackground: str = ..., activeborderwidth: _ScreenUnits = ..., - activeforeground: _Color = ..., - background: _Color = ..., + activeforeground: str = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., cursor: _Cursor = ..., - disabledforeground: _Color = ..., - fg: _Color = ..., + disabledforeground: str = ..., + fg: str = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., postcommand: Callable[[], object] | str = ..., relief: _Relief = ..., - selectcolor: _Color = ..., + selectcolor: str = ..., takefocus: _TakeFocusValue = ..., tearoff: bool = ..., tearoffcommand: Callable[[str, str], object] | str = ..., @@ -2281,22 +2286,22 @@ class Menu(Widget): config = configure def tk_popup(self, x: int, y: int, entry: str | int = "") -> None: ... def activate(self, index: str | int) -> None: ... - def add(self, itemType, cnf=..., **kw): ... # docstring says "Internal function." - def insert(self, index, itemType, cnf=..., **kw): ... # docstring says "Internal function." + def add(self, itemType, cnf={}, **kw): ... # docstring says "Internal function." + def insert(self, index, itemType, cnf={}, **kw): ... # docstring says "Internal function." def add_cascade( self, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, accelerator: str = ..., - activebackground: _Color = ..., - activeforeground: _Color = ..., - background: _Color = ..., - bitmap: _Bitmap = ..., + activebackground: str = ..., + activeforeground: str = ..., + background: str = ..., + bitmap: str = ..., columnbreak: int = ..., command: Callable[[], object] | str = ..., compound: _Compound = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., hidemargin: bool = ..., image: _ImageSpec = ..., label: str = ..., @@ -2306,25 +2311,25 @@ class Menu(Widget): ) -> None: ... def add_checkbutton( self, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, accelerator: str = ..., - activebackground: _Color = ..., - activeforeground: _Color = ..., - background: _Color = ..., - bitmap: _Bitmap = ..., + activebackground: str = ..., + activeforeground: str = ..., + background: str = ..., + bitmap: str = ..., columnbreak: int = ..., command: Callable[[], object] | str = ..., compound: _Compound = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., hidemargin: bool = ..., image: _ImageSpec = ..., indicatoron: bool = ..., label: str = ..., offvalue: Any = ..., onvalue: Any = ..., - selectcolor: _Color = ..., + selectcolor: str = ..., selectimage: _ImageSpec = ..., state: Literal["normal", "active", "disabled"] = ..., underline: int = ..., @@ -2332,18 +2337,18 @@ class Menu(Widget): ) -> None: ... def add_command( self, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, accelerator: str = ..., - activebackground: _Color = ..., - activeforeground: _Color = ..., - background: _Color = ..., - bitmap: _Bitmap = ..., + activebackground: str = ..., + activeforeground: str = ..., + background: str = ..., + bitmap: str = ..., columnbreak: int = ..., command: Callable[[], object] | str = ..., compound: _Compound = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., hidemargin: bool = ..., image: _ImageSpec = ..., label: str = ..., @@ -2352,45 +2357,45 @@ class Menu(Widget): ) -> None: ... def add_radiobutton( self, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, accelerator: str = ..., - activebackground: _Color = ..., - activeforeground: _Color = ..., - background: _Color = ..., - bitmap: _Bitmap = ..., + activebackground: str = ..., + activeforeground: str = ..., + background: str = ..., + bitmap: str = ..., columnbreak: int = ..., command: Callable[[], object] | str = ..., compound: _Compound = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., hidemargin: bool = ..., image: _ImageSpec = ..., indicatoron: bool = ..., label: str = ..., - selectcolor: _Color = ..., + selectcolor: str = ..., selectimage: _ImageSpec = ..., state: Literal["normal", "active", "disabled"] = ..., underline: int = ..., value: Any = ..., variable: Variable = ..., ) -> None: ... - def add_separator(self, cnf: dict[str, Any] | None = ..., *, background: _Color = ...) -> None: ... + def add_separator(self, cnf: dict[str, Any] | None = {}, *, background: str = ...) -> None: ... def insert_cascade( self, index: str | int, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, accelerator: str = ..., - activebackground: _Color = ..., - activeforeground: _Color = ..., - background: _Color = ..., - bitmap: _Bitmap = ..., + activebackground: str = ..., + activeforeground: str = ..., + background: str = ..., + bitmap: str = ..., columnbreak: int = ..., command: Callable[[], object] | str = ..., compound: _Compound = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., hidemargin: bool = ..., image: _ImageSpec = ..., label: str = ..., @@ -2401,25 +2406,25 @@ class Menu(Widget): def insert_checkbutton( self, index: str | int, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, accelerator: str = ..., - activebackground: _Color = ..., - activeforeground: _Color = ..., - background: _Color = ..., - bitmap: _Bitmap = ..., + activebackground: str = ..., + activeforeground: str = ..., + background: str = ..., + bitmap: str = ..., columnbreak: int = ..., command: Callable[[], object] | str = ..., compound: _Compound = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., hidemargin: bool = ..., image: _ImageSpec = ..., indicatoron: bool = ..., label: str = ..., offvalue: Any = ..., onvalue: Any = ..., - selectcolor: _Color = ..., + selectcolor: str = ..., selectimage: _ImageSpec = ..., state: Literal["normal", "active", "disabled"] = ..., underline: int = ..., @@ -2428,18 +2433,18 @@ class Menu(Widget): def insert_command( self, index: str | int, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, accelerator: str = ..., - activebackground: _Color = ..., - activeforeground: _Color = ..., - background: _Color = ..., - bitmap: _Bitmap = ..., + activebackground: str = ..., + activeforeground: str = ..., + background: str = ..., + bitmap: str = ..., columnbreak: int = ..., command: Callable[[], object] | str = ..., compound: _Compound = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., hidemargin: bool = ..., image: _ImageSpec = ..., label: str = ..., @@ -2449,30 +2454,30 @@ class Menu(Widget): def insert_radiobutton( self, index: str | int, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, accelerator: str = ..., - activebackground: _Color = ..., - activeforeground: _Color = ..., - background: _Color = ..., - bitmap: _Bitmap = ..., + activebackground: str = ..., + activeforeground: str = ..., + background: str = ..., + bitmap: str = ..., columnbreak: int = ..., command: Callable[[], object] | str = ..., compound: _Compound = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., hidemargin: bool = ..., image: _ImageSpec = ..., indicatoron: bool = ..., label: str = ..., - selectcolor: _Color = ..., + selectcolor: str = ..., selectimage: _ImageSpec = ..., state: Literal["normal", "active", "disabled"] = ..., underline: int = ..., value: Any = ..., variable: Variable = ..., ) -> None: ... - def insert_separator(self, index: str | int, cnf: dict[str, Any] | None = ..., *, background: _Color = ...) -> None: ... + def insert_separator(self, index: str | int, cnf: dict[str, Any] | None = {}, *, background: str = ...) -> None: ... def delete(self, index1: str | int, index2: str | int | None = None) -> None: ... def entrycget(self, index: str | int, option: str) -> Any: ... def entryconfigure( @@ -2491,68 +2496,68 @@ class Menubutton(Widget): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, - activebackground: _Color = ..., - activeforeground: _Color = ..., + activebackground: str = ..., + activeforeground: str = ..., anchor: _Anchor = ..., - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., - bitmap: _Bitmap = ..., + bg: str = ..., + bitmap: str = "", border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., - compound: _Compound = ..., - cursor: _Cursor = ..., - direction: Literal["above", "below", "left", "right", "flush"] = ..., - disabledforeground: _Color = ..., - fg: _Color = ..., - font: _FontDescription = ..., - foreground: _Color = ..., - height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., - highlightthickness: _ScreenUnits = ..., - image: _ImageSpec = ..., + compound: _Compound = "none", + cursor: _Cursor = "", + direction: Literal["above", "below", "left", "right", "flush"] = "below", + disabledforeground: str = ..., + fg: str = ..., + font: _FontDescription = "TkDefaultFont", + foreground: str = ..., + height: _ScreenUnits = 0, + highlightbackground: str = ..., + highlightcolor: str = ..., + highlightthickness: _ScreenUnits = 0, + image: _ImageSpec = "", indicatoron: bool = ..., justify: Literal["left", "center", "right"] = ..., menu: Menu = ..., name: str = ..., padx: _ScreenUnits = ..., pady: _ScreenUnits = ..., - relief: _Relief = ..., - state: Literal["normal", "active", "disabled"] = ..., - takefocus: _TakeFocusValue = ..., - text: float | str = ..., + relief: _Relief = "flat", + state: Literal["normal", "active", "disabled"] = "normal", + takefocus: _TakeFocusValue = 0, + text: float | str = "", textvariable: Variable = ..., - underline: int = ..., - width: _ScreenUnits = ..., - wraplength: _ScreenUnits = ..., + underline: int = -1, + width: _ScreenUnits = 0, + wraplength: _ScreenUnits = 0, ) -> None: ... @overload def configure( self, cnf: dict[str, Any] | None = None, *, - activebackground: _Color = ..., - activeforeground: _Color = ..., + activebackground: str = ..., + activeforeground: str = ..., anchor: _Anchor = ..., - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., - bitmap: _Bitmap = ..., + bg: str = ..., + bitmap: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., compound: _Compound = ..., cursor: _Cursor = ..., direction: Literal["above", "below", "left", "right", "flush"] = ..., - disabledforeground: _Color = ..., - fg: _Color = ..., + disabledforeground: str = ..., + fg: str = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., image: _ImageSpec = ..., indicatoron: bool = ..., @@ -2577,32 +2582,32 @@ class Message(Widget): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., - *, - anchor: _Anchor = ..., - aspect: int = ..., - background: _Color = ..., - bd: _ScreenUnits = ..., - bg: _Color = ..., - border: _ScreenUnits = ..., - borderwidth: _ScreenUnits = ..., - cursor: _Cursor = ..., - fg: _Color = ..., - font: _FontDescription = ..., - foreground: _Color = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., - highlightthickness: _ScreenUnits = ..., - justify: Literal["left", "center", "right"] = ..., + cnf: dict[str, Any] | None = {}, + *, + anchor: _Anchor = "center", + aspect: int = 150, + background: str = ..., + bd: _ScreenUnits = 1, + bg: str = ..., + border: _ScreenUnits = 1, + borderwidth: _ScreenUnits = 1, + cursor: _Cursor = "", + fg: str = ..., + font: _FontDescription = "TkDefaultFont", + foreground: str = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., + highlightthickness: _ScreenUnits = 0, + justify: Literal["left", "center", "right"] = "left", name: str = ..., padx: _ScreenUnits = ..., pady: _ScreenUnits = ..., - relief: _Relief = ..., - takefocus: _TakeFocusValue = ..., - text: float | str = ..., + relief: _Relief = "flat", + takefocus: _TakeFocusValue = 0, + text: float | str = "", textvariable: Variable = ..., # there's width but no height - width: _ScreenUnits = ..., + width: _ScreenUnits = 0, ) -> None: ... @overload def configure( @@ -2611,17 +2616,17 @@ class Message(Widget): *, anchor: _Anchor = ..., aspect: int = ..., - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., cursor: _Cursor = ..., - fg: _Color = ..., + fg: str = ..., font: _FontDescription = ..., - foreground: _Color = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + foreground: str = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., justify: Literal["left", "center", "right"] = ..., padx: _ScreenUnits = ..., @@ -2640,85 +2645,85 @@ class Radiobutton(Widget): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, - activebackground: _Color = ..., - activeforeground: _Color = ..., - anchor: _Anchor = ..., - background: _Color = ..., + activebackground: str = ..., + activeforeground: str = ..., + anchor: _Anchor = "center", + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., - bitmap: _Bitmap = ..., + bg: str = ..., + bitmap: str = "", border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., - command: _ButtonCommand = ..., - compound: _Compound = ..., - cursor: _Cursor = ..., - disabledforeground: _Color = ..., - fg: _Color = ..., - font: _FontDescription = ..., - foreground: _Color = ..., - height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., - highlightthickness: _ScreenUnits = ..., - image: _ImageSpec = ..., - indicatoron: bool = ..., - justify: Literal["left", "center", "right"] = ..., + command: _ButtonCommand = "", + compound: _Compound = "none", + cursor: _Cursor = "", + disabledforeground: str = ..., + fg: str = ..., + font: _FontDescription = "TkDefaultFont", + foreground: str = ..., + height: _ScreenUnits = 0, + highlightbackground: str = ..., + highlightcolor: str = ..., + highlightthickness: _ScreenUnits = 1, + image: _ImageSpec = "", + indicatoron: bool = True, + justify: Literal["left", "center", "right"] = "center", name: str = ..., offrelief: _Relief = ..., - overrelief: _Relief = ..., - padx: _ScreenUnits = ..., - pady: _ScreenUnits = ..., - relief: _Relief = ..., - selectcolor: _Color = ..., - selectimage: _ImageSpec = ..., - state: Literal["normal", "active", "disabled"] = ..., - takefocus: _TakeFocusValue = ..., - text: float | str = ..., + overrelief: _Relief | Literal[""] = "", + padx: _ScreenUnits = 1, + pady: _ScreenUnits = 1, + relief: _Relief = "flat", + selectcolor: str = ..., + selectimage: _ImageSpec = "", + state: Literal["normal", "active", "disabled"] = "normal", + takefocus: _TakeFocusValue = "", + text: float | str = "", textvariable: Variable = ..., - tristateimage: _ImageSpec = ..., - tristatevalue: Any = ..., - underline: int = ..., - value: Any = ..., + tristateimage: _ImageSpec = "", + tristatevalue: Any = "", + underline: int = -1, + value: Any = "", variable: Variable | Literal[""] = ..., - width: _ScreenUnits = ..., - wraplength: _ScreenUnits = ..., + width: _ScreenUnits = 0, + wraplength: _ScreenUnits = 0, ) -> None: ... @overload def configure( self, cnf: dict[str, Any] | None = None, *, - activebackground: _Color = ..., - activeforeground: _Color = ..., + activebackground: str = ..., + activeforeground: str = ..., anchor: _Anchor = ..., - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., - bitmap: _Bitmap = ..., + bg: str = ..., + bitmap: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., command: _ButtonCommand = ..., compound: _Compound = ..., cursor: _Cursor = ..., - disabledforeground: _Color = ..., - fg: _Color = ..., + disabledforeground: str = ..., + fg: str = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., image: _ImageSpec = ..., indicatoron: bool = ..., justify: Literal["left", "center", "right"] = ..., offrelief: _Relief = ..., - overrelief: _Relief = ..., + overrelief: _Relief | Literal[""] = ..., padx: _ScreenUnits = ..., pady: _ScreenUnits = ..., relief: _Relief = ..., - selectcolor: _Color = ..., + selectcolor: str = ..., selectimage: _ImageSpec = ..., state: Literal["normal", "active", "disabled"] = ..., takefocus: _TakeFocusValue = ..., @@ -2744,66 +2749,66 @@ class Scale(Widget): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., - *, - activebackground: _Color = ..., - background: _Color = ..., - bd: _ScreenUnits = ..., - bg: _Color = ..., - bigincrement: float = ..., - border: _ScreenUnits = ..., - borderwidth: _ScreenUnits = ..., + cnf: dict[str, Any] | None = {}, + *, + activebackground: str = ..., + background: str = ..., + bd: _ScreenUnits = 1, + bg: str = ..., + bigincrement: float = 0.0, + border: _ScreenUnits = 1, + borderwidth: _ScreenUnits = 1, # don't know why the callback gets string instead of float - command: str | Callable[[str], object] = ..., - cursor: _Cursor = ..., - digits: int = ..., - fg: _Color = ..., - font: _FontDescription = ..., - foreground: _Color = ..., - from_: float = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + command: str | Callable[[str], object] = "", + cursor: _Cursor = "", + digits: int = 0, + fg: str = ..., + font: _FontDescription = "TkDefaultFont", + foreground: str = ..., + from_: float = 0.0, + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., - label: str = ..., - length: _ScreenUnits = ..., + label: str = "", + length: _ScreenUnits = 100, name: str = ..., - orient: Literal["horizontal", "vertical"] = ..., - relief: _Relief = ..., - repeatdelay: int = ..., - repeatinterval: int = ..., - resolution: float = ..., - showvalue: bool = ..., - sliderlength: _ScreenUnits = ..., - sliderrelief: _Relief = ..., - state: Literal["normal", "active", "disabled"] = ..., - takefocus: _TakeFocusValue = ..., - tickinterval: float = ..., - to: float = ..., - troughcolor: _Color = ..., + orient: Literal["horizontal", "vertical"] = "vertical", + relief: _Relief = "flat", + repeatdelay: int = 300, + repeatinterval: int = 100, + resolution: float = 1.0, + showvalue: bool = True, + sliderlength: _ScreenUnits = 30, + sliderrelief: _Relief = "raised", + state: Literal["normal", "active", "disabled"] = "normal", + takefocus: _TakeFocusValue = "", + tickinterval: float = 0.0, + to: float = 100.0, + troughcolor: str = ..., variable: IntVar | DoubleVar = ..., - width: _ScreenUnits = ..., + width: _ScreenUnits = 15, ) -> None: ... @overload def configure( self, cnf: dict[str, Any] | None = None, *, - activebackground: _Color = ..., - background: _Color = ..., + activebackground: str = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., bigincrement: float = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., command: str | Callable[[str], object] = ..., cursor: _Cursor = ..., digits: int = ..., - fg: _Color = ..., + fg: str = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., from_: float = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., label: str = ..., length: _ScreenUnits = ..., @@ -2819,7 +2824,7 @@ class Scale(Widget): takefocus: _TakeFocusValue = ..., tickinterval: float = ..., to: float = ..., - troughcolor: _Color = ..., + troughcolor: str = ..., variable: IntVar | DoubleVar = ..., width: _ScreenUnits = ..., ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @@ -2835,33 +2840,33 @@ class Scrollbar(Widget): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, - activebackground: _Color = ..., - activerelief: _Relief = ..., - background: _Color = ..., + activebackground: str = ..., + activerelief: _Relief = "raised", + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., # There are many ways how the command may get called. Search for # 'SCROLLING COMMANDS' in scrollbar man page. There doesn't seem to # be any way to specify an overloaded callback function, so we say # that it can take any args while it can't in reality. - command: Callable[..., tuple[float, float] | None] | str = ..., - cursor: _Cursor = ..., - elementborderwidth: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., - highlightthickness: _ScreenUnits = ..., - jump: bool = ..., + command: Callable[..., tuple[float, float] | None] | str = "", + cursor: _Cursor = "", + elementborderwidth: _ScreenUnits = -1, + highlightbackground: str = ..., + highlightcolor: str = ..., + highlightthickness: _ScreenUnits = 0, + jump: bool = False, name: str = ..., - orient: Literal["horizontal", "vertical"] = ..., + orient: Literal["horizontal", "vertical"] = "vertical", relief: _Relief = ..., - repeatdelay: int = ..., - repeatinterval: int = ..., - takefocus: _TakeFocusValue = ..., - troughcolor: _Color = ..., + repeatdelay: int = 300, + repeatinterval: int = 100, + takefocus: _TakeFocusValue = "", + troughcolor: str = ..., width: _ScreenUnits = ..., ) -> None: ... @overload @@ -2869,18 +2874,18 @@ class Scrollbar(Widget): self, cnf: dict[str, Any] | None = None, *, - activebackground: _Color = ..., + activebackground: str = ..., activerelief: _Relief = ..., - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., command: Callable[..., tuple[float, float] | None] | str = ..., cursor: _Cursor = ..., elementborderwidth: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., jump: bool = ..., orient: Literal["horizontal", "vertical"] = ..., @@ -2888,7 +2893,7 @@ class Scrollbar(Widget): repeatdelay: int = ..., repeatinterval: int = ..., takefocus: _TakeFocusValue = ..., - troughcolor: _Color = ..., + troughcolor: str = ..., width: _ScreenUnits = ..., ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload @@ -2899,7 +2904,7 @@ class Scrollbar(Widget): def fraction(self, x: int, y: int) -> float: ... def identify(self, x: int, y: int) -> Literal["arrow1", "arrow2", "slider", "trough1", "trough2", ""]: ... def get(self) -> tuple[float, float, float, float] | tuple[float, float]: ... - def set(self, first: float, last: float) -> None: ... + def set(self, first: float | str, last: float | str) -> None: ... _TextIndex: TypeAlias = _tkinter.Tcl_Obj | str | float | Misc @@ -2907,58 +2912,58 @@ class Text(Widget, XView, YView): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, - autoseparators: bool = ..., - background: _Color = ..., + autoseparators: bool = True, + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., - blockcursor: bool = ..., + bg: str = ..., + blockcursor: bool = False, border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., - cursor: _Cursor = ..., - endline: int | Literal[""] = ..., - exportselection: bool = ..., - fg: _Color = ..., - font: _FontDescription = ..., - foreground: _Color = ..., + cursor: _Cursor = "xterm", + endline: int | Literal[""] = "", + exportselection: bool = True, + fg: str = ..., + font: _FontDescription = "TkFixedFont", + foreground: str = ..., # width is always int, but height is allowed to be ScreenUnits. # This doesn't make any sense to me, and this isn't documented. # The docs seem to say that both should be integers. - height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + height: _ScreenUnits = 24, + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., - inactiveselectbackground: _Color = ..., - insertbackground: _Color = ..., - insertborderwidth: _ScreenUnits = ..., - insertofftime: int = ..., - insertontime: int = ..., - insertunfocussed: Literal["none", "hollow", "solid"] = ..., + inactiveselectbackground: str = ..., + insertbackground: str = ..., + insertborderwidth: _ScreenUnits = 0, + insertofftime: int = 300, + insertontime: int = 600, + insertunfocussed: Literal["none", "hollow", "solid"] = "none", insertwidth: _ScreenUnits = ..., - maxundo: int = ..., + maxundo: int = 0, name: str = ..., - padx: _ScreenUnits = ..., - pady: _ScreenUnits = ..., + padx: _ScreenUnits = 1, + pady: _ScreenUnits = 1, relief: _Relief = ..., - selectbackground: _Color = ..., + selectbackground: str = ..., selectborderwidth: _ScreenUnits = ..., - selectforeground: _Color = ..., - setgrid: bool = ..., - spacing1: _ScreenUnits = ..., - spacing2: _ScreenUnits = ..., - spacing3: _ScreenUnits = ..., - startline: int | Literal[""] = ..., - state: Literal["normal", "disabled"] = ..., + selectforeground: str = ..., + setgrid: bool = False, + spacing1: _ScreenUnits = 0, + spacing2: _ScreenUnits = 0, + spacing3: _ScreenUnits = 0, + startline: int | Literal[""] = "", + state: Literal["normal", "disabled"] = "normal", # Literal inside Tuple doesn't actually work - tabs: _ScreenUnits | str | tuple[_ScreenUnits | str, ...] = ..., - tabstyle: Literal["tabular", "wordprocessor"] = ..., - takefocus: _TakeFocusValue = ..., - undo: bool = ..., - width: int = ..., - wrap: Literal["none", "char", "word"] = ..., - xscrollcommand: _XYScrollCommand = ..., - yscrollcommand: _XYScrollCommand = ..., + tabs: _ScreenUnits | str | tuple[_ScreenUnits | str, ...] = "", + tabstyle: Literal["tabular", "wordprocessor"] = "tabular", + takefocus: _TakeFocusValue = "", + undo: bool = False, + width: int = 80, + wrap: Literal["none", "char", "word"] = "char", + xscrollcommand: _XYScrollCommand = "", + yscrollcommand: _XYScrollCommand = "", ) -> None: ... @overload def configure( @@ -2966,24 +2971,24 @@ class Text(Widget, XView, YView): cnf: dict[str, Any] | None = None, *, autoseparators: bool = ..., - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., blockcursor: bool = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., cursor: _Cursor = ..., endline: int | Literal[""] = ..., exportselection: bool = ..., - fg: _Color = ..., + fg: str = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., - inactiveselectbackground: _Color = ..., - insertbackground: _Color = ..., + inactiveselectbackground: str = ..., + insertbackground: str = ..., insertborderwidth: _ScreenUnits = ..., insertofftime: int = ..., insertontime: int = ..., @@ -2993,9 +2998,9 @@ class Text(Widget, XView, YView): padx: _ScreenUnits = ..., pady: _ScreenUnits = ..., relief: _Relief = ..., - selectbackground: _Color = ..., + selectbackground: str = ..., selectborderwidth: _ScreenUnits = ..., - selectforeground: _Color = ..., + selectforeground: str = ..., setgrid: bool = ..., spacing1: _ScreenUnits = ..., spacing2: _ScreenUnits = ..., @@ -3075,11 +3080,40 @@ class Text(Widget, XView, YView): def edit_separator(self) -> None: ... # actually returns empty string def edit_undo(self) -> None: ... # actually returns empty string def get(self, index1: _TextIndex, index2: _TextIndex | None = None) -> str: ... - # TODO: image_* methods - def image_cget(self, index, option): ... - def image_configure(self, index, cnf: Incomplete | None = None, **kw): ... - def image_create(self, index, cnf=..., **kw): ... - def image_names(self): ... + @overload + def image_cget(self, index: _TextIndex, option: Literal["image", "name"]) -> str: ... + @overload + def image_cget(self, index: _TextIndex, option: Literal["padx", "pady"]) -> int: ... + @overload + def image_cget(self, index: _TextIndex, option: Literal["align"]) -> Literal["baseline", "bottom", "center", "top"]: ... + @overload + def image_cget(self, index: _TextIndex, option: str) -> Any: ... + @overload + def image_configure(self, index: _TextIndex, cnf: str) -> tuple[str, str, str, str, str | int]: ... + @overload + def image_configure( + self, + index: _TextIndex, + cnf: dict[str, Any] | None = {}, + *, + align: Literal["baseline", "bottom", "center", "top"] = ..., + image: _ImageSpec = ..., + name: str = ..., + padx: _ScreenUnits = ..., + pady: _ScreenUnits = ..., + ) -> dict[str, tuple[str, str, str, str, str | int]] | None: ... + def image_create( + self, + index: _TextIndex, + cnf: dict[str, Any] | None = {}, + *, + align: Literal["baseline", "bottom", "center", "top"] = ..., + image: _ImageSpec = ..., + name: str = ..., + padx: _ScreenUnits = ..., + pady: _ScreenUnits = ..., + ) -> str: ... + def image_names(self) -> tuple[str, ...]: ... def index(self, index: _TextIndex) -> str: ... def insert(self, index: _TextIndex, chars: str, *args: str | list[str] | tuple[str, ...]) -> None: ... @overload @@ -3092,7 +3126,7 @@ class Text(Widget, XView, YView): def mark_next(self, index: _TextIndex) -> str | None: ... def mark_previous(self, index: _TextIndex) -> str | None: ... # **kw of peer_create is same as the kwargs of Text.__init__ - def peer_create(self, newPathName: str | Text, cnf: dict[str, Any] = ..., **kw) -> None: ... + def peer_create(self, newPathName: str | Text, cnf: dict[str, Any] = {}, **kw) -> None: ... def peer_names(self) -> tuple[_tkinter.Tcl_Obj, ...]: ... def replace(self, index1: _TextIndex, index2: _TextIndex, chars: str, *args: str | list[str] | tuple[str, ...]) -> None: ... def scan_mark(self, x: int, y: int) -> None: ... @@ -3132,39 +3166,39 @@ class Text(Widget, XView, YView): tagName: str, cnf: dict[str, Any] | None = None, *, - background: _Color = ..., - bgstipple: _Bitmap = ..., + background: str = ..., + bgstipple: str = ..., borderwidth: _ScreenUnits = ..., border: _ScreenUnits = ..., # alias for borderwidth elide: bool = ..., - fgstipple: _Bitmap = ..., + fgstipple: str = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., justify: Literal["left", "right", "center"] = ..., lmargin1: _ScreenUnits = ..., lmargin2: _ScreenUnits = ..., - lmargincolor: _Color = ..., + lmargincolor: str = ..., offset: _ScreenUnits = ..., overstrike: bool = ..., - overstrikefg: _Color = ..., + overstrikefg: str = ..., relief: _Relief = ..., rmargin: _ScreenUnits = ..., - rmargincolor: _Color = ..., - selectbackground: _Color = ..., - selectforeground: _Color = ..., + rmargincolor: str = ..., + selectbackground: str = ..., + selectforeground: str = ..., spacing1: _ScreenUnits = ..., spacing2: _ScreenUnits = ..., spacing3: _ScreenUnits = ..., tabs: Any = ..., # the exact type is kind of complicated, see manual page tabstyle: Literal["tabular", "wordprocessor"] = ..., underline: bool = ..., - underlinefg: _Color = ..., + underlinefg: str = ..., wrap: Literal["none", "char", "word"] = ..., # be careful with "none" vs None ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def tag_configure(self, tagName: str, cnf: str) -> tuple[str, str, str, Any, Any]: ... tag_config = tag_configure - def tag_delete(self, __first_tag_name: str, *tagNames: str) -> None: ... # error if no tag names given + def tag_delete(self, first_tag_name: str, /, *tagNames: str) -> None: ... # error if no tag names given def tag_lower(self, tagName: str, belowThis: str | None = None) -> None: ... def tag_names(self, index: _TextIndex | None = None) -> tuple[str, ...]: ... def tag_nextrange( @@ -3177,12 +3211,45 @@ class Text(Widget, XView, YView): def tag_ranges(self, tagName: str) -> tuple[_tkinter.Tcl_Obj, ...]: ... # tag_remove and tag_delete are different def tag_remove(self, tagName: str, index1: _TextIndex, index2: _TextIndex | None = None) -> None: ... - # TODO: window_* methods - def window_cget(self, index, option): ... - def window_configure(self, index, cnf: Incomplete | None = None, **kw): ... + @overload + def window_cget(self, index: _TextIndex, option: Literal["padx", "pady"]) -> int: ... + @overload + def window_cget(self, index: _TextIndex, option: Literal["stretch"]) -> bool: ... # actually returns Literal[0, 1] + @overload + def window_cget(self, index: _TextIndex, option: Literal["align"]) -> Literal["baseline", "bottom", "center", "top"]: ... + @overload # window is set to a widget, but read as the string name. + def window_cget(self, index: _TextIndex, option: Literal["create", "window"]) -> str: ... + @overload + def window_cget(self, index: _TextIndex, option: str) -> Any: ... + @overload + def window_configure(self, index: _TextIndex, cnf: str) -> tuple[str, str, str, str, str | int]: ... + @overload + def window_configure( + self, + index: _TextIndex, + cnf: dict[str, Any] | None = None, + *, + align: Literal["baseline", "bottom", "center", "top"] = ..., + create: str = ..., + padx: _ScreenUnits = ..., + pady: _ScreenUnits = ..., + stretch: bool | Literal[0, 1] = ..., + window: Misc | str = ..., + ) -> dict[str, tuple[str, str, str, str, str | int]] | None: ... window_config = window_configure - def window_create(self, index, cnf=..., **kw) -> None: ... - def window_names(self): ... + def window_create( + self, + index: _TextIndex, + cnf: dict[str, Any] | None = {}, + *, + align: Literal["baseline", "bottom", "center", "top"] = ..., + create: str = ..., + padx: _ScreenUnits = ..., + pady: _ScreenUnits = ..., + stretch: bool | Literal[0, 1] = ..., + window: Misc | str = ..., + ) -> None: ... + def window_names(self) -> tuple[str, ...]: ... def yview_pickplace(self, *what): ... # deprecated class _setit: @@ -3206,16 +3273,23 @@ class OptionMenu(Menubutton): # configure, config, cget are inherited from Menubutton # destroy and __getitem__ are overridden, signature does not change -class _Image(Protocol): - tk: _tkinter.TkappType - def height(self) -> int: ... - def width(self) -> int: ... +# Marker to indicate that it is a valid bitmap/photo image. PIL implements compatible versions +# which don't share a class hierarchy. The actual API is a __str__() which returns a valid name, +# not something that type checkers can detect. +@type_check_only +class _Image: ... -class Image: +@type_check_only +class _BitmapImageLike(_Image): ... + +@type_check_only +class _PhotoImageLike(_Image): ... + +class Image(_Image): name: Incomplete tk: _tkinter.TkappType def __init__( - self, imgtype, name: Incomplete | None = None, cnf=..., master: Misc | _tkinter.TkappType | None = None, **kw + self, imgtype, name: Incomplete | None = None, cnf={}, master: Misc | _tkinter.TkappType | None = None, **kw ) -> None: ... def __del__(self) -> None: ... def __setitem__(self, key, value) -> None: ... @@ -3226,11 +3300,12 @@ class Image: def type(self): ... def width(self) -> int: ... -class PhotoImage(Image): +class PhotoImage(Image, _PhotoImageLike): + # This should be kept in sync with PIL.ImageTK.PhotoImage.__init__() def __init__( self, name: str | None = None, - cnf: dict[str, Any] = ..., + cnf: dict[str, Any] = {}, master: Misc | _tkinter.TkappType | None = None, *, data: str | bytes = ..., # not same as data argument of put() @@ -3265,30 +3340,30 @@ class PhotoImage(Image): data: ( str | list[str] - | list[list[_Color]] - | list[tuple[_Color, ...]] + | list[list[str]] + | list[tuple[str, ...]] | tuple[str, ...] - | tuple[list[_Color], ...] - | tuple[tuple[_Color, ...], ...] + | tuple[list[str], ...] + | tuple[tuple[str, ...], ...] ), to: tuple[int, int] | None = None, ) -> None: ... def write(self, filename: StrOrBytesPath, format: str | None = None, from_coords: tuple[int, int] | None = None) -> None: ... - if sys.version_info >= (3, 8): - def transparency_get(self, x: int, y: int) -> bool: ... - def transparency_set(self, x: int, y: int, boolean: bool) -> None: ... + def transparency_get(self, x: int, y: int) -> bool: ... + def transparency_set(self, x: int, y: int, boolean: bool) -> None: ... -class BitmapImage(Image): +class BitmapImage(Image, _BitmapImageLike): + # This should be kept in sync with PIL.ImageTK.BitmapImage.__init__() def __init__( self, name: Incomplete | None = None, - cnf: dict[str, Any] = ..., + cnf: dict[str, Any] = {}, master: Misc | _tkinter.TkappType | None = None, *, - background: _Color = ..., + background: str = ..., data: str | bytes = ..., file: StrOrBytesPath = ..., - foreground: _Color = ..., + foreground: str = ..., maskdata: str = ..., maskfile: StrOrBytesPath = ..., ) -> None: ... @@ -3300,91 +3375,91 @@ class Spinbox(Widget, XView): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., + cnf: dict[str, Any] | None = {}, *, - activebackground: _Color = ..., - background: _Color = ..., + activebackground: str = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., - buttonbackground: _Color = ..., - buttoncursor: _Cursor = ..., + buttonbackground: str = ..., + buttoncursor: _Cursor = "", buttondownrelief: _Relief = ..., buttonuprelief: _Relief = ..., # percent substitutions don't seem to be supported, it's similar to Entry's validation stuff - command: Callable[[], object] | str | list[str] | tuple[str, ...] = ..., - cursor: _Cursor = ..., - disabledbackground: _Color = ..., - disabledforeground: _Color = ..., - exportselection: bool = ..., - fg: _Color = ..., - font: _FontDescription = ..., - foreground: _Color = ..., - format: str = ..., - from_: float = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + command: Callable[[], object] | str | list[str] | tuple[str, ...] = "", + cursor: _Cursor = "xterm", + disabledbackground: str = ..., + disabledforeground: str = ..., + exportselection: bool = True, + fg: str = ..., + font: _FontDescription = "TkTextFont", + foreground: str = ..., + format: str = "", + from_: float = 0.0, + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., - increment: float = ..., - insertbackground: _Color = ..., - insertborderwidth: _ScreenUnits = ..., - insertofftime: int = ..., - insertontime: int = ..., + increment: float = 1.0, + insertbackground: str = ..., + insertborderwidth: _ScreenUnits = 0, + insertofftime: int = 300, + insertontime: int = 600, insertwidth: _ScreenUnits = ..., - invalidcommand: _EntryValidateCommand = ..., - invcmd: _EntryValidateCommand = ..., - justify: Literal["left", "center", "right"] = ..., + invalidcommand: _EntryValidateCommand = "", + invcmd: _EntryValidateCommand = "", + justify: Literal["left", "center", "right"] = "left", name: str = ..., - readonlybackground: _Color = ..., - relief: _Relief = ..., - repeatdelay: int = ..., - repeatinterval: int = ..., - selectbackground: _Color = ..., + readonlybackground: str = ..., + relief: _Relief = "sunken", + repeatdelay: int = 400, + repeatinterval: int = 100, + selectbackground: str = ..., selectborderwidth: _ScreenUnits = ..., - selectforeground: _Color = ..., - state: Literal["normal", "disabled", "readonly"] = ..., - takefocus: _TakeFocusValue = ..., + selectforeground: str = ..., + state: Literal["normal", "disabled", "readonly"] = "normal", + takefocus: _TakeFocusValue = "", textvariable: Variable = ..., - to: float = ..., - validate: Literal["none", "focus", "focusin", "focusout", "key", "all"] = ..., - validatecommand: _EntryValidateCommand = ..., - vcmd: _EntryValidateCommand = ..., + to: float = 0.0, + validate: Literal["none", "focus", "focusin", "focusout", "key", "all"] = "none", + validatecommand: _EntryValidateCommand = "", + vcmd: _EntryValidateCommand = "", values: list[str] | tuple[str, ...] = ..., - width: int = ..., - wrap: bool = ..., - xscrollcommand: _XYScrollCommand = ..., + width: int = 20, + wrap: bool = False, + xscrollcommand: _XYScrollCommand = "", ) -> None: ... @overload def configure( self, cnf: dict[str, Any] | None = None, *, - activebackground: _Color = ..., - background: _Color = ..., + activebackground: str = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., - buttonbackground: _Color = ..., + buttonbackground: str = ..., buttoncursor: _Cursor = ..., buttondownrelief: _Relief = ..., buttonuprelief: _Relief = ..., command: Callable[[], object] | str | list[str] | tuple[str, ...] = ..., cursor: _Cursor = ..., - disabledbackground: _Color = ..., - disabledforeground: _Color = ..., + disabledbackground: str = ..., + disabledforeground: str = ..., exportselection: bool = ..., - fg: _Color = ..., + fg: str = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., format: str = ..., from_: float = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., increment: float = ..., - insertbackground: _Color = ..., + insertbackground: str = ..., insertborderwidth: _ScreenUnits = ..., insertofftime: int = ..., insertontime: int = ..., @@ -3392,13 +3467,13 @@ class Spinbox(Widget, XView): invalidcommand: _EntryValidateCommand = ..., invcmd: _EntryValidateCommand = ..., justify: Literal["left", "center", "right"] = ..., - readonlybackground: _Color = ..., + readonlybackground: str = ..., relief: _Relief = ..., repeatdelay: int = ..., repeatinterval: int = ..., - selectbackground: _Color = ..., + selectbackground: str = ..., selectborderwidth: _ScreenUnits = ..., - selectforeground: _Color = ..., + selectforeground: str = ..., state: Literal["normal", "disabled", "readonly"] = ..., takefocus: _TakeFocusValue = ..., textvariable: Variable = ..., @@ -3419,8 +3494,8 @@ class Spinbox(Widget, XView): def get(self) -> str: ... def icursor(self, index): ... def identify(self, x: int, y: int) -> Literal["", "buttondown", "buttonup", "entry"]: ... - def index(self, index: _EntryIndex) -> int: ... - def insert(self, index: _EntryIndex, s: str) -> Literal[""]: ... + def index(self, index: str | int) -> int: ... + def insert(self, index: str | int, s: str) -> Literal[""]: ... # spinbox.invoke("asdf") gives error mentioning .invoke("none"), but it's not documented def invoke(self, element: Literal["none", "buttonup", "buttondown"]) -> Literal[""]: ... def scan(self, *args): ... @@ -3430,63 +3505,62 @@ class Spinbox(Widget, XView): def selection_adjust(self, index): ... def selection_clear(self): ... def selection_element(self, element: Incomplete | None = None): ... - if sys.version_info >= (3, 8): - def selection_from(self, index: int) -> None: ... - def selection_present(self) -> None: ... - def selection_range(self, start: int, end: int) -> None: ... - def selection_to(self, index: int) -> None: ... + def selection_from(self, index: int) -> None: ... + def selection_present(self) -> None: ... + def selection_range(self, start: int, end: int) -> None: ... + def selection_to(self, index: int) -> None: ... class LabelFrame(Widget): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., - *, - background: _Color = ..., - bd: _ScreenUnits = ..., - bg: _Color = ..., - border: _ScreenUnits = ..., - borderwidth: _ScreenUnits = ..., - class_: str = ..., # can't be changed with configure() - colormap: Literal["new", ""] | Misc = ..., # can't be changed with configure() - container: bool = ..., # undocumented, can't be changed with configure() - cursor: _Cursor = ..., - fg: _Color = ..., - font: _FontDescription = ..., - foreground: _Color = ..., - height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., - highlightthickness: _ScreenUnits = ..., + cnf: dict[str, Any] | None = {}, + *, + background: str = ..., + bd: _ScreenUnits = 2, + bg: str = ..., + border: _ScreenUnits = 2, + borderwidth: _ScreenUnits = 2, + class_: str = "Labelframe", # can't be changed with configure() + colormap: Literal["new", ""] | Misc = "", # can't be changed with configure() + container: bool = False, # undocumented, can't be changed with configure() + cursor: _Cursor = "", + fg: str = ..., + font: _FontDescription = "TkDefaultFont", + foreground: str = ..., + height: _ScreenUnits = 0, + highlightbackground: str = ..., + highlightcolor: str = ..., + highlightthickness: _ScreenUnits = 0, # 'ne' and 'en' are valid labelanchors, but only 'ne' is a valid _Anchor. - labelanchor: Literal["nw", "n", "ne", "en", "e", "es", "se", "s", "sw", "ws", "w", "wn"] = ..., + labelanchor: Literal["nw", "n", "ne", "en", "e", "es", "se", "s", "sw", "ws", "w", "wn"] = "nw", labelwidget: Misc = ..., name: str = ..., - padx: _ScreenUnits = ..., - pady: _ScreenUnits = ..., - relief: _Relief = ..., - takefocus: _TakeFocusValue = ..., - text: float | str = ..., - visual: str | tuple[str, int] = ..., # can't be changed with configure() - width: _ScreenUnits = ..., + padx: _ScreenUnits = 0, + pady: _ScreenUnits = 0, + relief: _Relief = "groove", + takefocus: _TakeFocusValue = 0, + text: float | str = "", + visual: str | tuple[str, int] = "", # can't be changed with configure() + width: _ScreenUnits = 0, ) -> None: ... @overload def configure( self, cnf: dict[str, Any] | None = None, *, - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., cursor: _Cursor = ..., - fg: _Color = ..., + fg: str = ..., font: _FontDescription = ..., - foreground: _Color = ..., + foreground: str = ..., height: _ScreenUnits = ..., - highlightbackground: _Color = ..., - highlightcolor: _Color = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., highlightthickness: _ScreenUnits = ..., labelanchor: Literal["nw", "n", "ne", "en", "e", "es", "se", "s", "sw", "ws", "w", "wn"] = ..., labelwidget: Misc = ..., @@ -3505,39 +3579,39 @@ class PanedWindow(Widget): def __init__( self, master: Misc | None = None, - cnf: dict[str, Any] | None = ..., - *, - background: _Color = ..., - bd: _ScreenUnits = ..., - bg: _Color = ..., - border: _ScreenUnits = ..., - borderwidth: _ScreenUnits = ..., - cursor: _Cursor = ..., - handlepad: _ScreenUnits = ..., - handlesize: _ScreenUnits = ..., - height: _ScreenUnits = ..., + cnf: dict[str, Any] | None = {}, + *, + background: str = ..., + bd: _ScreenUnits = 1, + bg: str = ..., + border: _ScreenUnits = 1, + borderwidth: _ScreenUnits = 1, + cursor: _Cursor = "", + handlepad: _ScreenUnits = 8, + handlesize: _ScreenUnits = 8, + height: _ScreenUnits = "", name: str = ..., - opaqueresize: bool = ..., - orient: Literal["horizontal", "vertical"] = ..., - proxybackground: _Color = ..., - proxyborderwidth: _ScreenUnits = ..., - proxyrelief: _Relief = ..., - relief: _Relief = ..., - sashcursor: _Cursor = ..., - sashpad: _ScreenUnits = ..., - sashrelief: _Relief = ..., - sashwidth: _ScreenUnits = ..., - showhandle: bool = ..., - width: _ScreenUnits = ..., + opaqueresize: bool = True, + orient: Literal["horizontal", "vertical"] = "horizontal", + proxybackground: str = "", + proxyborderwidth: _ScreenUnits = 2, + proxyrelief: _Relief = "flat", + relief: _Relief = "flat", + sashcursor: _Cursor = "", + sashpad: _ScreenUnits = 0, + sashrelief: _Relief = "flat", + sashwidth: _ScreenUnits = 3, + showhandle: bool = False, + width: _ScreenUnits = "", ) -> None: ... @overload def configure( self, cnf: dict[str, Any] | None = None, *, - background: _Color = ..., + background: str = ..., bd: _ScreenUnits = ..., - bg: _Color = ..., + bg: str = ..., border: _ScreenUnits = ..., borderwidth: _ScreenUnits = ..., cursor: _Cursor = ..., @@ -3546,7 +3620,7 @@ class PanedWindow(Widget): height: _ScreenUnits = ..., opaqueresize: bool = ..., orient: Literal["horizontal", "vertical"] = ..., - proxybackground: _Color = ..., + proxybackground: str = ..., proxyborderwidth: _ScreenUnits = ..., proxyrelief: _Relief = ..., relief: _Relief = ..., diff --git a/mypy/typeshed/stdlib/tkinter/colorchooser.pyi b/mypy/typeshed/stdlib/tkinter/colorchooser.pyi index 4300d94f58e8..09bc8cbb4f1e 100644 --- a/mypy/typeshed/stdlib/tkinter/colorchooser.pyi +++ b/mypy/typeshed/stdlib/tkinter/colorchooser.pyi @@ -1,5 +1,5 @@ import sys -from tkinter import Misc, _Color +from tkinter import Misc from tkinter.commondialog import Dialog from typing import ClassVar @@ -11,10 +11,10 @@ class Chooser(Dialog): if sys.version_info >= (3, 9): def askcolor( - color: str | bytes | None = None, *, initialcolor: _Color = ..., parent: Misc = ..., title: str = ... + color: str | bytes | None = None, *, initialcolor: str = ..., parent: Misc = ..., title: str = ... ) -> tuple[None, None] | tuple[tuple[int, int, int], str]: ... else: def askcolor( - color: str | bytes | None = None, *, initialcolor: _Color = ..., parent: Misc = ..., title: str = ... + color: str | bytes | None = None, *, initialcolor: str = ..., parent: Misc = ..., title: str = ... ) -> tuple[None, None] | tuple[tuple[float, float, float], str]: ... diff --git a/mypy/typeshed/stdlib/tkinter/commondialog.pyi b/mypy/typeshed/stdlib/tkinter/commondialog.pyi index eba3ab5be3bd..d06c08df5b76 100644 --- a/mypy/typeshed/stdlib/tkinter/commondialog.pyi +++ b/mypy/typeshed/stdlib/tkinter/commondialog.pyi @@ -10,5 +10,5 @@ class Dialog: command: ClassVar[str | None] master: Incomplete | None options: Mapping[str, Incomplete] - def __init__(self, master: Incomplete | None = None, **options: Incomplete) -> None: ... - def show(self, **options: Incomplete) -> Incomplete: ... + def __init__(self, master: Incomplete | None = None, **options) -> None: ... + def show(self, **options): ... diff --git a/mypy/typeshed/stdlib/tkinter/constants.pyi b/mypy/typeshed/stdlib/tkinter/constants.pyi index 1383b0f9bf4b..74fa72acb0bf 100644 --- a/mypy/typeshed/stdlib/tkinter/constants.pyi +++ b/mypy/typeshed/stdlib/tkinter/constants.pyi @@ -1,4 +1,4 @@ -from typing_extensions import Literal +from typing import Literal # These are not actually bools. See #4669 NO: bool diff --git a/mypy/typeshed/stdlib/tkinter/dialog.pyi b/mypy/typeshed/stdlib/tkinter/dialog.pyi index 8825188c767e..f76732a25460 100644 --- a/mypy/typeshed/stdlib/tkinter/dialog.pyi +++ b/mypy/typeshed/stdlib/tkinter/dialog.pyi @@ -12,5 +12,5 @@ DIALOG_ICON: str class Dialog(Widget): widgetName: str num: int - def __init__(self, master: Incomplete | None = None, cnf: Mapping[str, Any] = ..., **kw: Incomplete) -> None: ... + def __init__(self, master: Incomplete | None = None, cnf: Mapping[str, Any] = {}, **kw) -> None: ... def destroy(self) -> None: ... diff --git a/mypy/typeshed/stdlib/tkinter/dnd.pyi b/mypy/typeshed/stdlib/tkinter/dnd.pyi index 4a6ab42b3e33..d806be74068e 100644 --- a/mypy/typeshed/stdlib/tkinter/dnd.pyi +++ b/mypy/typeshed/stdlib/tkinter/dnd.pyi @@ -6,7 +6,7 @@ if sys.version_info >= (3, 9): __all__ = ["dnd_start", "DndHandler"] class _DndSource(Protocol): - def dnd_end(self, target: Widget | None, event: Event[Misc] | None) -> None: ... + def dnd_end(self, target: Widget | None, event: Event[Misc] | None, /) -> None: ... class DndHandler: root: ClassVar[Tk | None] @@ -15,5 +15,6 @@ class DndHandler: def finish(self, event: Event[Misc] | None, commit: int = 0) -> None: ... def on_motion(self, event: Event[Misc]) -> None: ... def on_release(self, event: Event[Misc]) -> None: ... + def __del__(self) -> None: ... def dnd_start(source: _DndSource, event: Event[Misc]) -> DndHandler | None: ... diff --git a/mypy/typeshed/stdlib/tkinter/filedialog.pyi b/mypy/typeshed/stdlib/tkinter/filedialog.pyi index 10b36e4d3c06..3d62f079178e 100644 --- a/mypy/typeshed/stdlib/tkinter/filedialog.pyi +++ b/mypy/typeshed/stdlib/tkinter/filedialog.pyi @@ -2,8 +2,7 @@ import sys from _typeshed import Incomplete, StrOrBytesPath from collections.abc import Iterable from tkinter import Button, Entry, Frame, Listbox, Misc, Scrollbar, StringVar, Toplevel, commondialog -from typing import IO, ClassVar -from typing_extensions import Literal +from typing import IO, ClassVar, Literal if sys.version_info >= (3, 9): __all__ = [ diff --git a/mypy/typeshed/stdlib/tkinter/font.pyi b/mypy/typeshed/stdlib/tkinter/font.pyi index 0a557e921914..46625014d4ac 100644 --- a/mypy/typeshed/stdlib/tkinter/font.pyi +++ b/mypy/typeshed/stdlib/tkinter/font.pyi @@ -1,8 +1,8 @@ import _tkinter import sys import tkinter -from typing import Any, overload -from typing_extensions import Literal, TypeAlias, TypedDict +from typing import Any, Literal, TypedDict, overload +from typing_extensions import TypeAlias if sys.version_info >= (3, 9): __all__ = ["NORMAL", "ROMAN", "BOLD", "ITALIC", "nametofont", "Font", "families", "names"] @@ -15,8 +15,11 @@ ITALIC: Literal["italic"] _FontDescription: TypeAlias = ( str # "Helvetica 12" | Font # A font object constructed in Python - | list[Any] # ("Helvetica", 12, BOLD) - | tuple[Any, ...] + | list[Any] # ["Helvetica", 12, BOLD] + | tuple[str] # ("Liberation Sans",) needs wrapping in tuple/list to handle spaces + | tuple[str, int] # ("Liberation Sans", 12) + | tuple[str, int, str] # ("Liberation Sans", 12, "bold") + | tuple[str, int, list[str] | tuple[str, ...]] # e.g. bold and italic | _tkinter.Tcl_Obj # A font object constructed in Tcl ) @@ -94,13 +97,14 @@ class Font: configure = config def copy(self) -> Font: ... @overload - def metrics(self, __option: Literal["ascent", "descent", "linespace"], *, displayof: tkinter.Misc | None = ...) -> int: ... + def metrics(self, option: Literal["ascent", "descent", "linespace"], /, *, displayof: tkinter.Misc | None = ...) -> int: ... @overload - def metrics(self, __option: Literal["fixed"], *, displayof: tkinter.Misc | None = ...) -> bool: ... + def metrics(self, option: Literal["fixed"], /, *, displayof: tkinter.Misc | None = ...) -> bool: ... @overload def metrics(self, *, displayof: tkinter.Misc | None = ...) -> _MetricsDict: ... def measure(self, text: str, displayof: tkinter.Misc | None = None) -> int: ... def __eq__(self, other: object) -> bool: ... + def __del__(self) -> None: ... def families(root: tkinter.Misc | None = None, displayof: tkinter.Misc | None = None) -> tuple[str, ...]: ... def names(root: tkinter.Misc | None = None) -> tuple[str, ...]: ... diff --git a/mypy/typeshed/stdlib/tkinter/scrolledtext.pyi b/mypy/typeshed/stdlib/tkinter/scrolledtext.pyi index 114f8c3de3ea..6f1abc714487 100644 --- a/mypy/typeshed/stdlib/tkinter/scrolledtext.pyi +++ b/mypy/typeshed/stdlib/tkinter/scrolledtext.pyi @@ -1,4 +1,3 @@ -from _typeshed import Incomplete from tkinter import Frame, Misc, Scrollbar, Text __all__ = ["ScrolledText"] @@ -7,4 +6,4 @@ __all__ = ["ScrolledText"] class ScrolledText(Text): frame: Frame vbar: Scrollbar - def __init__(self, master: Misc | None = None, **kwargs: Incomplete) -> None: ... + def __init__(self, master: Misc | None = None, **kwargs) -> None: ... diff --git a/mypy/typeshed/stdlib/tkinter/simpledialog.pyi b/mypy/typeshed/stdlib/tkinter/simpledialog.pyi index 2c57cce7371c..45dce21a6b1c 100644 --- a/mypy/typeshed/stdlib/tkinter/simpledialog.pyi +++ b/mypy/typeshed/stdlib/tkinter/simpledialog.pyi @@ -14,7 +14,7 @@ class SimpleDialog: self, master: Misc | None, text: str = "", - buttons: list[str] = ..., + buttons: list[str] = [], default: int | None = None, cancel: int | None = None, title: str | None = None, diff --git a/mypy/typeshed/stdlib/tkinter/tix.pyi b/mypy/typeshed/stdlib/tkinter/tix.pyi index 5dd6f040fab7..73649de427e8 100644 --- a/mypy/typeshed/stdlib/tkinter/tix.pyi +++ b/mypy/typeshed/stdlib/tkinter/tix.pyi @@ -1,7 +1,6 @@ import tkinter from _typeshed import Incomplete -from typing import Any -from typing_extensions import Literal +from typing import Any, Literal WINDOW: Literal["window"] TEXT: Literal["text"] @@ -54,117 +53,117 @@ class TixWidget(tkinter.Widget): master: tkinter.Misc | None = None, widgetName: str | None = None, static_options: list[str] | None = None, - cnf: dict[str, Any] = ..., - kw: dict[str, Any] = ..., + cnf: dict[str, Any] = {}, + kw: dict[str, Any] = {}, ) -> None: ... def __getattr__(self, name: str): ... def set_silent(self, value: str) -> None: ... def subwidget(self, name: str) -> tkinter.Widget: ... def subwidgets_all(self) -> list[tkinter.Widget]: ... def config_all(self, option: Any, value: Any) -> None: ... - def image_create(self, imgtype: str, cnf: dict[str, Any] = ..., master: tkinter.Widget | None = None, **kw) -> None: ... + def image_create(self, imgtype: str, cnf: dict[str, Any] = {}, master: tkinter.Widget | None = None, **kw) -> None: ... def image_delete(self, imgname: str) -> None: ... class TixSubWidget(TixWidget): def __init__(self, master: tkinter.Widget, name: str, destroy_physically: int = 1, check_intermediate: int = 1) -> None: ... class DisplayStyle: - def __init__(self, itemtype: str, cnf: dict[str, Any] = ..., *, master: tkinter.Widget | None = None, **kw) -> None: ... + def __init__(self, itemtype: str, cnf: dict[str, Any] = {}, *, master: tkinter.Widget | None = None, **kw) -> None: ... def __getitem__(self, key: str): ... def __setitem__(self, key: str, value: Any) -> None: ... def delete(self) -> None: ... - def config(self, cnf: dict[str, Any] = ..., **kw): ... + def config(self, cnf: dict[str, Any] = {}, **kw): ... class Balloon(TixWidget): - def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = ..., **kw) -> None: ... - def bind_widget(self, widget: tkinter.Widget, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = {}, **kw) -> None: ... + def bind_widget(self, widget: tkinter.Widget, cnf: dict[str, Any] = {}, **kw) -> None: ... def unbind_widget(self, widget: tkinter.Widget) -> None: ... class ButtonBox(TixWidget): - def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = ..., **kw) -> None: ... - def add(self, name: str, cnf: dict[str, Any] = ..., **kw) -> tkinter.Widget: ... + def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = {}, **kw) -> None: ... + def add(self, name: str, cnf: dict[str, Any] = {}, **kw) -> tkinter.Widget: ... def invoke(self, name: str) -> None: ... class ComboBox(TixWidget): - def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = {}, **kw) -> None: ... def add_history(self, str: str) -> None: ... def append_history(self, str: str) -> None: ... def insert(self, index: int, str: str) -> None: ... def pick(self, index: int) -> None: ... class Control(TixWidget): - def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = {}, **kw) -> None: ... def decrement(self) -> None: ... def increment(self) -> None: ... def invoke(self) -> None: ... class LabelEntry(TixWidget): - def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = {}, **kw) -> None: ... class LabelFrame(TixWidget): - def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = {}, **kw) -> None: ... class Meter(TixWidget): - def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = {}, **kw) -> None: ... class OptionMenu(TixWidget): - def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = ..., **kw) -> None: ... - def add_command(self, name: str, cnf: dict[str, Any] = ..., **kw) -> None: ... - def add_separator(self, name: str, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = {}, **kw) -> None: ... + def add_command(self, name: str, cnf: dict[str, Any] = {}, **kw) -> None: ... + def add_separator(self, name: str, cnf: dict[str, Any] = {}, **kw) -> None: ... def delete(self, name: str) -> None: ... def disable(self, name: str) -> None: ... def enable(self, name: str) -> None: ... class PopupMenu(TixWidget): - def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = {}, **kw) -> None: ... def bind_widget(self, widget: tkinter.Widget) -> None: ... def unbind_widget(self, widget: tkinter.Widget) -> None: ... def post_widget(self, widget: tkinter.Widget, x: int, y: int) -> None: ... class Select(TixWidget): - def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = ..., **kw) -> None: ... - def add(self, name: str, cnf: dict[str, Any] = ..., **kw) -> tkinter.Widget: ... + def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = {}, **kw) -> None: ... + def add(self, name: str, cnf: dict[str, Any] = {}, **kw) -> tkinter.Widget: ... def invoke(self, name: str) -> None: ... class StdButtonBox(TixWidget): - def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = {}, **kw) -> None: ... def invoke(self, name: str) -> None: ... class DirList(TixWidget): - def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = {}, **kw) -> None: ... def chdir(self, dir: str) -> None: ... class DirTree(TixWidget): - def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = {}, **kw) -> None: ... def chdir(self, dir: str) -> None: ... class DirSelectDialog(TixWidget): - def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = {}, **kw) -> None: ... def popup(self) -> None: ... def popdown(self) -> None: ... class DirSelectBox(TixWidget): - def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = {}, **kw) -> None: ... class ExFileSelectBox(TixWidget): - def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = {}, **kw) -> None: ... def filter(self) -> None: ... def invoke(self) -> None: ... class FileSelectBox(TixWidget): - def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = {}, **kw) -> None: ... def apply_filter(self) -> None: ... def invoke(self) -> None: ... class FileEntry(TixWidget): - def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = {}, **kw) -> None: ... def invoke(self) -> None: ... def file_dialog(self) -> None: ... class HList(TixWidget, tkinter.XView, tkinter.YView): - def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = ..., **kw) -> None: ... - def add(self, entry: str, cnf: dict[str, Any] = ..., **kw) -> tkinter.Widget: ... - def add_child(self, parent: str | None = None, cnf: dict[str, Any] = ..., **kw) -> tkinter.Widget: ... + def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = {}, **kw) -> None: ... + def add(self, entry: str, cnf: dict[str, Any] = {}, **kw) -> tkinter.Widget: ... + def add_child(self, parent: str | None = None, cnf: dict[str, Any] = {}, **kw) -> tkinter.Widget: ... def anchor_set(self, entry: str) -> None: ... def anchor_clear(self) -> None: ... # FIXME: Overload, certain combos return, others don't @@ -177,16 +176,16 @@ class HList(TixWidget, tkinter.XView, tkinter.YView): def dragsite_clear(self) -> None: ... def dropsite_set(self, index: int) -> None: ... def dropsite_clear(self) -> None: ... - def header_create(self, col: int, cnf: dict[str, Any] = ..., **kw) -> None: ... - def header_configure(self, col: int, cnf: dict[str, Any] = ..., **kw) -> Incomplete | None: ... + def header_create(self, col: int, cnf: dict[str, Any] = {}, **kw) -> None: ... + def header_configure(self, col: int, cnf: dict[str, Any] = {}, **kw) -> Incomplete | None: ... def header_cget(self, col: int, opt): ... def header_exists(self, col: int) -> bool: ... def header_exist(self, col: int) -> bool: ... def header_delete(self, col: int) -> None: ... def header_size(self, col: int) -> int: ... def hide_entry(self, entry: str) -> None: ... - def indicator_create(self, entry: str, cnf: dict[str, Any] = ..., **kw) -> None: ... - def indicator_configure(self, entry: str, cnf: dict[str, Any] = ..., **kw) -> Incomplete | None: ... + def indicator_create(self, entry: str, cnf: dict[str, Any] = {}, **kw) -> None: ... + def indicator_configure(self, entry: str, cnf: dict[str, Any] = {}, **kw) -> Incomplete | None: ... def indicator_cget(self, entry: str, opt): ... def indicator_exists(self, entry: str) -> bool: ... def indicator_delete(self, entry: str) -> None: ... @@ -204,21 +203,21 @@ class HList(TixWidget, tkinter.XView, tkinter.YView): def info_prev(self, entry: str) -> str: ... def info_selection(self) -> tuple[str, ...]: ... def item_cget(self, entry: str, col: int, opt): ... - def item_configure(self, entry: str, col: int, cnf: dict[str, Any] = ..., **kw) -> Incomplete | None: ... - def item_create(self, entry: str, col: int, cnf: dict[str, Any] = ..., **kw) -> None: ... + def item_configure(self, entry: str, col: int, cnf: dict[str, Any] = {}, **kw) -> Incomplete | None: ... + def item_create(self, entry: str, col: int, cnf: dict[str, Any] = {}, **kw) -> None: ... def item_exists(self, entry: str, col: int) -> bool: ... def item_delete(self, entry: str, col: int) -> None: ... def entrycget(self, entry: str, opt): ... - def entryconfigure(self, entry: str, cnf: dict[str, Any] = ..., **kw) -> Incomplete | None: ... + def entryconfigure(self, entry: str, cnf: dict[str, Any] = {}, **kw) -> Incomplete | None: ... def nearest(self, y: int) -> str: ... def see(self, entry: str) -> None: ... - def selection_clear(self, cnf: dict[str, Any] = ..., **kw) -> None: ... + def selection_clear(self, cnf: dict[str, Any] = {}, **kw) -> None: ... def selection_includes(self, entry: str) -> bool: ... def selection_set(self, first: str, last: str | None = None) -> None: ... def show_entry(self, entry: str) -> None: ... class CheckList(TixWidget): - def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = {}, **kw) -> None: ... def autosetmode(self) -> None: ... def close(self, entrypath: str) -> None: ... def getmode(self, entrypath: str) -> str: ... @@ -228,7 +227,7 @@ class CheckList(TixWidget): def setstatus(self, entrypath: str, mode: str = "on") -> None: ... class Tree(TixWidget): - def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = {}, **kw) -> None: ... def autosetmode(self) -> None: ... def close(self, entrypath: str) -> None: ... def getmode(self, entrypath: str) -> str: ... @@ -236,7 +235,7 @@ class Tree(TixWidget): def setmode(self, entrypath: str, mode: str = "none") -> None: ... class TList(TixWidget, tkinter.XView, tkinter.YView): - def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = {}, **kw) -> None: ... def active_set(self, index: int) -> None: ... def active_clear(self) -> None: ... def anchor_set(self, index: int) -> None: ... @@ -246,7 +245,7 @@ class TList(TixWidget, tkinter.XView, tkinter.YView): def dragsite_clear(self) -> None: ... def dropsite_set(self, index: int) -> None: ... def dropsite_clear(self) -> None: ... - def insert(self, index: int, cnf: dict[str, Any] = ..., **kw) -> None: ... + def insert(self, index: int, cnf: dict[str, Any] = {}, **kw) -> None: ... def info_active(self) -> int: ... def info_anchor(self) -> int: ... def info_down(self, index: int) -> int: ... @@ -257,29 +256,29 @@ class TList(TixWidget, tkinter.XView, tkinter.YView): def info_up(self, index: int) -> int: ... def nearest(self, x: int, y: int) -> int: ... def see(self, index: int) -> None: ... - def selection_clear(self, cnf: dict[str, Any] = ..., **kw) -> None: ... + def selection_clear(self, cnf: dict[str, Any] = {}, **kw) -> None: ... def selection_includes(self, index: int) -> bool: ... def selection_set(self, first: int, last: int | None = None) -> None: ... class PanedWindow(TixWidget): - def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = ..., **kw) -> None: ... - def add(self, name: str, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = {}, **kw) -> None: ... + def add(self, name: str, cnf: dict[str, Any] = {}, **kw) -> None: ... def delete(self, name: str) -> None: ... def forget(self, name: str) -> None: ... # type: ignore[override] def panecget(self, entry: str, opt): ... - def paneconfigure(self, entry: str, cnf: dict[str, Any] = ..., **kw) -> Incomplete | None: ... + def paneconfigure(self, entry: str, cnf: dict[str, Any] = {}, **kw) -> Incomplete | None: ... def panes(self) -> list[tkinter.Widget]: ... class ListNoteBook(TixWidget): - def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = ..., **kw) -> None: ... - def add(self, name: str, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None, cnf: dict[str, Any] = {}, **kw) -> None: ... + def add(self, name: str, cnf: dict[str, Any] = {}, **kw) -> None: ... def page(self, name: str) -> tkinter.Widget: ... def pages(self) -> list[tkinter.Widget]: ... def raise_page(self, name: str) -> None: ... class NoteBook(TixWidget): - def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = ..., **kw) -> None: ... - def add(self, name: str, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = {}, **kw) -> None: ... + def add(self, name: str, cnf: dict[str, Any] = {}, **kw) -> None: ... def delete(self, name: str) -> None: ... def page(self, name: str) -> tkinter.Widget: ... def pages(self) -> list[tkinter.Widget]: ... @@ -287,12 +286,12 @@ class NoteBook(TixWidget): def raised(self) -> bool: ... class InputOnly(TixWidget): - def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = ..., **kw) -> None: ... + def __init__(self, master: tkinter.Widget | None = None, cnf: dict[str, Any] = {}, **kw) -> None: ... class Form: def __setitem__(self, key: str, value: Any) -> None: ... - def config(self, cnf: dict[str, Any] = ..., **kw) -> None: ... - def form(self, cnf: dict[str, Any] = ..., **kw) -> None: ... + def config(self, cnf: dict[str, Any] = {}, **kw) -> None: ... + def form(self, cnf: dict[str, Any] = {}, **kw) -> None: ... def check(self) -> bool: ... def forget(self) -> None: ... def grid(self, xsize: int = 0, ysize: int = 0) -> tuple[int, int] | None: ... diff --git a/mypy/typeshed/stdlib/tkinter/ttk.pyi b/mypy/typeshed/stdlib/tkinter/ttk.pyi index 61ebc0e2734f..86a23ce82211 100644 --- a/mypy/typeshed/stdlib/tkinter/ttk.pyi +++ b/mypy/typeshed/stdlib/tkinter/ttk.pyi @@ -1,11 +1,10 @@ import _tkinter -import sys import tkinter from _typeshed import Incomplete from collections.abc import Callable from tkinter.font import _FontDescription -from typing import Any, overload -from typing_extensions import Literal, TypeAlias, TypedDict +from typing import Any, Literal, TypedDict, overload +from typing_extensions import TypeAlias __all__ = [ "Button", @@ -47,7 +46,7 @@ _Padding: TypeAlias = ( ) # from ttk_widget (aka ttk::widget) manual page, differs from tkinter._Compound -_TtkCompound: TypeAlias = Literal["text", "image", tkinter._Compound] +_TtkCompound: TypeAlias = Literal["", "text", "image", tkinter._Compound] class Style: master: Incomplete @@ -79,21 +78,21 @@ class Button(Widget): self, master: tkinter.Misc | None = None, *, - class_: str = ..., - command: tkinter._ButtonCommand = ..., - compound: _TtkCompound = ..., - cursor: tkinter._Cursor = ..., - default: Literal["normal", "active", "disabled"] = ..., - image: tkinter._ImageSpec = ..., + class_: str = "", + command: tkinter._ButtonCommand = "", + compound: _TtkCompound = "", + cursor: tkinter._Cursor = "", + default: Literal["normal", "active", "disabled"] = "normal", + image: tkinter._ImageSpec = "", name: str = ..., padding=..., # undocumented - state: str = ..., - style: str = ..., + state: str = "normal", + style: str = "", takefocus: tkinter._TakeFocusValue = ..., - text: float | str = ..., + text: float | str = "", textvariable: tkinter.Variable = ..., - underline: int = ..., - width: int | Literal[""] = ..., + underline: int = -1, + width: int | Literal[""] = "", ) -> None: ... @overload def configure( @@ -124,26 +123,26 @@ class Checkbutton(Widget): self, master: tkinter.Misc | None = None, *, - class_: str = ..., - command: tkinter._ButtonCommand = ..., - compound: _TtkCompound = ..., - cursor: tkinter._Cursor = ..., - image: tkinter._ImageSpec = ..., + class_: str = "", + command: tkinter._ButtonCommand = "", + compound: _TtkCompound = "", + cursor: tkinter._Cursor = "", + image: tkinter._ImageSpec = "", name: str = ..., - offvalue: Any = ..., - onvalue: Any = ..., + offvalue: Any = 0, + onvalue: Any = 1, padding=..., # undocumented - state: str = ..., - style: str = ..., + state: str = "normal", + style: str = "", takefocus: tkinter._TakeFocusValue = ..., - text: float | str = ..., + text: float | str = "", textvariable: tkinter.Variable = ..., - underline: int = ..., + underline: int = -1, # Seems like variable can be empty string, but actually setting it to # empty string segfaults before Tcl 8.6.9. Search for ttk::checkbutton # here: https://sourceforge.net/projects/tcl/files/Tcl/8.6.9/tcltk-release-notes-8.6.9.txt/view variable: tkinter.Variable = ..., - width: int | Literal[""] = ..., + width: int | Literal[""] = "", ) -> None: ... @overload def configure( @@ -177,35 +176,35 @@ class Entry(Widget, tkinter.Entry): master: tkinter.Misc | None = None, widget: str | None = None, *, - background: tkinter._Color = ..., # undocumented - class_: str = ..., + background: str = ..., # undocumented + class_: str = "", cursor: tkinter._Cursor = ..., - exportselection: bool = ..., - font: _FontDescription = ..., - foreground: tkinter._Color = ..., - invalidcommand: tkinter._EntryValidateCommand = ..., - justify: Literal["left", "center", "right"] = ..., + exportselection: bool = True, + font: _FontDescription = "TkTextFont", + foreground: str = "", + invalidcommand: tkinter._EntryValidateCommand = "", + justify: Literal["left", "center", "right"] = "left", name: str = ..., - show: str = ..., - state: str = ..., - style: str = ..., + show: str = "", + state: str = "normal", + style: str = "", takefocus: tkinter._TakeFocusValue = ..., textvariable: tkinter.Variable = ..., - validate: Literal["none", "focus", "focusin", "focusout", "key", "all"] = ..., - validatecommand: tkinter._EntryValidateCommand = ..., - width: int = ..., - xscrollcommand: tkinter._XYScrollCommand = ..., + validate: Literal["none", "focus", "focusin", "focusout", "key", "all"] = "none", + validatecommand: tkinter._EntryValidateCommand = "", + width: int = 20, + xscrollcommand: tkinter._XYScrollCommand = "", ) -> None: ... @overload # type: ignore[override] def configure( self, cnf: dict[str, Any] | None = None, *, - background: tkinter._Color = ..., + background: str = ..., cursor: tkinter._Cursor = ..., exportselection: bool = ..., font: _FontDescription = ..., - foreground: tkinter._Color = ..., + foreground: str = ..., invalidcommand: tkinter._EntryValidateCommand = ..., justify: Literal["left", "center", "right"] = ..., show: str = ..., @@ -226,11 +225,11 @@ class Entry(Widget, tkinter.Entry): self, cnf: dict[str, Any] | None = None, *, - background: tkinter._Color = ..., + background: str = ..., cursor: tkinter._Cursor = ..., exportselection: bool = ..., font: _FontDescription = ..., - foreground: tkinter._Color = ..., + foreground: str = ..., invalidcommand: tkinter._EntryValidateCommand = ..., justify: Literal["left", "center", "right"] = ..., show: str = ..., @@ -254,26 +253,26 @@ class Combobox(Entry): self, master: tkinter.Misc | None = None, *, - background: tkinter._Color = ..., # undocumented - class_: str = ..., - cursor: tkinter._Cursor = ..., - exportselection: bool = ..., + background: str = ..., # undocumented + class_: str = "", + cursor: tkinter._Cursor = "", + exportselection: bool = True, font: _FontDescription = ..., # undocumented - foreground: tkinter._Color = ..., # undocumented - height: int = ..., + foreground: str = ..., # undocumented + height: int = 10, invalidcommand: tkinter._EntryValidateCommand = ..., # undocumented - justify: Literal["left", "center", "right"] = ..., + justify: Literal["left", "center", "right"] = "left", name: str = ..., - postcommand: Callable[[], object] | str = ..., + postcommand: Callable[[], object] | str = "", show=..., # undocumented - state: str = ..., - style: str = ..., + state: str = "normal", + style: str = "", takefocus: tkinter._TakeFocusValue = ..., textvariable: tkinter.Variable = ..., validate: Literal["none", "focus", "focusin", "focusout", "key", "all"] = ..., # undocumented validatecommand: tkinter._EntryValidateCommand = ..., # undocumented values: list[str] | tuple[str, ...] = ..., - width: int = ..., + width: int = 20, xscrollcommand: tkinter._XYScrollCommand = ..., # undocumented ) -> None: ... @overload # type: ignore[override] @@ -281,11 +280,11 @@ class Combobox(Entry): self, cnf: dict[str, Any] | None = None, *, - background: tkinter._Color = ..., + background: str = ..., cursor: tkinter._Cursor = ..., exportselection: bool = ..., font: _FontDescription = ..., - foreground: tkinter._Color = ..., + foreground: str = ..., height: int = ..., invalidcommand: tkinter._EntryValidateCommand = ..., justify: Literal["left", "center", "right"] = ..., @@ -309,11 +308,11 @@ class Combobox(Entry): self, cnf: dict[str, Any] | None = None, *, - background: tkinter._Color = ..., + background: str = ..., cursor: tkinter._Cursor = ..., exportselection: bool = ..., font: _FontDescription = ..., - foreground: tkinter._Color = ..., + foreground: str = ..., height: int = ..., invalidcommand: tkinter._EntryValidateCommand = ..., justify: Literal["left", "center", "right"] = ..., @@ -335,21 +334,23 @@ class Combobox(Entry): def set(self, value: Any) -> None: ... class Frame(Widget): + # This should be kept in sync with tkinter.ttk.LabeledScale.__init__() + # (all of these keyword-only arguments are also present there) def __init__( self, master: tkinter.Misc | None = None, *, border: tkinter._ScreenUnits = ..., borderwidth: tkinter._ScreenUnits = ..., - class_: str = ..., - cursor: tkinter._Cursor = ..., - height: tkinter._ScreenUnits = ..., + class_: str = "", + cursor: tkinter._Cursor = "", + height: tkinter._ScreenUnits = 0, name: str = ..., padding: _Padding = ..., relief: tkinter._Relief = ..., - style: str = ..., - takefocus: tkinter._TakeFocusValue = ..., - width: tkinter._ScreenUnits = ..., + style: str = "", + takefocus: tkinter._TakeFocusValue = "", + width: tkinter._ScreenUnits = 0, ) -> None: ... @overload def configure( @@ -376,26 +377,26 @@ class Label(Widget): master: tkinter.Misc | None = None, *, anchor: tkinter._Anchor = ..., - background: tkinter._Color = ..., + background: str = "", border: tkinter._ScreenUnits = ..., # alias for borderwidth borderwidth: tkinter._ScreenUnits = ..., # undocumented - class_: str = ..., - compound: _TtkCompound = ..., - cursor: tkinter._Cursor = ..., + class_: str = "", + compound: _TtkCompound = "", + cursor: tkinter._Cursor = "", font: _FontDescription = ..., - foreground: tkinter._Color = ..., - image: tkinter._ImageSpec = ..., + foreground: str = "", + image: tkinter._ImageSpec = "", justify: Literal["left", "center", "right"] = ..., name: str = ..., padding: _Padding = ..., relief: tkinter._Relief = ..., - state: str = ..., - style: str = ..., - takefocus: tkinter._TakeFocusValue = ..., - text: float | str = ..., + state: str = "normal", + style: str = "", + takefocus: tkinter._TakeFocusValue = "", + text: float | str = "", textvariable: tkinter.Variable = ..., - underline: int = ..., - width: int | Literal[""] = ..., + underline: int = -1, + width: int | Literal[""] = "", wraplength: tkinter._ScreenUnits = ..., ) -> None: ... @overload @@ -404,13 +405,13 @@ class Label(Widget): cnf: dict[str, Any] | None = None, *, anchor: tkinter._Anchor = ..., - background: tkinter._Color = ..., + background: str = ..., border: tkinter._ScreenUnits = ..., borderwidth: tkinter._ScreenUnits = ..., compound: _TtkCompound = ..., cursor: tkinter._Cursor = ..., font: _FontDescription = ..., - foreground: tkinter._Color = ..., + foreground: str = ..., image: tkinter._ImageSpec = ..., justify: Literal["left", "center", "right"] = ..., padding: _Padding = ..., @@ -435,19 +436,19 @@ class Labelframe(Widget): *, border: tkinter._ScreenUnits = ..., borderwidth: tkinter._ScreenUnits = ..., # undocumented - class_: str = ..., - cursor: tkinter._Cursor = ..., - height: tkinter._ScreenUnits = ..., + class_: str = "", + cursor: tkinter._Cursor = "", + height: tkinter._ScreenUnits = 0, labelanchor: Literal["nw", "n", "ne", "en", "e", "es", "se", "s", "sw", "ws", "w", "wn"] = ..., labelwidget: tkinter.Misc = ..., name: str = ..., padding: _Padding = ..., relief: tkinter._Relief = ..., # undocumented - style: str = ..., - takefocus: tkinter._TakeFocusValue = ..., - text: float | str = ..., - underline: int = ..., - width: tkinter._ScreenUnits = ..., + style: str = "", + takefocus: tkinter._TakeFocusValue = "", + text: float | str = "", + underline: int = -1, + width: tkinter._ScreenUnits = 0, ) -> None: ... @overload def configure( @@ -479,21 +480,21 @@ class Menubutton(Widget): self, master: tkinter.Misc | None = None, *, - class_: str = ..., - compound: _TtkCompound = ..., - cursor: tkinter._Cursor = ..., - direction: Literal["above", "below", "left", "right", "flush"] = ..., - image: tkinter._ImageSpec = ..., + class_: str = "", + compound: _TtkCompound = "", + cursor: tkinter._Cursor = "", + direction: Literal["above", "below", "left", "right", "flush"] = "below", + image: tkinter._ImageSpec = "", menu: tkinter.Menu = ..., name: str = ..., padding=..., # undocumented - state: str = ..., - style: str = ..., + state: str = "normal", + style: str = "", takefocus: tkinter._TakeFocusValue = ..., - text: float | str = ..., + text: float | str = "", textvariable: tkinter.Variable = ..., - underline: int = ..., - width: int | Literal[""] = ..., + underline: int = -1, + width: int | Literal[""] = "", ) -> None: ... @overload def configure( @@ -523,14 +524,14 @@ class Notebook(Widget): self, master: tkinter.Misc | None = None, *, - class_: str = ..., - cursor: tkinter._Cursor = ..., - height: int = ..., + class_: str = "", + cursor: tkinter._Cursor = "", + height: int = 0, name: str = ..., padding: _Padding = ..., - style: str = ..., + style: str = "", takefocus: tkinter._TakeFocusValue = ..., - width: int = ..., + width: int = 0, ) -> None: ... @overload def configure( @@ -574,15 +575,15 @@ class Panedwindow(Widget, tkinter.PanedWindow): self, master: tkinter.Misc | None = None, *, - class_: str = ..., - cursor: tkinter._Cursor = ..., + class_: str = "", + cursor: tkinter._Cursor = "", # width and height for tkinter.ttk.Panedwindow are int but for tkinter.PanedWindow they are screen units - height: int = ..., + height: int = 0, name: str = ..., - orient: Literal["vertical", "horizontal"] = ..., # can't be changed with configure() - style: str = ..., - takefocus: tkinter._TakeFocusValue = ..., - width: int = ..., + orient: Literal["vertical", "horizontal"] = "vertical", # can't be changed with configure() + style: str = "", + takefocus: tkinter._TakeFocusValue = "", + width: int = 0, ) -> None: ... def add(self, child: tkinter.Widget, *, weight: int = ..., **kw) -> None: ... @overload # type: ignore[override] @@ -624,17 +625,17 @@ class Progressbar(Widget): self, master: tkinter.Misc | None = None, *, - class_: str = ..., - cursor: tkinter._Cursor = ..., - length: tkinter._ScreenUnits = ..., - maximum: float = ..., - mode: Literal["determinate", "indeterminate"] = ..., + class_: str = "", + cursor: tkinter._Cursor = "", + length: tkinter._ScreenUnits = 100, + maximum: float = 100, + mode: Literal["determinate", "indeterminate"] = "determinate", name: str = ..., - orient: Literal["horizontal", "vertical"] = ..., - phase: int = ..., # docs say read-only but assigning int to this works - style: str = ..., - takefocus: tkinter._TakeFocusValue = ..., - value: float = ..., + orient: Literal["horizontal", "vertical"] = "horizontal", + phase: int = 0, # docs say read-only but assigning int to this works + style: str = "", + takefocus: tkinter._TakeFocusValue = "", + value: float = 0.0, variable: tkinter.IntVar | tkinter.DoubleVar = ..., ) -> None: ... @overload @@ -665,22 +666,22 @@ class Radiobutton(Widget): self, master: tkinter.Misc | None = None, *, - class_: str = ..., - command: tkinter._ButtonCommand = ..., - compound: _TtkCompound = ..., - cursor: tkinter._Cursor = ..., - image: tkinter._ImageSpec = ..., + class_: str = "", + command: tkinter._ButtonCommand = "", + compound: _TtkCompound = "", + cursor: tkinter._Cursor = "", + image: tkinter._ImageSpec = "", name: str = ..., padding=..., # undocumented - state: str = ..., - style: str = ..., + state: str = "normal", + style: str = "", takefocus: tkinter._TakeFocusValue = ..., - text: float | str = ..., + text: float | str = "", textvariable: tkinter.Variable = ..., - underline: int = ..., - value: Any = ..., + underline: int = -1, + value: Any = "1", variable: tkinter.Variable | Literal[""] = ..., - width: int | Literal[""] = ..., + width: int | Literal[""] = "", ) -> None: ... @overload def configure( @@ -713,18 +714,18 @@ class Scale(Widget, tkinter.Scale): # type: ignore[misc] self, master: tkinter.Misc | None = None, *, - class_: str = ..., - command: str | Callable[[str], object] = ..., - cursor: tkinter._Cursor = ..., - from_: float = ..., - length: tkinter._ScreenUnits = ..., + class_: str = "", + command: str | Callable[[str], object] = "", + cursor: tkinter._Cursor = "", + from_: float = 0, + length: tkinter._ScreenUnits = 100, name: str = ..., - orient: Literal["horizontal", "vertical"] = ..., + orient: Literal["horizontal", "vertical"] = "horizontal", state: str = ..., # undocumented - style: str = ..., + style: str = "", takefocus: tkinter._TakeFocusValue = ..., - to: float = ..., - value: float = ..., + to: float = 1.0, + value: float = 0, variable: tkinter.IntVar | tkinter.DoubleVar = ..., ) -> None: ... @overload # type: ignore[override] @@ -774,13 +775,13 @@ class Scrollbar(Widget, tkinter.Scrollbar): # type: ignore[misc] self, master: tkinter.Misc | None = None, *, - class_: str = ..., - command: Callable[..., tuple[float, float] | None] | str = ..., - cursor: tkinter._Cursor = ..., + class_: str = "", + command: Callable[..., tuple[float, float] | None] | str = "", + cursor: tkinter._Cursor = "", name: str = ..., - orient: Literal["horizontal", "vertical"] = ..., - style: str = ..., - takefocus: tkinter._TakeFocusValue = ..., + orient: Literal["horizontal", "vertical"] = "vertical", + style: str = "", + takefocus: tkinter._TakeFocusValue = "", ) -> None: ... @overload # type: ignore[override] def configure( @@ -815,12 +816,12 @@ class Separator(Widget): self, master: tkinter.Misc | None = None, *, - class_: str = ..., - cursor: tkinter._Cursor = ..., + class_: str = "", + cursor: tkinter._Cursor = "", name: str = ..., - orient: Literal["horizontal", "vertical"] = ..., - style: str = ..., - takefocus: tkinter._TakeFocusValue = ..., + orient: Literal["horizontal", "vertical"] = "horizontal", + style: str = "", + takefocus: tkinter._TakeFocusValue = "", ) -> None: ... @overload def configure( @@ -841,11 +842,11 @@ class Sizegrip(Widget): self, master: tkinter.Misc | None = None, *, - class_: str = ..., + class_: str = "", cursor: tkinter._Cursor = ..., name: str = ..., - style: str = ..., - takefocus: tkinter._TakeFocusValue = ..., + style: str = "", + takefocus: tkinter._TakeFocusValue = "", ) -> None: ... @overload def configure( @@ -865,43 +866,43 @@ class Spinbox(Entry): self, master: tkinter.Misc | None = None, *, - background: tkinter._Color = ..., # undocumented - class_: str = ..., - command: Callable[[], object] | str | list[str] | tuple[str, ...] = ..., - cursor: tkinter._Cursor = ..., + background: str = ..., # undocumented + class_: str = "", + command: Callable[[], object] | str | list[str] | tuple[str, ...] = "", + cursor: tkinter._Cursor = "", exportselection: bool = ..., # undocumented font: _FontDescription = ..., # undocumented - foreground: tkinter._Color = ..., # undocumented - format: str = ..., - from_: float = ..., - increment: float = ..., + foreground: str = ..., # undocumented + format: str = "", + from_: float = 0, + increment: float = 1, invalidcommand: tkinter._EntryValidateCommand = ..., # undocumented justify: Literal["left", "center", "right"] = ..., # undocumented name: str = ..., show=..., # undocumented - state: str = ..., - style: str = ..., + state: str = "normal", + style: str = "", takefocus: tkinter._TakeFocusValue = ..., textvariable: tkinter.Variable = ..., # undocumented - to: float = ..., - validate: Literal["none", "focus", "focusin", "focusout", "key", "all"] = ..., - validatecommand: tkinter._EntryValidateCommand = ..., + to: float = 0, + validate: Literal["none", "focus", "focusin", "focusout", "key", "all"] = "none", + validatecommand: tkinter._EntryValidateCommand = "", values: list[str] | tuple[str, ...] = ..., width: int = ..., # undocumented - wrap: bool = ..., - xscrollcommand: tkinter._XYScrollCommand = ..., + wrap: bool = False, + xscrollcommand: tkinter._XYScrollCommand = "", ) -> None: ... @overload # type: ignore[override] def configure( self, cnf: dict[str, Any] | None = None, *, - background: tkinter._Color = ..., + background: str = ..., command: Callable[[], object] | str | list[str] | tuple[str, ...] = ..., cursor: tkinter._Cursor = ..., exportselection: bool = ..., font: _FontDescription = ..., - foreground: tkinter._Color = ..., + foreground: str = ..., format: str = ..., from_: float = ..., increment: float = ..., @@ -934,8 +935,8 @@ class _TreeviewItemDict(TypedDict): class _TreeviewTagDict(TypedDict): # There is also 'text' and 'anchor', but they don't seem to do anything, using them is likely a bug - foreground: tkinter._Color - background: tkinter._Color + foreground: str + background: str font: _FontDescription image: str # not wrapped in list :D @@ -953,39 +954,37 @@ class _TreeviewColumnDict(TypedDict): anchor: tkinter._Anchor id: str -_TreeviewColumnId: TypeAlias = int | str # manual page: "COLUMN IDENTIFIERS" - class Treeview(Widget, tkinter.XView, tkinter.YView): def __init__( self, master: tkinter.Misc | None = None, *, - class_: str = ..., - columns: str | list[str] | tuple[str, ...] = ..., - cursor: tkinter._Cursor = ..., - displaycolumns: str | list[str] | tuple[str, ...] | list[int] | tuple[int, ...] = ..., - height: int = ..., + class_: str = "", + columns: str | list[str] | list[int] | list[str | int] | tuple[str | int, ...] = "", + cursor: tkinter._Cursor = "", + displaycolumns: str | int | list[str] | tuple[str, ...] | list[int] | tuple[int, ...] = ("#all",), + height: int = 10, name: str = ..., padding: _Padding = ..., - selectmode: Literal["extended", "browse", "none"] = ..., + selectmode: Literal["extended", "browse", "none"] = "extended", # list/tuple of Literal don't actually work in mypy # # 'tree headings' is same as ['tree', 'headings'], and I wouldn't be # surprised if someone is using it. - show: Literal["tree", "headings", "tree headings", ""] | list[str] | tuple[str, ...] = ..., - style: str = ..., + show: Literal["tree", "headings", "tree headings", ""] | list[str] | tuple[str, ...] = ("tree", "headings"), + style: str = "", takefocus: tkinter._TakeFocusValue = ..., - xscrollcommand: tkinter._XYScrollCommand = ..., - yscrollcommand: tkinter._XYScrollCommand = ..., + xscrollcommand: tkinter._XYScrollCommand = "", + yscrollcommand: tkinter._XYScrollCommand = "", ) -> None: ... @overload def configure( self, cnf: dict[str, Any] | None = None, *, - columns: str | list[str] | tuple[str, ...] = ..., + columns: str | list[str] | list[int] | list[str | int] | tuple[str | int, ...] = ..., cursor: tkinter._Cursor = ..., - displaycolumns: str | list[str] | tuple[str, ...] | list[int] | tuple[int, ...] = ..., + displaycolumns: str | int | list[str] | tuple[str, ...] | list[int] | tuple[int, ...] = ..., height: int = ..., padding: _Padding = ..., selectmode: Literal["extended", "browse", "none"] = ..., @@ -998,23 +997,23 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... config = configure - def bbox(self, item, column: _TreeviewColumnId | None = None) -> tuple[int, int, int, int] | Literal[""]: ... # type: ignore[override] - def get_children(self, item: str | None = None) -> tuple[str, ...]: ... - def set_children(self, item: str, *newchildren: str) -> None: ... + def bbox(self, item: str | int, column: str | int | None = None) -> tuple[int, int, int, int] | Literal[""]: ... # type: ignore[override] + def get_children(self, item: str | int | None = None) -> tuple[str, ...]: ... + def set_children(self, item: str | int, *newchildren: str | int) -> None: ... @overload - def column(self, column: _TreeviewColumnId, option: Literal["width", "minwidth"]) -> int: ... + def column(self, column: str | int, option: Literal["width", "minwidth"]) -> int: ... @overload - def column(self, column: _TreeviewColumnId, option: Literal["stretch"]) -> bool: ... # actually 0 or 1 + def column(self, column: str | int, option: Literal["stretch"]) -> bool: ... # actually 0 or 1 @overload - def column(self, column: _TreeviewColumnId, option: Literal["anchor"]) -> _tkinter.Tcl_Obj: ... + def column(self, column: str | int, option: Literal["anchor"]) -> _tkinter.Tcl_Obj: ... @overload - def column(self, column: _TreeviewColumnId, option: Literal["id"]) -> str: ... + def column(self, column: str | int, option: Literal["id"]) -> str: ... @overload - def column(self, column: _TreeviewColumnId, option: str) -> Any: ... + def column(self, column: str | int, option: str) -> Any: ... @overload def column( self, - column: _TreeviewColumnId, + column: str | int, option: None = None, *, width: int = ..., @@ -1023,29 +1022,29 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): anchor: tkinter._Anchor = ..., # id is read-only ) -> _TreeviewColumnDict | None: ... - def delete(self, *items: str) -> None: ... - def detach(self, *items: str) -> None: ... - def exists(self, item: str) -> bool: ... + def delete(self, *items: str | int) -> None: ... + def detach(self, *items: str | int) -> None: ... + def exists(self, item: str | int) -> bool: ... @overload # type: ignore[override] def focus(self, item: None = None) -> str: ... # can return empty string @overload - def focus(self, item: str) -> Literal[""]: ... + def focus(self, item: str | int) -> Literal[""]: ... @overload - def heading(self, column: _TreeviewColumnId, option: Literal["text"]) -> str: ... + def heading(self, column: str | int, option: Literal["text"]) -> str: ... @overload - def heading(self, column: _TreeviewColumnId, option: Literal["image"]) -> tuple[str] | str: ... + def heading(self, column: str | int, option: Literal["image"]) -> tuple[str] | str: ... @overload - def heading(self, column: _TreeviewColumnId, option: Literal["anchor"]) -> _tkinter.Tcl_Obj: ... + def heading(self, column: str | int, option: Literal["anchor"]) -> _tkinter.Tcl_Obj: ... @overload - def heading(self, column: _TreeviewColumnId, option: Literal["command"]) -> str: ... + def heading(self, column: str | int, option: Literal["command"]) -> str: ... @overload - def heading(self, column: _TreeviewColumnId, option: str) -> Any: ... + def heading(self, column: str | int, option: str) -> Any: ... @overload - def heading(self, column: _TreeviewColumnId, option: None = None) -> _TreeviewHeaderDict: ... # type: ignore[misc] + def heading(self, column: str | int, option: None = None) -> _TreeviewHeaderDict: ... # type: ignore[overload-overlap] @overload def heading( self, - column: _TreeviewColumnId, + column: str | int, option: None = None, *, text: str = ..., @@ -1058,14 +1057,14 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): def identify_column(self, x: int) -> str: ... def identify_region(self, x: int, y: int) -> Literal["heading", "separator", "tree", "cell", "nothing"]: ... def identify_element(self, x: int, y: int) -> str: ... # don't know what possible return values are - def index(self, item: str) -> int: ... + def index(self, item: str | int) -> int: ... def insert( self, parent: str, index: int | Literal["end"], - iid: str | None = None, + iid: str | int | None = None, *, - id: str = ..., # same as iid + id: str | int = ..., # same as iid text: str = ..., image: tkinter._ImageSpec = ..., values: list[Any] | tuple[Any, ...] = ..., @@ -1073,23 +1072,23 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): tags: str | list[str] | tuple[str, ...] = ..., ) -> str: ... @overload - def item(self, item: str, option: Literal["text"]) -> str: ... + def item(self, item: str | int, option: Literal["text"]) -> str: ... @overload - def item(self, item: str, option: Literal["image"]) -> tuple[str] | Literal[""]: ... + def item(self, item: str | int, option: Literal["image"]) -> tuple[str] | Literal[""]: ... @overload - def item(self, item: str, option: Literal["values"]) -> tuple[Any, ...] | Literal[""]: ... + def item(self, item: str | int, option: Literal["values"]) -> tuple[Any, ...] | Literal[""]: ... @overload - def item(self, item: str, option: Literal["open"]) -> bool: ... # actually 0 or 1 + def item(self, item: str | int, option: Literal["open"]) -> bool: ... # actually 0 or 1 @overload - def item(self, item: str, option: Literal["tags"]) -> tuple[str, ...] | Literal[""]: ... + def item(self, item: str | int, option: Literal["tags"]) -> tuple[str, ...] | Literal[""]: ... @overload - def item(self, item: str, option: str) -> Any: ... + def item(self, item: str | int, option: str) -> Any: ... @overload - def item(self, item: str, option: None = None) -> _TreeviewItemDict: ... # type: ignore[misc] + def item(self, item: str | int, option: None = None) -> _TreeviewItemDict: ... # type: ignore[overload-overlap] @overload def item( self, - item: str, + item: str | int, option: None = None, *, text: str = ..., @@ -1098,27 +1097,35 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): open: bool = ..., tags: str | list[str] | tuple[str, ...] = ..., ) -> None: ... - def move(self, item: str, parent: str, index: int) -> None: ... + def move(self, item: str | int, parent: str, index: int) -> None: ... reattach = move - def next(self, item: str) -> str: ... # returning empty string means last item - def parent(self, item: str) -> str: ... - def prev(self, item: str) -> str: ... # returning empty string means first item - def see(self, item: str) -> None: ... - if sys.version_info >= (3, 8): - def selection(self) -> tuple[str, ...]: ... - else: - def selection(self, selop: Incomplete | None = ..., items: Incomplete | None = None) -> tuple[str, ...]: ... - - def selection_set(self, items: str | list[str] | tuple[str, ...]) -> None: ... - def selection_add(self, items: str | list[str] | tuple[str, ...]) -> None: ... - def selection_remove(self, items: str | list[str] | tuple[str, ...]) -> None: ... - def selection_toggle(self, items: str | list[str] | tuple[str, ...]) -> None: ... + def next(self, item: str | int) -> str: ... # returning empty string means last item + def parent(self, item: str | int) -> str: ... + def prev(self, item: str | int) -> str: ... # returning empty string means first item + def see(self, item: str | int) -> None: ... + def selection(self) -> tuple[str, ...]: ... + @overload + def selection_set(self, items: list[str] | tuple[str, ...] | list[int] | tuple[int, ...], /) -> None: ... + @overload + def selection_set(self, *items: str | int) -> None: ... + @overload + def selection_add(self, items: list[str] | tuple[str, ...] | list[int] | tuple[int, ...], /) -> None: ... + @overload + def selection_add(self, *items: str | int) -> None: ... + @overload + def selection_remove(self, items: list[str] | tuple[str, ...] | list[int] | tuple[int, ...], /) -> None: ... + @overload + def selection_remove(self, *items: str | int) -> None: ... @overload - def set(self, item: str, column: None = None, value: None = None) -> dict[str, Any]: ... + def selection_toggle(self, items: list[str] | tuple[str, ...] | list[int] | tuple[int, ...], /) -> None: ... @overload - def set(self, item: str, column: _TreeviewColumnId, value: None = None) -> Any: ... + def selection_toggle(self, *items: str | int) -> None: ... @overload - def set(self, item: str, column: _TreeviewColumnId, value: Any) -> Literal[""]: ... + def set(self, item: str | int, column: None = None, value: None = None) -> dict[str, Any]: ... + @overload + def set(self, item: str | int, column: str | int, value: None = None) -> Any: ... + @overload + def set(self, item: str | int, column: str | int, value: Any) -> Literal[""]: ... # There's no tag_unbind() or 'add' argument for whatever reason. # Also, it's 'callback' instead of 'func' here. @overload @@ -1130,7 +1137,7 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): @overload def tag_bind(self, tagname: str, *, callback: str) -> None: ... @overload - def tag_configure(self, tagname: str, option: Literal["foreground", "background"]) -> tkinter._Color: ... + def tag_configure(self, tagname: str, option: Literal["foreground", "background"]) -> str: ... @overload def tag_configure(self, tagname: str, option: Literal["font"]) -> _FontDescription: ... @overload @@ -1142,20 +1149,21 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): option: None = None, *, # There is also 'text' and 'anchor', but they don't seem to do anything, using them is likely a bug - foreground: tkinter._Color = ..., - background: tkinter._Color = ..., + foreground: str = ..., + background: str = ..., font: _FontDescription = ..., image: tkinter._ImageSpec = ..., ) -> _TreeviewTagDict | Any: ... # can be None but annoying to check @overload def tag_has(self, tagname: str, item: None = None) -> tuple[str, ...]: ... @overload - def tag_has(self, tagname: str, item: str) -> bool: ... + def tag_has(self, tagname: str, item: str | int) -> bool: ... class LabeledScale(Frame): - label: Incomplete - scale: Incomplete - # TODO: don't any-type **kw. That goes to Frame.__init__. + label: Label + scale: Scale + # This should be kept in sync with tkinter.ttk.Frame.__init__() + # (all the keyword-only args except compound are from there) def __init__( self, master: tkinter.Misc | None = None, @@ -1163,8 +1171,18 @@ class LabeledScale(Frame): from_: float = 0, to: float = 10, *, - compound: Literal["top", "bottom"] = ..., - **kw, + border: tkinter._ScreenUnits = ..., + borderwidth: tkinter._ScreenUnits = ..., + class_: str = "", + compound: Literal["top", "bottom"] = "top", + cursor: tkinter._Cursor = "", + height: tkinter._ScreenUnits = 0, + name: str = ..., + padding: _Padding = ..., + relief: tkinter._Relief = ..., + style: str = "", + takefocus: tkinter._TakeFocusValue = "", + width: tkinter._ScreenUnits = 0, ) -> None: ... # destroy is overridden, signature does not change value: Any @@ -1172,15 +1190,15 @@ class LabeledScale(Frame): class OptionMenu(Menubutton): def __init__( self, - master, - variable, + master: tkinter.Misc | None, + variable: tkinter.StringVar, default: str | None = None, *values: str, # rest of these are keyword-only because *args syntax used above - style: str = ..., - direction: Literal["above", "below", "left", "right", "flush"] = ..., - command: Callable[[tkinter.StringVar], object] | None = ..., + style: str = "", + direction: Literal["above", "below", "left", "right", "flush"] = "below", + command: Callable[[tkinter.StringVar], object] | None = None, ) -> None: ... # configure, config, cget, destroy are inherited from Menubutton # destroy and __setitem__ are overridden, signature does not change - def set_menu(self, default: Incomplete | None = None, *values) -> None: ... + def set_menu(self, default: str | None = None, *values: str) -> None: ... diff --git a/mypy/typeshed/stdlib/token.pyi b/mypy/typeshed/stdlib/token.pyi index fcd6ef87d217..f1fec7698043 100644 --- a/mypy/typeshed/stdlib/token.pyi +++ b/mypy/typeshed/stdlib/token.pyi @@ -3,11 +3,14 @@ import sys __all__ = [ "AMPER", "AMPEREQUAL", + "ASYNC", "AT", "ATEQUAL", + "AWAIT", "CIRCUMFLEX", "CIRCUMFLEXEQUAL", "COLON", + "COLONEQUAL", "COMMA", "DEDENT", "DOT", @@ -59,6 +62,8 @@ __all__ = [ "STAREQUAL", "STRING", "TILDE", + "TYPE_COMMENT", + "TYPE_IGNORE", "VBAR", "VBAREQUAL", "tok_name", @@ -67,12 +72,12 @@ __all__ = [ "COMMENT", ] -if sys.version_info >= (3, 8): - __all__ += ["ASYNC", "AWAIT", "COLONEQUAL", "TYPE_COMMENT", "TYPE_IGNORE"] - if sys.version_info >= (3, 10): __all__ += ["SOFT_KEYWORD"] +if sys.version_info >= (3, 12): + __all__ += ["EXCLAMATION", "FSTRING_END", "FSTRING_MIDDLE", "FSTRING_START"] + ENDMARKER: int NAME: int NUMBER: int @@ -126,9 +131,8 @@ AT: int RARROW: int ELLIPSIS: int ATEQUAL: int -if sys.version_info >= (3, 8): - AWAIT: int - ASYNC: int +AWAIT: int +ASYNC: int OP: int ERRORTOKEN: int N_TOKENS: int @@ -137,14 +141,19 @@ tok_name: dict[int, str] COMMENT: int NL: int ENCODING: int -if sys.version_info >= (3, 8): - TYPE_COMMENT: int - TYPE_IGNORE: int - COLONEQUAL: int - EXACT_TOKEN_TYPES: dict[str, int] +TYPE_COMMENT: int +TYPE_IGNORE: int +COLONEQUAL: int +EXACT_TOKEN_TYPES: dict[str, int] if sys.version_info >= (3, 10): SOFT_KEYWORD: int +if sys.version_info >= (3, 12): + EXCLAMATION: int + FSTRING_END: int + FSTRING_MIDDLE: int + FSTRING_START: int + def ISTERMINAL(x: int) -> bool: ... def ISNONTERMINAL(x: int) -> bool: ... def ISEOF(x: int) -> bool: ... diff --git a/mypy/typeshed/stdlib/tokenize.pyi b/mypy/typeshed/stdlib/tokenize.pyi index ba57402fb845..3cd9ab8f87ce 100644 --- a/mypy/typeshed/stdlib/tokenize.pyi +++ b/mypy/typeshed/stdlib/tokenize.pyi @@ -3,17 +3,21 @@ from _typeshed import FileDescriptorOrPath from collections.abc import Callable, Generator, Iterable, Sequence from re import Pattern from token import * +from token import EXACT_TOKEN_TYPES as EXACT_TOKEN_TYPES from typing import Any, NamedTuple, TextIO from typing_extensions import TypeAlias __all__ = [ "AMPER", "AMPEREQUAL", + "ASYNC", "AT", "ATEQUAL", + "AWAIT", "CIRCUMFLEX", "CIRCUMFLEXEQUAL", "COLON", + "COLONEQUAL", "COMMA", "COMMENT", "DEDENT", @@ -68,25 +72,23 @@ __all__ = [ "STAREQUAL", "STRING", "TILDE", + "TYPE_COMMENT", + "TYPE_IGNORE", "TokenInfo", "VBAR", "VBAREQUAL", "detect_encoding", + "generate_tokens", "tok_name", "tokenize", "untokenize", ] -if sys.version_info >= (3, 8): - __all__ += ["ASYNC", "AWAIT", "COLONEQUAL", "generate_tokens", "TYPE_COMMENT", "TYPE_IGNORE"] - if sys.version_info >= (3, 10): __all__ += ["SOFT_KEYWORD"] -if sys.version_info >= (3, 8): - from token import EXACT_TOKEN_TYPES as EXACT_TOKEN_TYPES -else: - EXACT_TOKEN_TYPES: dict[str, int] +if sys.version_info >= (3, 12): + __all__ += ["EXCLAMATION", "FSTRING_END", "FSTRING_MIDDLE", "FSTRING_START"] cookie_re: Pattern[str] blank_re: Pattern[bytes] diff --git a/mypy/typeshed/stdlib/tomllib.pyi b/mypy/typeshed/stdlib/tomllib.pyi index 3a6ce93f87e1..d559568b912b 100644 --- a/mypy/typeshed/stdlib/tomllib.pyi +++ b/mypy/typeshed/stdlib/tomllib.pyi @@ -6,5 +6,5 @@ __all__ = ("loads", "load", "TOMLDecodeError") class TOMLDecodeError(ValueError): ... -def load(__fp: SupportsRead[bytes], *, parse_float: Callable[[str], Any] = ...) -> dict[str, Any]: ... -def loads(__s: str, *, parse_float: Callable[[str], Any] = ...) -> dict[str, Any]: ... +def load(fp: SupportsRead[bytes], /, *, parse_float: Callable[[str], Any] = ...) -> dict[str, Any]: ... +def loads(s: str, /, *, parse_float: Callable[[str], Any] = ...) -> dict[str, Any]: ... diff --git a/mypy/typeshed/stdlib/trace.pyi b/mypy/typeshed/stdlib/trace.pyi index f79b38f1ce82..d32647a55cb5 100644 --- a/mypy/typeshed/stdlib/trace.pyi +++ b/mypy/typeshed/stdlib/trace.pyi @@ -1,7 +1,7 @@ import sys import types -from _typeshed import StrPath, TraceFunction -from collections.abc import Callable, Mapping, Sequence +from _typeshed import Incomplete, StrPath, TraceFunction +from collections.abc import Callable, Iterable, Mapping, Sequence from typing import Any, TypeVar from typing_extensions import ParamSpec, TypeAlias @@ -12,6 +12,12 @@ _P = ParamSpec("_P") _FileModuleFunction: TypeAlias = tuple[str, str | None, str] class CoverageResults: + counts: dict[tuple[str, int], int] + counter: dict[tuple[str, int], int] + calledfuncs: dict[_FileModuleFunction, int] + callers: dict[tuple[_FileModuleFunction, _FileModuleFunction], int] + inifile: StrPath | None + outfile: StrPath | None def __init__( self, counts: dict[tuple[str, int], int] | None = None, @@ -27,15 +33,29 @@ class CoverageResults: ) -> tuple[int, int]: ... def is_ignored_filename(self, filename: str) -> bool: ... # undocumented +class _Ignore: + def __init__(self, modules: Iterable[str] | None = None, dirs: Iterable[StrPath] | None = None) -> None: ... + def names(self, filename: str, modulename: str) -> int: ... + class Trace: + inifile: StrPath | None + outfile: StrPath | None + ignore: _Ignore + counts: dict[str, int] + pathtobasename: dict[Incomplete, Incomplete] + donothing: int + trace: int + start_time: int | None + globaltrace: TraceFunction + localtrace: TraceFunction def __init__( self, count: int = 1, trace: int = 1, countfuncs: int = 0, countcallers: int = 0, - ignoremods: Sequence[str] = ..., - ignoredirs: Sequence[str] = ..., + ignoremods: Sequence[str] = (), + ignoredirs: Sequence[str] = (), infile: StrPath | None = None, outfile: StrPath | None = None, timing: bool = False, @@ -45,7 +65,7 @@ class Trace: self, cmd: str | types.CodeType, globals: Mapping[str, Any] | None = None, locals: Mapping[str, Any] | None = None ) -> None: ... if sys.version_info >= (3, 9): - def runfunc(self, __func: Callable[_P, _T], *args: _P.args, **kw: _P.kwargs) -> _T: ... + def runfunc(self, func: Callable[_P, _T], /, *args: _P.args, **kw: _P.kwargs) -> _T: ... else: def runfunc(self, func: Callable[_P, _T], *args: _P.args, **kw: _P.kwargs) -> _T: ... diff --git a/mypy/typeshed/stdlib/traceback.pyi b/mypy/typeshed/stdlib/traceback.pyi index 4483a8c2a1b0..39803003cfe5 100644 --- a/mypy/typeshed/stdlib/traceback.pyi +++ b/mypy/typeshed/stdlib/traceback.pyi @@ -1,9 +1,9 @@ import sys -from _typeshed import SupportsWrite +from _typeshed import SupportsWrite, Unused from collections.abc import Generator, Iterable, Iterator, Mapping from types import FrameType, TracebackType -from typing import Any, overload -from typing_extensions import Literal, Self, TypeAlias +from typing import Any, Literal, overload +from typing_extensions import Self, TypeAlias __all__ = [ "extract_stack", @@ -27,14 +27,15 @@ __all__ = [ "walk_tb", ] -_PT: TypeAlias = tuple[str, int, str, str | None] +_FrameSummaryTuple: TypeAlias = tuple[str, int, str, str | None] def print_tb(tb: TracebackType | None, limit: int | None = None, file: SupportsWrite[str] | None = None) -> None: ... if sys.version_info >= (3, 10): @overload def print_exception( - __exc: type[BaseException] | None, + exc: type[BaseException] | None, + /, value: BaseException | None = ..., tb: TracebackType | None = ..., limit: int | None = None, @@ -43,18 +44,19 @@ if sys.version_info >= (3, 10): ) -> None: ... @overload def print_exception( - __exc: BaseException, *, limit: int | None = None, file: SupportsWrite[str] | None = None, chain: bool = True + exc: BaseException, /, *, limit: int | None = None, file: SupportsWrite[str] | None = None, chain: bool = True ) -> None: ... @overload def format_exception( - __exc: type[BaseException] | None, + exc: type[BaseException] | None, + /, value: BaseException | None = ..., tb: TracebackType | None = ..., limit: int | None = None, chain: bool = True, ) -> list[str]: ... @overload - def format_exception(__exc: BaseException, *, limit: int | None = None, chain: bool = True) -> list[str]: ... + def format_exception(exc: BaseException, /, *, limit: int | None = None, chain: bool = True) -> list[str]: ... else: def print_exception( @@ -78,13 +80,16 @@ def print_last(limit: int | None = None, file: SupportsWrite[str] | None = None, def print_stack(f: FrameType | None = None, limit: int | None = None, file: SupportsWrite[str] | None = None) -> None: ... def extract_tb(tb: TracebackType | None, limit: int | None = None) -> StackSummary: ... def extract_stack(f: FrameType | None = None, limit: int | None = None) -> StackSummary: ... -def format_list(extracted_list: list[FrameSummary]) -> list[str]: ... +def format_list(extracted_list: Iterable[FrameSummary | _FrameSummaryTuple]) -> list[str]: ... # undocumented -def print_list(extracted_list: list[FrameSummary], file: SupportsWrite[str] | None = None) -> None: ... +def print_list(extracted_list: Iterable[FrameSummary | _FrameSummaryTuple], file: SupportsWrite[str] | None = None) -> None: ... if sys.version_info >= (3, 10): - def format_exception_only(__exc: type[BaseException] | None, value: BaseException | None = ...) -> list[str]: ... + @overload + def format_exception_only(exc: BaseException | None, /) -> list[str]: ... + @overload + def format_exception_only(exc: Unused, /, value: BaseException | None) -> list[str]: ... else: def format_exception_only(etype: type[BaseException] | None, value: BaseException | None) -> list[str]: ... @@ -132,12 +137,12 @@ class TracebackException: cls, exc: BaseException, *, - limit: int | None = ..., - lookup_lines: bool = ..., - capture_locals: bool = ..., - compact: bool = ..., - max_group_width: int = ..., - max_group_depth: int = ..., + limit: int | None = None, + lookup_lines: bool = True, + capture_locals: bool = False, + compact: bool = False, + max_group_width: int = 15, + max_group_depth: int = 10, ) -> Self: ... elif sys.version_info >= (3, 10): def __init__( @@ -157,10 +162,10 @@ class TracebackException: cls, exc: BaseException, *, - limit: int | None = ..., - lookup_lines: bool = ..., - capture_locals: bool = ..., - compact: bool = ..., + limit: int | None = None, + lookup_lines: bool = True, + capture_locals: bool = False, + compact: bool = False, ) -> Self: ... else: def __init__( @@ -176,7 +181,7 @@ class TracebackException: ) -> None: ... @classmethod def from_exception( - cls, exc: BaseException, *, limit: int | None = ..., lookup_lines: bool = ..., capture_locals: bool = ... + cls, exc: BaseException, *, limit: int | None = None, lookup_lines: bool = True, capture_locals: bool = False ) -> Self: ... def __eq__(self, other: object) -> bool: ... @@ -237,8 +242,7 @@ class FrameSummary(Iterable[Any]): def __getitem__(self, pos: int) -> Any: ... def __iter__(self) -> Iterator[Any]: ... def __eq__(self, other: object) -> bool: ... - if sys.version_info >= (3, 8): - def __len__(self) -> Literal[4]: ... + def __len__(self) -> Literal[4]: ... class StackSummary(list[FrameSummary]): @classmethod @@ -251,7 +255,7 @@ class StackSummary(list[FrameSummary]): capture_locals: bool = False, ) -> StackSummary: ... @classmethod - def from_list(cls, a_list: Iterable[FrameSummary | _PT]) -> StackSummary: ... + def from_list(cls, a_list: Iterable[FrameSummary | _FrameSummaryTuple]) -> StackSummary: ... if sys.version_info >= (3, 11): def format_frame_summary(self, frame_summary: FrameSummary) -> str: ... diff --git a/mypy/typeshed/stdlib/tracemalloc.pyi b/mypy/typeshed/stdlib/tracemalloc.pyi index 3dc8b8603fe5..e721e414138b 100644 --- a/mypy/typeshed/stdlib/tracemalloc.pyi +++ b/mypy/typeshed/stdlib/tracemalloc.pyi @@ -1,8 +1,8 @@ import sys from _tracemalloc import * from collections.abc import Sequence -from typing import Any, overload -from typing_extensions import SupportsIndex, TypeAlias +from typing import Any, SupportsIndex, overload +from typing_extensions import TypeAlias def get_object_traceback(obj: object) -> Traceback | None: ... def take_snapshot() -> Snapshot: ... @@ -37,6 +37,7 @@ class Statistic: traceback: Traceback def __init__(self, traceback: Traceback, size: int, count: int) -> None: ... def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... class StatisticDiff: count: int @@ -46,6 +47,7 @@ class StatisticDiff: traceback: Traceback def __init__(self, traceback: Traceback, size: int, size_diff: int, count: int, count_diff: int) -> None: ... def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... _FrameTuple: TypeAlias = tuple[str, int] @@ -56,6 +58,7 @@ class Frame: def lineno(self) -> int: ... def __init__(self, frame: _FrameTuple) -> None: ... def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... def __lt__(self, other: Frame) -> bool: ... if sys.version_info >= (3, 11): def __gt__(self, other: Frame) -> bool: ... @@ -80,6 +83,7 @@ class Trace: def traceback(self) -> Traceback: ... def __init__(self, trace: _TraceTuple) -> None: ... def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... class Traceback(Sequence[Frame]): if sys.version_info >= (3, 9): @@ -97,6 +101,7 @@ class Traceback(Sequence[Frame]): def __contains__(self, frame: Frame) -> bool: ... # type: ignore[override] def __len__(self) -> int: ... def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... def __lt__(self, other: Traceback) -> bool: ... if sys.version_info >= (3, 11): def __gt__(self, other: Traceback) -> bool: ... diff --git a/mypy/typeshed/stdlib/tty.pyi b/mypy/typeshed/stdlib/tty.pyi index 43f2e1cf9087..add0d57a8d4b 100644 --- a/mypy/typeshed/stdlib/tty.pyi +++ b/mypy/typeshed/stdlib/tty.pyi @@ -1,9 +1,16 @@ import sys +import termios from typing import IO from typing_extensions import TypeAlias if sys.platform != "win32": __all__ = ["setraw", "setcbreak"] + if sys.version_info >= (3, 12): + __all__ += ["cfmakeraw", "cfmakecbreak"] + + _ModeSetterReturn: TypeAlias = termios._AttrReturn + else: + _ModeSetterReturn: TypeAlias = None _FD: TypeAlias = int | IO[str] @@ -15,5 +22,9 @@ if sys.platform != "win32": ISPEED: int OSPEED: int CC: int - def setraw(fd: _FD, when: int = 2) -> None: ... - def setcbreak(fd: _FD, when: int = 2) -> None: ... + def setraw(fd: _FD, when: int = 2) -> _ModeSetterReturn: ... + def setcbreak(fd: _FD, when: int = 2) -> _ModeSetterReturn: ... + + if sys.version_info >= (3, 12): + def cfmakeraw(mode: termios._Attr) -> None: ... + def cfmakecbreak(mode: termios._Attr) -> None: ... diff --git a/mypy/typeshed/stdlib/turtle.pyi b/mypy/typeshed/stdlib/turtle.pyi index 8017c8290fb9..fd0723fd73ed 100644 --- a/mypy/typeshed/stdlib/turtle.pyi +++ b/mypy/typeshed/stdlib/turtle.pyi @@ -1,3 +1,4 @@ +import sys from collections.abc import Callable, Sequence from tkinter import Canvas, Frame, Misc, PhotoImage, Scrollbar from typing import Any, ClassVar, overload @@ -128,6 +129,9 @@ __all__ = [ "Terminator", ] +if sys.version_info >= (3, 12): + __all__ += ["teleport"] + # Note: '_Color' is the alias we use for arguments and _AnyColor is the # alias we use for return types. Really, these two aliases should be the # same, but as per the "no union returns" typeshed policy, we'll return @@ -249,6 +253,9 @@ class TNavigator: def reset(self) -> None: ... def degrees(self, fullcircle: float = 360.0) -> None: ... def radians(self) -> None: ... + if sys.version_info >= (3, 12): + def teleport(self, x: float | None = None, y: float | None = None, *, fill_gap: bool = False) -> None: ... + def forward(self, distance: float) -> None: ... def back(self, distance: float) -> None: ... def right(self, angle: float) -> None: ... @@ -321,12 +328,15 @@ class TPen: def color(self, r: float, g: float, b: float) -> None: ... @overload def color(self, color1: _Color, color2: _Color) -> None: ... + if sys.version_info >= (3, 12): + def teleport(self, x: float | None = None, y: float | None = None, *, fill_gap: bool = False) -> None: ... + def showturtle(self) -> None: ... def hideturtle(self) -> None: ... def isvisible(self) -> bool: ... # Note: signatures 1 and 2 overlap unsafely when no arguments are provided @overload - def pen(self) -> _PenState: ... # type: ignore[misc] + def pen(self) -> _PenState: ... # type: ignore[overload-overlap] @overload def pen( self, @@ -372,7 +382,7 @@ class RawTurtle(TPen, TNavigator): def shape(self, name: str) -> None: ... # Unsafely overlaps when no arguments are provided @overload - def shapesize(self) -> tuple[float, float, float]: ... # type: ignore[misc] + def shapesize(self) -> tuple[float, float, float]: ... # type: ignore[overload-overlap] @overload def shapesize( self, stretch_wid: float | None = None, stretch_len: float | None = None, outline: float | None = None @@ -383,7 +393,7 @@ class RawTurtle(TPen, TNavigator): def shearfactor(self, shear: float) -> None: ... # Unsafely overlaps when no arguments are provided @overload - def shapetransform(self) -> tuple[float, float, float, float]: ... # type: ignore[misc] + def shapetransform(self) -> tuple[float, float, float, float]: ... # type: ignore[overload-overlap] @overload def shapetransform( self, t11: float | None = None, t12: float | None = None, t21: float | None = None, t22: float | None = None @@ -405,7 +415,9 @@ class RawTurtle(TPen, TNavigator): def begin_fill(self) -> None: ... def end_fill(self) -> None: ... def dot(self, size: int | None = None, *color: _Color) -> None: ... - def write(self, arg: object, move: bool = False, align: str = "left", font: tuple[str, int, str] = ...) -> None: ... + def write( + self, arg: object, move: bool = False, align: str = "left", font: tuple[str, int, str] = ("Arial", 8, "normal") + ) -> None: ... def begin_poly(self) -> None: ... def end_poly(self) -> None: ... def get_poly(self) -> _PolygonCoords | None: ... @@ -605,7 +617,7 @@ def isvisible() -> bool: ... # Note: signatures 1 and 2 overlap unsafely when no arguments are provided @overload -def pen() -> _PenState: ... # type: ignore[misc] +def pen() -> _PenState: ... # type: ignore[overload-overlap] @overload def pen( pen: _PenState | None = None, @@ -639,9 +651,12 @@ def shape(name: None = None) -> str: ... @overload def shape(name: str) -> None: ... +if sys.version_info >= (3, 12): + def teleport(x: float | None = None, y: float | None = None, *, fill_gap: bool = False) -> None: ... + # Unsafely overlaps when no arguments are provided @overload -def shapesize() -> tuple[float, float, float]: ... # type: ignore[misc] +def shapesize() -> tuple[float, float, float]: ... # type: ignore[overload-overlap] @overload def shapesize(stretch_wid: float | None = None, stretch_len: float | None = None, outline: float | None = None) -> None: ... @overload @@ -651,7 +666,7 @@ def shearfactor(shear: float) -> None: ... # Unsafely overlaps when no arguments are provided @overload -def shapetransform() -> tuple[float, float, float, float]: ... # type: ignore[misc] +def shapetransform() -> tuple[float, float, float, float]: ... # type: ignore[overload-overlap] @overload def shapetransform( t11: float | None = None, t12: float | None = None, t21: float | None = None, t22: float | None = None @@ -674,7 +689,7 @@ def filling() -> bool: ... def begin_fill() -> None: ... def end_fill() -> None: ... def dot(size: int | None = None, *color: _Color) -> None: ... -def write(arg: object, move: bool = False, align: str = "left", font: tuple[str, int, str] = ...) -> None: ... +def write(arg: object, move: bool = False, align: str = "left", font: tuple[str, int, str] = ("Arial", 8, "normal")) -> None: ... def begin_poly() -> None: ... def end_poly() -> None: ... def get_poly() -> _PolygonCoords | None: ... diff --git a/mypy/typeshed/stdlib/types.pyi b/mypy/typeshed/stdlib/types.pyi index d529b3d9ad1a..38940b4345c8 100644 --- a/mypy/typeshed/stdlib/types.pyi +++ b/mypy/typeshed/stdlib/types.pyi @@ -1,5 +1,6 @@ import sys from _typeshed import SupportsKeysAndGetItem +from _typeshed.importlib import LoaderProtocol from collections.abc import ( AsyncGenerator, Awaitable, @@ -16,8 +17,8 @@ from collections.abc import ( from importlib.machinery import ModuleSpec # pytype crashes if types.MappingProxyType inherits from collections.abc.Mapping instead of typing.Mapping -from typing import Any, ClassVar, Generic, Mapping, Protocol, TypeVar, overload # noqa: Y022 -from typing_extensions import Literal, ParamSpec, final +from typing import Any, ClassVar, Literal, Mapping, TypeVar, final, overload # noqa: Y022 +from typing_extensions import ParamSpec, Self, TypeVarTuple, deprecated __all__ = [ "FunctionType", @@ -45,40 +46,30 @@ __all__ = [ "MethodWrapperType", "WrapperDescriptorType", "resolve_bases", + "CellType", ] -if sys.version_info >= (3, 8): - __all__ += ["CellType"] - if sys.version_info >= (3, 9): __all__ += ["GenericAlias"] if sys.version_info >= (3, 10): __all__ += ["EllipsisType", "NoneType", "NotImplementedType", "UnionType"] +if sys.version_info >= (3, 12): + __all__ += ["get_original_bases"] + # Note, all classes "defined" here require special handling. _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") -_T_co = TypeVar("_T_co", covariant=True) -_T_contra = TypeVar("_T_contra", contravariant=True) _KT = TypeVar("_KT") _VT_co = TypeVar("_VT_co", covariant=True) -_V_co = TypeVar("_V_co", covariant=True) - -@final -class _Cell: - if sys.version_info >= (3, 8): - def __init__(self, __contents: object = ...) -> None: ... - - __hash__: ClassVar[None] # type: ignore[assignment] - cell_contents: Any # Make sure this class definition stays roughly in line with `builtins.function` @final class FunctionType: @property - def __closure__(self) -> tuple[_Cell, ...] | None: ... + def __closure__(self) -> tuple[CellType, ...] | None: ... __code__: CodeType __defaults__: tuple[Any, ...] | None __dict__: dict[str, Any] @@ -91,32 +82,34 @@ class FunctionType: if sys.version_info >= (3, 10): @property def __builtins__(self) -> dict[str, Any]: ... + if sys.version_info >= (3, 12): + __type_params__: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] __module__: str - def __init__( - self, + def __new__( + cls, code: CodeType, globals: dict[str, Any], name: str | None = ..., argdefs: tuple[object, ...] | None = ..., - closure: tuple[_Cell, ...] | None = ..., - ) -> None: ... + closure: tuple[CellType, ...] | None = ..., + ) -> Self: ... def __call__(self, *args: Any, **kwargs: Any) -> Any: ... @overload - def __get__(self, obj: None, type: type) -> FunctionType: ... + def __get__(self, instance: None, owner: type, /) -> FunctionType: ... @overload - def __get__(self, obj: object, type: type | None = ...) -> MethodType: ... + def __get__(self, instance: object, owner: type | None = None, /) -> MethodType: ... LambdaType = FunctionType @final class CodeType: + def __eq__(self, value: object, /) -> bool: ... + def __hash__(self) -> int: ... @property def co_argcount(self) -> int: ... - if sys.version_info >= (3, 8): - @property - def co_posonlyargcount(self) -> int: ... - + @property + def co_posonlyargcount(self) -> int: ... @property def co_kwonlyargcount(self) -> int: ... @property @@ -139,8 +132,14 @@ class CodeType: def co_name(self) -> str: ... @property def co_firstlineno(self) -> int: ... - @property - def co_lnotab(self) -> bytes: ... + if sys.version_info >= (3, 10): + @property + @deprecated("Will be removed in Python 3.14. Use the co_lines() method instead.") + def co_lnotab(self) -> bytes: ... + else: + @property + def co_lnotab(self) -> bytes: ... + @property def co_freevars(self) -> tuple[str, ...]: ... @property @@ -157,86 +156,70 @@ class CodeType: def co_positions(self) -> Iterable[tuple[int | None, int | None, int | None, int | None]]: ... if sys.version_info >= (3, 11): - def __init__( - self, - __argcount: int, - __posonlyargcount: int, - __kwonlyargcount: int, - __nlocals: int, - __stacksize: int, - __flags: int, - __codestring: bytes, - __constants: tuple[object, ...], - __names: tuple[str, ...], - __varnames: tuple[str, ...], - __filename: str, - __name: str, - __qualname: str, - __firstlineno: int, - __linetable: bytes, - __exceptiontable: bytes, - __freevars: tuple[str, ...] = ..., - __cellvars: tuple[str, ...] = ..., - ) -> None: ... + def __new__( + cls, + argcount: int, + posonlyargcount: int, + kwonlyargcount: int, + nlocals: int, + stacksize: int, + flags: int, + codestring: bytes, + constants: tuple[object, ...], + names: tuple[str, ...], + varnames: tuple[str, ...], + filename: str, + name: str, + qualname: str, + firstlineno: int, + linetable: bytes, + exceptiontable: bytes, + freevars: tuple[str, ...] = ..., + cellvars: tuple[str, ...] = ..., + /, + ) -> Self: ... elif sys.version_info >= (3, 10): - def __init__( - self, - __argcount: int, - __posonlyargcount: int, - __kwonlyargcount: int, - __nlocals: int, - __stacksize: int, - __flags: int, - __codestring: bytes, - __constants: tuple[object, ...], - __names: tuple[str, ...], - __varnames: tuple[str, ...], - __filename: str, - __name: str, - __firstlineno: int, - __linetable: bytes, - __freevars: tuple[str, ...] = ..., - __cellvars: tuple[str, ...] = ..., - ) -> None: ... - elif sys.version_info >= (3, 8): - def __init__( - self, - __argcount: int, - __posonlyargcount: int, - __kwonlyargcount: int, - __nlocals: int, - __stacksize: int, - __flags: int, - __codestring: bytes, - __constants: tuple[object, ...], - __names: tuple[str, ...], - __varnames: tuple[str, ...], - __filename: str, - __name: str, - __firstlineno: int, - __lnotab: bytes, - __freevars: tuple[str, ...] = ..., - __cellvars: tuple[str, ...] = ..., - ) -> None: ... + def __new__( + cls, + argcount: int, + posonlyargcount: int, + kwonlyargcount: int, + nlocals: int, + stacksize: int, + flags: int, + codestring: bytes, + constants: tuple[object, ...], + names: tuple[str, ...], + varnames: tuple[str, ...], + filename: str, + name: str, + firstlineno: int, + linetable: bytes, + freevars: tuple[str, ...] = ..., + cellvars: tuple[str, ...] = ..., + /, + ) -> Self: ... else: - def __init__( - self, - __argcount: int, - __kwonlyargcount: int, - __nlocals: int, - __stacksize: int, - __flags: int, - __codestring: bytes, - __constants: tuple[object, ...], - __names: tuple[str, ...], - __varnames: tuple[str, ...], - __filename: str, - __name: str, - __firstlineno: int, - __lnotab: bytes, - __freevars: tuple[str, ...] = ..., - __cellvars: tuple[str, ...] = ..., - ) -> None: ... + def __new__( + cls, + argcount: int, + posonlyargcount: int, + kwonlyargcount: int, + nlocals: int, + stacksize: int, + flags: int, + codestring: bytes, + constants: tuple[object, ...], + names: tuple[str, ...], + varnames: tuple[str, ...], + filename: str, + name: str, + firstlineno: int, + lnotab: bytes, + freevars: tuple[str, ...] = ..., + cellvars: tuple[str, ...] = ..., + /, + ) -> Self: ... if sys.version_info >= (3, 11): def replace( self, @@ -281,7 +264,7 @@ class CodeType: co_name: str = ..., co_linetable: bytes = ..., ) -> CodeType: ... - elif sys.version_info >= (3, 8): + else: def replace( self, *, @@ -304,13 +287,13 @@ class CodeType: ) -> CodeType: ... @final -class MappingProxyType(Mapping[_KT, _VT_co], Generic[_KT, _VT_co]): +class MappingProxyType(Mapping[_KT, _VT_co]): __hash__: ClassVar[None] # type: ignore[assignment] - def __init__(self, mapping: SupportsKeysAndGetItem[_KT, _VT_co]) -> None: ... - def __getitem__(self, __key: _KT) -> _VT_co: ... + def __new__(cls, mapping: SupportsKeysAndGetItem[_KT, _VT_co]) -> Self: ... + def __getitem__(self, key: _KT, /) -> _VT_co: ... def __iter__(self) -> Iterator[_KT]: ... def __len__(self) -> int: ... - def __eq__(self, __value: object) -> bool: ... + def __eq__(self, value: object, /) -> bool: ... def copy(self) -> dict[_KT, _VT_co]: ... def keys(self) -> KeysView[_KT]: ... def values(self) -> ValuesView[_VT_co]: ... @@ -318,25 +301,23 @@ class MappingProxyType(Mapping[_KT, _VT_co], Generic[_KT, _VT_co]): if sys.version_info >= (3, 9): def __class_getitem__(cls, item: Any) -> GenericAlias: ... def __reversed__(self) -> Iterator[_KT]: ... - def __or__(self, __value: Mapping[_T1, _T2]) -> dict[_KT | _T1, _VT_co | _T2]: ... - def __ror__(self, __value: Mapping[_T1, _T2]) -> dict[_KT | _T1, _VT_co | _T2]: ... + def __or__(self, value: Mapping[_T1, _T2], /) -> dict[_KT | _T1, _VT_co | _T2]: ... + def __ror__(self, value: Mapping[_T1, _T2], /) -> dict[_KT | _T1, _VT_co | _T2]: ... class SimpleNamespace: __hash__: ClassVar[None] # type: ignore[assignment] def __init__(self, **kwargs: Any) -> None: ... - def __getattribute__(self, __name: str) -> Any: ... - def __setattr__(self, __name: str, __value: Any) -> None: ... - def __delattr__(self, __name: str) -> None: ... - -class _LoaderProtocol(Protocol): - def load_module(self, fullname: str) -> ModuleType: ... + def __eq__(self, value: object, /) -> bool: ... + def __getattribute__(self, name: str, /) -> Any: ... + def __setattr__(self, name: str, value: Any, /) -> None: ... + def __delattr__(self, name: str, /) -> None: ... class ModuleType: __name__: str __file__: str | None @property def __dict__(self) -> dict[str, Any]: ... # type: ignore[override] - __loader__: _LoaderProtocol | None + __loader__: LoaderProtocol | None __package__: str | None __path__: MutableSequence[str] __spec__: ModuleSpec | None @@ -347,45 +328,59 @@ class ModuleType: def __getattr__(self, name: str) -> Any: ... @final -class GeneratorType(Generator[_T_co, _T_contra, _V_co]): +class CellType: + def __new__(cls, contents: object = ..., /) -> Self: ... + __hash__: ClassVar[None] # type: ignore[assignment] + cell_contents: Any + +_YieldT_co = TypeVar("_YieldT_co", covariant=True) +_SendT_contra = TypeVar("_SendT_contra", contravariant=True) +_ReturnT_co = TypeVar("_ReturnT_co", covariant=True) + +@final +class GeneratorType(Generator[_YieldT_co, _SendT_contra, _ReturnT_co]): @property - def gi_yieldfrom(self) -> GeneratorType[_T_co, _T_contra, Any] | None: ... + def gi_yieldfrom(self) -> GeneratorType[_YieldT_co, _SendT_contra, Any] | None: ... if sys.version_info >= (3, 11): @property def gi_suspended(self) -> bool: ... __name__: str __qualname__: str - def __iter__(self) -> GeneratorType[_T_co, _T_contra, _V_co]: ... - def __next__(self) -> _T_co: ... - def send(self, __arg: _T_contra) -> _T_co: ... + def __iter__(self) -> Self: ... + def __next__(self) -> _YieldT_co: ... + def send(self, arg: _SendT_contra, /) -> _YieldT_co: ... @overload def throw( - self, __typ: type[BaseException], __val: BaseException | object = ..., __tb: TracebackType | None = ... - ) -> _T_co: ... + self, typ: type[BaseException], val: BaseException | object = ..., tb: TracebackType | None = ..., / + ) -> _YieldT_co: ... @overload - def throw(self, __typ: BaseException, __val: None = None, __tb: TracebackType | None = ...) -> _T_co: ... + def throw(self, typ: BaseException, val: None = None, tb: TracebackType | None = ..., /) -> _YieldT_co: ... @final -class AsyncGeneratorType(AsyncGenerator[_T_co, _T_contra]): +class AsyncGeneratorType(AsyncGenerator[_YieldT_co, _SendT_contra]): @property def ag_await(self) -> Awaitable[Any] | None: ... __name__: str __qualname__: str - def __aiter__(self) -> AsyncGeneratorType[_T_co, _T_contra]: ... - def __anext__(self) -> Coroutine[Any, Any, _T_co]: ... - def asend(self, __val: _T_contra) -> Coroutine[Any, Any, _T_co]: ... + if sys.version_info >= (3, 12): + @property + def ag_suspended(self) -> bool: ... + + def __aiter__(self) -> Self: ... + def __anext__(self) -> Coroutine[Any, Any, _YieldT_co]: ... + def asend(self, val: _SendT_contra, /) -> Coroutine[Any, Any, _YieldT_co]: ... @overload async def athrow( - self, __typ: type[BaseException], __val: BaseException | object = ..., __tb: TracebackType | None = ... - ) -> _T_co: ... + self, typ: type[BaseException], val: BaseException | object = ..., tb: TracebackType | None = ..., / + ) -> _YieldT_co: ... @overload - async def athrow(self, __typ: BaseException, __val: None = None, __tb: TracebackType | None = ...) -> _T_co: ... + async def athrow(self, typ: BaseException, val: None = None, tb: TracebackType | None = ..., /) -> _YieldT_co: ... def aclose(self) -> Coroutine[Any, Any, None]: ... if sys.version_info >= (3, 9): - def __class_getitem__(cls, __item: Any) -> GenericAlias: ... + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @final -class CoroutineType(Coroutine[_T_co, _T_contra, _V_co]): +class CoroutineType(Coroutine[_YieldT_co, _SendT_contra, _ReturnT_co]): __name__: str __qualname__: str @property @@ -395,43 +390,33 @@ class CoroutineType(Coroutine[_T_co, _T_contra, _V_co]): def cr_suspended(self) -> bool: ... def close(self) -> None: ... - def __await__(self) -> Generator[Any, None, _V_co]: ... - def send(self, __arg: _T_contra) -> _T_co: ... + def __await__(self) -> Generator[Any, None, _ReturnT_co]: ... + def send(self, arg: _SendT_contra, /) -> _YieldT_co: ... @overload def throw( - self, __typ: type[BaseException], __val: BaseException | object = ..., __tb: TracebackType | None = ... - ) -> _T_co: ... + self, typ: type[BaseException], val: BaseException | object = ..., tb: TracebackType | None = ..., / + ) -> _YieldT_co: ... @overload - def throw(self, __typ: BaseException, __val: None = None, __tb: TracebackType | None = ...) -> _T_co: ... - -class _StaticFunctionType: - # Fictional type to correct the type of MethodType.__func__. - # FunctionType is a descriptor, so mypy follows the descriptor protocol and - # converts MethodType.__func__ back to MethodType (the return type of - # FunctionType.__get__). But this is actually a special case; MethodType is - # implemented in C and its attribute access doesn't go through - # __getattribute__. - # By wrapping FunctionType in _StaticFunctionType, we get the right result; - # similar to wrapping a function in staticmethod() at runtime to prevent it - # being bound as a method. - def __get__(self, obj: object, type: type | None) -> FunctionType: ... + def throw(self, typ: BaseException, val: None = None, tb: TracebackType | None = ..., /) -> _YieldT_co: ... @final class MethodType: @property - def __closure__(self) -> tuple[_Cell, ...] | None: ... # inherited from the added function + def __closure__(self) -> tuple[CellType, ...] | None: ... # inherited from the added function @property def __defaults__(self) -> tuple[Any, ...] | None: ... # inherited from the added function @property - def __func__(self) -> _StaticFunctionType: ... + def __func__(self) -> Callable[..., Any]: ... @property def __self__(self) -> object: ... @property def __name__(self) -> str: ... # inherited from the added function @property def __qualname__(self) -> str: ... # inherited from the added function - def __init__(self, __func: Callable[..., Any], __obj: object) -> None: ... + def __new__(cls, func: Callable[..., Any], obj: object, /) -> Self: ... def __call__(self, *args: Any, **kwargs: Any) -> Any: ... + def __eq__(self, value: object, /) -> bool: ... + def __hash__(self) -> int: ... @final class BuiltinFunctionType: @@ -442,6 +427,8 @@ class BuiltinFunctionType: @property def __qualname__(self) -> str: ... def __call__(self, *args: Any, **kwargs: Any) -> Any: ... + def __eq__(self, value: object, /) -> bool: ... + def __hash__(self) -> int: ... BuiltinMethodType = BuiltinFunctionType @@ -454,7 +441,7 @@ class WrapperDescriptorType: @property def __objclass__(self) -> type: ... def __call__(self, *args: Any, **kwargs: Any) -> Any: ... - def __get__(self, __obj: Any, __type: type = ...) -> Any: ... + def __get__(self, instance: Any, owner: type | None = None, /) -> Any: ... @final class MethodWrapperType: @@ -467,8 +454,9 @@ class MethodWrapperType: @property def __objclass__(self) -> type: ... def __call__(self, *args: Any, **kwargs: Any) -> Any: ... - def __eq__(self, __other: object) -> bool: ... - def __ne__(self, __other: object) -> bool: ... + def __eq__(self, value: object, /) -> bool: ... + def __ne__(self, value: object, /) -> bool: ... + def __hash__(self) -> int: ... @final class MethodDescriptorType: @@ -479,7 +467,7 @@ class MethodDescriptorType: @property def __objclass__(self) -> type: ... def __call__(self, *args: Any, **kwargs: Any) -> Any: ... - def __get__(self, obj: Any, type: type = ...) -> Any: ... + def __get__(self, instance: Any, owner: type | None = None, /) -> Any: ... @final class ClassMethodDescriptorType: @@ -490,13 +478,13 @@ class ClassMethodDescriptorType: @property def __objclass__(self) -> type: ... def __call__(self, *args: Any, **kwargs: Any) -> Any: ... - def __get__(self, obj: Any, type: type = ...) -> Any: ... + def __get__(self, instance: Any, owner: type | None = None, /) -> Any: ... @final class TracebackType: - def __init__(self, tb_next: TracebackType | None, tb_frame: FrameType, tb_lasti: int, tb_lineno: int) -> None: ... + def __new__(cls, tb_next: TracebackType | None, tb_frame: FrameType, tb_lasti: int, tb_lineno: int) -> Self: ... tb_next: TracebackType | None - # the rest are read-only even in 3.7 + # the rest are read-only @property def tb_frame(self) -> FrameType: ... @property @@ -536,9 +524,9 @@ class GetSetDescriptorType: def __qualname__(self) -> str: ... @property def __objclass__(self) -> type: ... - def __get__(self, __obj: Any, __type: type = ...) -> Any: ... - def __set__(self, __instance: Any, __value: Any) -> None: ... - def __delete__(self, __obj: Any) -> None: ... + def __get__(self, instance: Any, owner: type | None = None, /) -> Any: ... + def __set__(self, instance: Any, value: Any, /) -> None: ... + def __delete__(self, instance: Any, /) -> None: ... @final class MemberDescriptorType: @@ -548,21 +536,24 @@ class MemberDescriptorType: def __qualname__(self) -> str: ... @property def __objclass__(self) -> type: ... - def __get__(self, __obj: Any, __type: type = ...) -> Any: ... - def __set__(self, __instance: Any, __value: Any) -> None: ... - def __delete__(self, __obj: Any) -> None: ... + def __get__(self, instance: Any, owner: type | None = None, /) -> Any: ... + def __set__(self, instance: Any, value: Any, /) -> None: ... + def __delete__(self, instance: Any, /) -> None: ... def new_class( name: str, - bases: Iterable[object] = ..., + bases: Iterable[object] = (), kwds: dict[str, Any] | None = None, exec_body: Callable[[dict[str, Any]], object] | None = None, ) -> type: ... def resolve_bases(bases: Iterable[object]) -> tuple[Any, ...]: ... def prepare_class( - name: str, bases: tuple[type, ...] = ..., kwds: dict[str, Any] | None = None + name: str, bases: tuple[type, ...] = (), kwds: dict[str, Any] | None = None ) -> tuple[type, dict[str, Any], dict[str, Any]]: ... +if sys.version_info >= (3, 12): + def get_original_bases(cls: type, /) -> tuple[Any, ...]: ... + # Actually a different type, but `property` is special and we want that too. DynamicClassAttribute = property @@ -571,15 +562,11 @@ _R = TypeVar("_R") _P = ParamSpec("_P") # it's not really an Awaitable, but can be used in an await expression. Real type: Generator & Awaitable -# The type: ignore is due to overlapping overloads, not the use of ParamSpec @overload -def coroutine(func: Callable[_P, Generator[Any, Any, _R]]) -> Callable[_P, Awaitable[_R]]: ... # type: ignore[misc] +def coroutine(func: Callable[_P, Generator[Any, Any, _R]]) -> Callable[_P, Awaitable[_R]]: ... # type: ignore[overload-overlap] @overload def coroutine(func: _Fn) -> _Fn: ... -if sys.version_info >= (3, 8): - CellType = _Cell - if sys.version_info >= (3, 9): class GenericAlias: @property @@ -588,8 +575,10 @@ if sys.version_info >= (3, 9): def __args__(self) -> tuple[Any, ...]: ... @property def __parameters__(self) -> tuple[Any, ...]: ... - def __init__(self, origin: type, args: Any) -> None: ... - def __getitem__(self, __typeargs: Any) -> GenericAlias: ... + def __new__(cls, origin: type, args: Any) -> Self: ... + def __getitem__(self, typeargs: Any, /) -> GenericAlias: ... + def __eq__(self, value: object, /) -> bool: ... + def __hash__(self) -> int: ... if sys.version_info >= (3, 11): @property def __unpacked__(self) -> bool: ... @@ -603,7 +592,10 @@ if sys.version_info >= (3, 10): @final class NoneType: def __bool__(self) -> Literal[False]: ... - EllipsisType = ellipsis # noqa: F821 from builtins + + @final + class EllipsisType: ... + from builtins import _NotImplementedType NotImplementedType = _NotImplementedType @@ -611,5 +603,7 @@ if sys.version_info >= (3, 10): class UnionType: @property def __args__(self) -> tuple[Any, ...]: ... - def __or__(self, __obj: Any) -> UnionType: ... - def __ror__(self, __obj: Any) -> UnionType: ... + def __or__(self, value: Any, /) -> UnionType: ... + def __ror__(self, value: Any, /) -> UnionType: ... + def __eq__(self, value: object, /) -> bool: ... + def __hash__(self) -> int: ... diff --git a/mypy/typeshed/stdlib/typing.pyi b/mypy/typeshed/stdlib/typing.pyi index d06b081d3ddc..d047f1c87621 100644 --- a/mypy/typeshed/stdlib/typing.pyi +++ b/mypy/typeshed/stdlib/typing.pyi @@ -1,10 +1,13 @@ -import collections # Needed by aliases like DefaultDict, see mypy issue 2986 +# Since this module defines "overload" it is not recognized by Ruff as typing.overload +# ruff: noqa: F811 +# TODO: The collections import is required, otherwise mypy crashes. +# https://github.com/python/mypy/issues/16744 +import collections # noqa: F401 # pyright: ignore import sys import typing_extensions from _collections_abc import dict_items, dict_keys, dict_values -from _typeshed import IdentityFunction, Incomplete, SupportsKeysAndGetItem +from _typeshed import IdentityFunction, ReadableBuffer, SupportsKeysAndGetItem from abc import ABCMeta, abstractmethod -from contextlib import AbstractAsyncContextManager, AbstractContextManager from re import Match as Match, Pattern as Pattern from types import ( BuiltinFunctionType, @@ -18,7 +21,12 @@ from types import ( TracebackType, WrapperDescriptorType, ) -from typing_extensions import Never as _Never, ParamSpec as _ParamSpec, final as _final +from typing_extensions import Never as _Never, ParamSpec as _ParamSpec + +if sys.version_info >= (3, 9): + from types import GenericAlias +if sys.version_info >= (3, 10): + from types import UnionType __all__ = [ "AbstractSet", @@ -41,6 +49,7 @@ __all__ = [ "DefaultDict", "Deque", "Dict", + "Final", "FrozenSet", "Generator", "Generic", @@ -50,6 +59,7 @@ __all__ = [ "Iterator", "KeysView", "List", + "Literal", "Mapping", "MappingView", "MutableMapping", @@ -58,6 +68,7 @@ __all__ = [ "NamedTuple", "NewType", "Optional", + "Protocol", "Reversible", "Sequence", "Set", @@ -66,38 +77,31 @@ __all__ = [ "SupportsBytes", "SupportsComplex", "SupportsFloat", + "SupportsIndex", "SupportsInt", "SupportsRound", "Text", "Tuple", "Type", "TypeVar", + "TypedDict", "Union", "ValuesView", "TYPE_CHECKING", "cast", + "final", + "get_args", + "get_origin", "get_type_hints", "no_type_check", "no_type_check_decorator", "overload", + "runtime_checkable", "ForwardRef", "NoReturn", "OrderedDict", ] -if sys.version_info >= (3, 8): - __all__ += [ - "Final", - "Literal", - "Protocol", - "SupportsIndex", - "TypedDict", - "final", - "get_args", - "get_origin", - "runtime_checkable", - ] - if sys.version_info >= (3, 9): __all__ += ["Annotated", "BinaryIO", "IO", "Match", "Pattern", "TextIO"] @@ -121,140 +125,182 @@ if sys.version_info >= (3, 11): "reveal_type", ] -ContextManager = AbstractContextManager -AsyncContextManager = AbstractAsyncContextManager - -# This itself is only available during type checking -def type_check_only(func_or_cls: _F) -> _F: ... +if sys.version_info >= (3, 12): + __all__ += ["TypeAliasType", "override"] Any = object() -@_final +def final(f: _T) -> _T: ... +@final class TypeVar: - __name__: str - __bound__: Any | None - __constraints__: tuple[Any, ...] - __covariant__: bool - __contravariant__: bool - def __init__( - self, name: str, *constraints: Any, bound: Any | None = None, covariant: bool = False, contravariant: bool = False - ) -> None: ... + @property + def __name__(self) -> str: ... + @property + def __bound__(self) -> Any | None: ... + @property + def __constraints__(self) -> tuple[Any, ...]: ... + @property + def __covariant__(self) -> bool: ... + @property + def __contravariant__(self) -> bool: ... + if sys.version_info >= (3, 12): + @property + def __infer_variance__(self) -> bool: ... + def __init__( + self, + name: str, + *constraints: Any, + bound: Any | None = None, + covariant: bool = False, + contravariant: bool = False, + infer_variance: bool = False, + ) -> None: ... + else: + def __init__( + self, name: str, *constraints: Any, bound: Any | None = None, covariant: bool = False, contravariant: bool = False + ) -> None: ... if sys.version_info >= (3, 10): def __or__(self, right: Any) -> _SpecialForm: ... def __ror__(self, left: Any) -> _SpecialForm: ... if sys.version_info >= (3, 11): - def __typing_subst__(self, arg: Incomplete) -> Incomplete: ... + def __typing_subst__(self, arg: Any) -> Any: ... # Used for an undocumented mypy feature. Does not exist at runtime. _promote = object() # N.B. Keep this definition in sync with typing_extensions._SpecialForm -@_final +@final class _SpecialForm: def __getitem__(self, parameters: Any) -> object: ... if sys.version_info >= (3, 10): def __or__(self, other: Any) -> _SpecialForm: ... def __ror__(self, other: Any) -> _SpecialForm: ... -_F = TypeVar("_F", bound=Callable[..., Any]) -_P = _ParamSpec("_P") -_T = TypeVar("_T") - -def overload(func: _F) -> _F: ... - -# Unlike the vast majority module-level objects in stub files, -# these `_SpecialForm` objects in typing need the default value `= ...`, -# due to the fact that they are used elswhere in the same file. -# Otherwise, flake8 erroneously flags them as undefined. -# `_SpecialForm` objects in typing.py that are not used elswhere in the same file -# do not need the default value assignment. -Union: _SpecialForm = ... -Generic: _SpecialForm = ... +Union: _SpecialForm +Generic: _SpecialForm # Protocol is only present in 3.8 and later, but mypy needs it unconditionally -Protocol: _SpecialForm = ... -Callable: _SpecialForm = ... -Type: _SpecialForm = ... -NoReturn: _SpecialForm = ... -ClassVar: _SpecialForm = ... +Protocol: _SpecialForm +Callable: _SpecialForm +Type: _SpecialForm +NoReturn: _SpecialForm +ClassVar: _SpecialForm Optional: _SpecialForm Tuple: _SpecialForm -if sys.version_info >= (3, 8): - Final: _SpecialForm - def final(f: _T) -> _T: ... - Literal: _SpecialForm - # TypedDict is a (non-subscriptable) special form. - TypedDict: object +Final: _SpecialForm + +Literal: _SpecialForm +# TypedDict is a (non-subscriptable) special form. +TypedDict: object if sys.version_info >= (3, 11): Self: _SpecialForm - Never: _SpecialForm = ... + Never: _SpecialForm Unpack: _SpecialForm Required: _SpecialForm NotRequired: _SpecialForm LiteralString: _SpecialForm + @final class TypeVarTuple: - __name__: str + @property + def __name__(self) -> str: ... def __init__(self, name: str) -> None: ... def __iter__(self) -> Any: ... def __typing_subst__(self, arg: Never) -> Never: ... - def __typing_prepare_subst__(self, alias: Incomplete, args: Incomplete) -> Incomplete: ... + def __typing_prepare_subst__(self, alias: Any, args: Any) -> tuple[Any, ...]: ... if sys.version_info >= (3, 10): + @final class ParamSpecArgs: - __origin__: ParamSpec + @property + def __origin__(self) -> ParamSpec: ... def __init__(self, origin: ParamSpec) -> None: ... + def __eq__(self, other: object) -> bool: ... + @final class ParamSpecKwargs: - __origin__: ParamSpec + @property + def __origin__(self) -> ParamSpec: ... def __init__(self, origin: ParamSpec) -> None: ... + def __eq__(self, other: object) -> bool: ... + @final class ParamSpec: - __name__: str - __bound__: Any | None - __covariant__: bool - __contravariant__: bool - def __init__( - self, name: str, *, bound: Any | None = None, contravariant: bool = False, covariant: bool = False - ) -> None: ... + @property + def __name__(self) -> str: ... + @property + def __bound__(self) -> Any | None: ... + @property + def __covariant__(self) -> bool: ... + @property + def __contravariant__(self) -> bool: ... + if sys.version_info >= (3, 12): + @property + def __infer_variance__(self) -> bool: ... + def __init__( + self, + name: str, + *, + bound: Any | None = None, + contravariant: bool = False, + covariant: bool = False, + infer_variance: bool = False, + ) -> None: ... + else: + def __init__( + self, name: str, *, bound: Any | None = None, contravariant: bool = False, covariant: bool = False + ) -> None: ... + @property def args(self) -> ParamSpecArgs: ... @property def kwargs(self) -> ParamSpecKwargs: ... if sys.version_info >= (3, 11): - def __typing_subst__(self, arg: Incomplete) -> Incomplete: ... - def __typing_prepare_subst__(self, alias: Incomplete, args: Incomplete) -> Incomplete: ... + def __typing_subst__(self, arg: Any) -> Any: ... + def __typing_prepare_subst__(self, alias: Any, args: Any) -> tuple[Any, ...]: ... def __or__(self, right: Any) -> _SpecialForm: ... def __ror__(self, left: Any) -> _SpecialForm: ... + Concatenate: _SpecialForm TypeAlias: _SpecialForm TypeGuard: _SpecialForm class NewType: def __init__(self, name: str, tp: Any) -> None: ... - def __call__(self, x: _T) -> _T: ... + if sys.version_info >= (3, 11): + @staticmethod + def __call__(x: _T, /) -> _T: ... + else: + def __call__(self, x: _T) -> _T: ... + def __or__(self, other: Any) -> _SpecialForm: ... def __ror__(self, other: Any) -> _SpecialForm: ... - __supertype__: type + __supertype__: type | NewType else: def NewType(name: str, tp: Any) -> Any: ... +_F = TypeVar("_F", bound=Callable[..., Any]) +_P = _ParamSpec("_P") +_T = TypeVar("_T") + # These type variables are used by the container types. _S = TypeVar("_S") _KT = TypeVar("_KT") # Key type. _VT = TypeVar("_VT") # Value type. _T_co = TypeVar("_T_co", covariant=True) # Any type covariant containers. -_V_co = TypeVar("_V_co", covariant=True) # Any type covariant containers. _KT_co = TypeVar("_KT_co", covariant=True) # Key type covariant containers. _VT_co = TypeVar("_VT_co", covariant=True) # Value type covariant containers. -_T_contra = TypeVar("_T_contra", contravariant=True) # Ditto contravariant. -_TC = TypeVar("_TC", bound=Type[object]) +_TC = TypeVar("_TC", bound=type[object]) +def overload(func: _F) -> _F: ... def no_type_check(arg: _F) -> _F: ... -def no_type_check_decorator(decorator: Callable[_P, _T]) -> Callable[_P, _T]: ... # type: ignore[misc] +def no_type_check_decorator(decorator: Callable[_P, _T]) -> Callable[_P, _T]: ... + +# This itself is only available during type checking +def type_check_only(func_or_cls: _F) -> _F: ... # Type aliases and type constructors @@ -279,9 +325,9 @@ if sys.version_info >= (3, 9): # Predefined type variables. AnyStr = TypeVar("AnyStr", str, bytes) # noqa: Y001 -# Technically in 3.7 this inherited from GenericMeta. But let's not reflect that, since -# type checkers tend to assume that Protocols all have the ABCMeta metaclass. -class _ProtocolMeta(ABCMeta): ... +class _ProtocolMeta(ABCMeta): + if sys.version_info >= (3, 12): + def __init__(cls, *args: Any, **kwargs: Any) -> None: ... # Abstract base classes. @@ -306,11 +352,10 @@ class SupportsBytes(Protocol, metaclass=ABCMeta): @abstractmethod def __bytes__(self) -> bytes: ... -if sys.version_info >= (3, 8): - @runtime_checkable - class SupportsIndex(Protocol, metaclass=ABCMeta): - @abstractmethod - def __index__(self) -> int: ... +@runtime_checkable +class SupportsIndex(Protocol, metaclass=ABCMeta): + @abstractmethod + def __index__(self) -> int: ... @runtime_checkable class SupportsAbs(Protocol[_T_co]): @@ -324,7 +369,7 @@ class SupportsRound(Protocol[_T_co]): def __round__(self) -> int: ... @overload @abstractmethod - def __round__(self, __ndigits: int) -> _T_co: ... + def __round__(self, ndigits: int, /) -> _T_co: ... @runtime_checkable class Sized(Protocol, metaclass=ABCMeta): @@ -355,20 +400,24 @@ class Reversible(Iterable[_T_co], Protocol[_T_co]): @abstractmethod def __reversed__(self) -> Iterator[_T_co]: ... -class Generator(Iterator[_T_co], Generic[_T_co, _T_contra, _V_co]): - def __next__(self) -> _T_co: ... +_YieldT_co = TypeVar("_YieldT_co", covariant=True) +_SendT_contra = TypeVar("_SendT_contra", contravariant=True, default=None) +_ReturnT_co = TypeVar("_ReturnT_co", covariant=True, default=None) + +class Generator(Iterator[_YieldT_co], Generic[_YieldT_co, _SendT_contra, _ReturnT_co]): + def __next__(self) -> _YieldT_co: ... @abstractmethod - def send(self, __value: _T_contra) -> _T_co: ... + def send(self, value: _SendT_contra, /) -> _YieldT_co: ... @overload @abstractmethod def throw( - self, __typ: Type[BaseException], __val: BaseException | object = None, __tb: TracebackType | None = None - ) -> _T_co: ... + self, typ: type[BaseException], val: BaseException | object = None, tb: TracebackType | None = None, / + ) -> _YieldT_co: ... @overload @abstractmethod - def throw(self, __typ: BaseException, __val: None = None, __tb: TracebackType | None = None) -> _T_co: ... + def throw(self, typ: BaseException, val: None = None, tb: TracebackType | None = None, /) -> _YieldT_co: ... def close(self) -> None: ... - def __iter__(self) -> Generator[_T_co, _T_contra, _V_co]: ... + def __iter__(self) -> Generator[_YieldT_co, _SendT_contra, _ReturnT_co]: ... @property def gi_code(self) -> CodeType: ... @property @@ -378,12 +427,28 @@ class Generator(Iterator[_T_co], Generic[_T_co, _T_contra, _V_co]): @property def gi_yieldfrom(self) -> Generator[Any, Any, Any] | None: ... +# NOTE: Prior to Python 3.13 these aliases are lacking the second _ExitT_co parameter +if sys.version_info >= (3, 13): + from contextlib import AbstractAsyncContextManager as AsyncContextManager, AbstractContextManager as ContextManager +else: + from contextlib import AbstractAsyncContextManager, AbstractContextManager + + @runtime_checkable + class ContextManager(AbstractContextManager[_T_co, bool | None], Protocol[_T_co]): ... + + @runtime_checkable + class AsyncContextManager(AbstractAsyncContextManager[_T_co, bool | None], Protocol[_T_co]): ... + @runtime_checkable class Awaitable(Protocol[_T_co]): @abstractmethod - def __await__(self) -> Generator[Any, None, _T_co]: ... + def __await__(self) -> Generator[Any, Any, _T_co]: ... + +# Non-default variations to accommodate couroutines, and `AwaitableGenerator` having a 4th type parameter. +_SendT_contra_nd = TypeVar("_SendT_contra_nd", contravariant=True) +_ReturnT_co_nd = TypeVar("_ReturnT_co_nd", covariant=True) -class Coroutine(Awaitable[_V_co], Generic[_T_co, _T_contra, _V_co]): +class Coroutine(Awaitable[_ReturnT_co_nd], Generic[_YieldT_co, _SendT_contra_nd, _ReturnT_co_nd]): __name__: str __qualname__: str @property @@ -391,19 +456,19 @@ class Coroutine(Awaitable[_V_co], Generic[_T_co, _T_contra, _V_co]): @property def cr_code(self) -> CodeType: ... @property - def cr_frame(self) -> FrameType: ... + def cr_frame(self) -> FrameType | None: ... @property def cr_running(self) -> bool: ... @abstractmethod - def send(self, __value: _T_contra) -> _T_co: ... + def send(self, value: _SendT_contra_nd, /) -> _YieldT_co: ... @overload @abstractmethod def throw( - self, __typ: Type[BaseException], __val: BaseException | object = None, __tb: TracebackType | None = None - ) -> _T_co: ... + self, typ: type[BaseException], val: BaseException | object = None, tb: TracebackType | None = None, / + ) -> _YieldT_co: ... @overload @abstractmethod - def throw(self, __typ: BaseException, __val: None = None, __tb: TracebackType | None = None) -> _T_co: ... + def throw(self, typ: BaseException, val: None = None, tb: TracebackType | None = None, /) -> _YieldT_co: ... @abstractmethod def close(self) -> None: ... @@ -411,7 +476,10 @@ class Coroutine(Awaitable[_V_co], Generic[_T_co, _T_contra, _V_co]): # The parameters correspond to Generator, but the 4th is the original type. @type_check_only class AwaitableGenerator( - Awaitable[_V_co], Generator[_T_co, _T_contra, _V_co], Generic[_T_co, _T_contra, _V_co, _S], metaclass=ABCMeta + Awaitable[_ReturnT_co_nd], + Generator[_YieldT_co, _SendT_contra_nd, _ReturnT_co_nd], + Generic[_YieldT_co, _SendT_contra_nd, _ReturnT_co_nd, _S], + metaclass=ABCMeta, ): ... @runtime_checkable @@ -425,18 +493,18 @@ class AsyncIterator(AsyncIterable[_T_co], Protocol[_T_co]): def __anext__(self) -> Awaitable[_T_co]: ... def __aiter__(self) -> AsyncIterator[_T_co]: ... -class AsyncGenerator(AsyncIterator[_T_co], Generic[_T_co, _T_contra]): - def __anext__(self) -> Awaitable[_T_co]: ... +class AsyncGenerator(AsyncIterator[_YieldT_co], Generic[_YieldT_co, _SendT_contra]): + def __anext__(self) -> Awaitable[_YieldT_co]: ... @abstractmethod - def asend(self, __value: _T_contra) -> Awaitable[_T_co]: ... + def asend(self, value: _SendT_contra, /) -> Awaitable[_YieldT_co]: ... @overload @abstractmethod def athrow( - self, __typ: Type[BaseException], __val: BaseException | object = None, __tb: TracebackType | None = None - ) -> Awaitable[_T_co]: ... + self, typ: type[BaseException], val: BaseException | object = None, tb: TracebackType | None = None, / + ) -> Awaitable[_YieldT_co]: ... @overload @abstractmethod - def athrow(self, __typ: BaseException, __val: None = None, __tb: TracebackType | None = None) -> Awaitable[_T_co]: ... + def athrow(self, typ: BaseException, val: None = None, tb: TracebackType | None = None, /) -> Awaitable[_YieldT_co]: ... def aclose(self) -> Awaitable[None]: ... @property def ag_await(self) -> Any: ... @@ -451,7 +519,7 @@ class AsyncGenerator(AsyncIterator[_T_co], Generic[_T_co, _T_contra]): class Container(Protocol[_T_co]): # This is generic more on vibes than anything else @abstractmethod - def __contains__(self, __x: object) -> bool: ... + def __contains__(self, x: object, /) -> bool: ... @runtime_checkable class Collection(Iterable[_T_co], Container[_T_co], Protocol[_T_co]): @@ -459,7 +527,7 @@ class Collection(Iterable[_T_co], Container[_T_co], Protocol[_T_co]): @abstractmethod def __len__(self) -> int: ... -class Sequence(Collection[_T_co], Reversible[_T_co], Generic[_T_co]): +class Sequence(Collection[_T_co], Reversible[_T_co]): @overload @abstractmethod def __getitem__(self, index: int) -> _T_co: ... @@ -473,7 +541,7 @@ class Sequence(Collection[_T_co], Reversible[_T_co], Generic[_T_co]): def __iter__(self) -> Iterator[_T_co]: ... def __reversed__(self) -> Iterator[_T_co]: ... -class MutableSequence(Sequence[_T], Generic[_T]): +class MutableSequence(Sequence[_T]): @abstractmethod def insert(self, index: int, value: _T) -> None: ... @overload @@ -503,7 +571,7 @@ class MutableSequence(Sequence[_T], Generic[_T]): def remove(self, value: _T) -> None: ... def __iadd__(self, values: Iterable[_T]) -> typing_extensions.Self: ... -class AbstractSet(Collection[_T_co], Generic[_T_co]): +class AbstractSet(Collection[_T_co]): @abstractmethod def __contains__(self, x: object) -> bool: ... def _hash(self) -> int: ... @@ -516,9 +584,10 @@ class AbstractSet(Collection[_T_co], Generic[_T_co]): def __or__(self, other: AbstractSet[_T]) -> AbstractSet[_T_co | _T]: ... def __sub__(self, other: AbstractSet[Any]) -> AbstractSet[_T_co]: ... def __xor__(self, other: AbstractSet[_T]) -> AbstractSet[_T_co | _T]: ... + def __eq__(self, other: object) -> bool: ... def isdisjoint(self, other: Iterable[Any]) -> bool: ... -class MutableSet(AbstractSet[_T], Generic[_T]): +class MutableSet(AbstractSet[_T]): @abstractmethod def add(self, value: _T) -> None: ... @abstractmethod @@ -542,9 +611,7 @@ class ItemsView(MappingView, AbstractSet[tuple[_KT_co, _VT_co]], Generic[_KT_co, def __rand__(self, other: Iterable[_T]) -> set[_T]: ... def __contains__(self, item: object) -> bool: ... def __iter__(self) -> Iterator[tuple[_KT_co, _VT_co]]: ... - if sys.version_info >= (3, 8): - def __reversed__(self) -> Iterator[tuple[_KT_co, _VT_co]]: ... - + def __reversed__(self) -> Iterator[tuple[_KT_co, _VT_co]]: ... def __or__(self, other: Iterable[_T]) -> set[tuple[_KT_co, _VT_co] | _T]: ... def __ror__(self, other: Iterable[_T]) -> set[tuple[_KT_co, _VT_co] | _T]: ... def __sub__(self, other: Iterable[Any]) -> set[tuple[_KT_co, _VT_co]]: ... @@ -552,15 +619,13 @@ class ItemsView(MappingView, AbstractSet[tuple[_KT_co, _VT_co]], Generic[_KT_co, def __xor__(self, other: Iterable[_T]) -> set[tuple[_KT_co, _VT_co] | _T]: ... def __rxor__(self, other: Iterable[_T]) -> set[tuple[_KT_co, _VT_co] | _T]: ... -class KeysView(MappingView, AbstractSet[_KT_co], Generic[_KT_co]): +class KeysView(MappingView, AbstractSet[_KT_co]): def __init__(self, mapping: Mapping[_KT_co, Any]) -> None: ... # undocumented def __and__(self, other: Iterable[Any]) -> set[_KT_co]: ... def __rand__(self, other: Iterable[_T]) -> set[_T]: ... def __contains__(self, key: object) -> bool: ... def __iter__(self) -> Iterator[_KT_co]: ... - if sys.version_info >= (3, 8): - def __reversed__(self) -> Iterator[_KT_co]: ... - + def __reversed__(self) -> Iterator[_KT_co]: ... def __or__(self, other: Iterable[_T]) -> set[_KT_co | _T]: ... def __ror__(self, other: Iterable[_T]) -> set[_KT_co | _T]: ... def __sub__(self, other: Iterable[Any]) -> set[_KT_co]: ... @@ -568,38 +633,40 @@ class KeysView(MappingView, AbstractSet[_KT_co], Generic[_KT_co]): def __xor__(self, other: Iterable[_T]) -> set[_KT_co | _T]: ... def __rxor__(self, other: Iterable[_T]) -> set[_KT_co | _T]: ... -class ValuesView(MappingView, Collection[_VT_co], Generic[_VT_co]): +class ValuesView(MappingView, Collection[_VT_co]): def __init__(self, mapping: Mapping[Any, _VT_co]) -> None: ... # undocumented def __contains__(self, value: object) -> bool: ... def __iter__(self) -> Iterator[_VT_co]: ... - if sys.version_info >= (3, 8): - def __reversed__(self) -> Iterator[_VT_co]: ... + def __reversed__(self) -> Iterator[_VT_co]: ... class Mapping(Collection[_KT], Generic[_KT, _VT_co]): # TODO: We wish the key type could also be covariant, but that doesn't work, # see discussion in https://github.com/python/typing/pull/273. @abstractmethod - def __getitem__(self, __key: _KT) -> _VT_co: ... + def __getitem__(self, key: _KT, /) -> _VT_co: ... # Mixin methods @overload - def get(self, __key: _KT) -> _VT_co | None: ... + def get(self, key: _KT, /) -> _VT_co | None: ... @overload - def get(self, __key: _KT, default: _VT_co | _T) -> _VT_co | _T: ... + def get(self, key: _KT, /, default: _VT_co | _T) -> _VT_co | _T: ... def items(self) -> ItemsView[_KT, _VT_co]: ... def keys(self) -> KeysView[_KT]: ... def values(self) -> ValuesView[_VT_co]: ... - def __contains__(self, __o: object) -> bool: ... + def __contains__(self, key: object, /) -> bool: ... + def __eq__(self, other: object, /) -> bool: ... -class MutableMapping(Mapping[_KT, _VT], Generic[_KT, _VT]): +class MutableMapping(Mapping[_KT, _VT]): @abstractmethod - def __setitem__(self, __key: _KT, __value: _VT) -> None: ... + def __setitem__(self, key: _KT, value: _VT, /) -> None: ... @abstractmethod - def __delitem__(self, __key: _KT) -> None: ... + def __delitem__(self, key: _KT, /) -> None: ... def clear(self) -> None: ... @overload - def pop(self, __key: _KT) -> _VT: ... + def pop(self, key: _KT, /) -> _VT: ... @overload - def pop(self, __key: _KT, default: _VT | _T) -> _VT | _T: ... + def pop(self, key: _KT, /, default: _VT) -> _VT: ... + @overload + def pop(self, key: _KT, /, default: _T) -> _VT | _T: ... def popitem(self) -> tuple[_KT, _VT]: ... # This overload should be allowed only if the value type is compatible with None. # @@ -608,9 +675,9 @@ class MutableMapping(Mapping[_KT, _VT], Generic[_KT, _VT]): # -- collections.ChainMap.setdefault # -- weakref.WeakKeyDictionary.setdefault @overload - def setdefault(self: MutableMapping[_KT, _T | None], __key: _KT, __default: None = None) -> _T | None: ... + def setdefault(self: MutableMapping[_KT, _T | None], key: _KT, default: None = None, /) -> _T | None: ... @overload - def setdefault(self, __key: _KT, __default: _VT) -> _VT: ... + def setdefault(self, key: _KT, default: _VT, /) -> _VT: ... # 'update' used to take a Union, but using overloading is better. # The second overloaded type here is a bit too general, because # Mapping[tuple[_KT, _VT], W] is a subclass of Iterable[tuple[_KT, _VT]], @@ -632,9 +699,9 @@ class MutableMapping(Mapping[_KT, _VT], Generic[_KT, _VT]): # -- weakref.WeakValueDictionary.__ior__ # -- weakref.WeakKeyDictionary.__ior__ @overload - def update(self, __m: SupportsKeysAndGetItem[_KT, _VT], **kwargs: _VT) -> None: ... + def update(self, m: SupportsKeysAndGetItem[_KT, _VT], /, **kwargs: _VT) -> None: ... @overload - def update(self, __m: Iterable[tuple[_KT, _VT]], **kwargs: _VT) -> None: ... + def update(self, m: Iterable[tuple[_KT, _VT]], /, **kwargs: _VT) -> None: ... @overload def update(self, **kwargs: _VT) -> None: ... @@ -645,14 +712,16 @@ TYPE_CHECKING: bool # In stubs, the arguments of the IO class are marked as positional-only. # This differs from runtime, but better reflects the fact that in reality # classes deriving from IO use different names for the arguments. -class IO(Iterator[AnyStr], Generic[AnyStr]): +class IO(Iterator[AnyStr]): # At runtime these are all abstract properties, # but making them abstract in the stub is hugely disruptive, for not much gain. # See #8726 @property def mode(self) -> str: ... + # Usually str, but may be bytes if a bytes path was passed to open(). See #10737. + # If PEP 696 becomes available, we may want to use a defaulted TypeVar here. @property - def name(self) -> str: ... + def name(self) -> str | Any: ... @abstractmethod def close(self) -> None: ... @property @@ -664,27 +733,41 @@ class IO(Iterator[AnyStr], Generic[AnyStr]): @abstractmethod def isatty(self) -> bool: ... @abstractmethod - def read(self, __n: int = -1) -> AnyStr: ... + def read(self, n: int = -1, /) -> AnyStr: ... @abstractmethod def readable(self) -> bool: ... @abstractmethod - def readline(self, __limit: int = -1) -> AnyStr: ... + def readline(self, limit: int = -1, /) -> AnyStr: ... @abstractmethod - def readlines(self, __hint: int = -1) -> list[AnyStr]: ... + def readlines(self, hint: int = -1, /) -> list[AnyStr]: ... @abstractmethod - def seek(self, __offset: int, __whence: int = 0) -> int: ... + def seek(self, offset: int, whence: int = 0, /) -> int: ... @abstractmethod def seekable(self) -> bool: ... @abstractmethod def tell(self) -> int: ... @abstractmethod - def truncate(self, __size: int | None = None) -> int: ... + def truncate(self, size: int | None = None, /) -> int: ... @abstractmethod def writable(self) -> bool: ... @abstractmethod - def write(self, __s: AnyStr) -> int: ... + @overload + def write(self: IO[str], s: str, /) -> int: ... + @abstractmethod + @overload + def write(self: IO[bytes], s: ReadableBuffer, /) -> int: ... @abstractmethod - def writelines(self, __lines: Iterable[AnyStr]) -> None: ... + @overload + def write(self, s: AnyStr, /) -> int: ... + @abstractmethod + @overload + def writelines(self: IO[str], lines: Iterable[str], /) -> None: ... + @abstractmethod + @overload + def writelines(self: IO[bytes], lines: Iterable[ReadableBuffer], /) -> None: ... + @abstractmethod + @overload + def writelines(self, lines: Iterable[AnyStr], /) -> None: ... @abstractmethod def __next__(self) -> AnyStr: ... @abstractmethod @@ -693,7 +776,7 @@ class IO(Iterator[AnyStr], Generic[AnyStr]): def __enter__(self) -> IO[AnyStr]: ... @abstractmethod def __exit__( - self, __t: Type[BaseException] | None, __value: BaseException | None, __traceback: TracebackType | None + self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None, / ) -> None: ... class BinaryIO(IO[bytes]): @@ -715,7 +798,7 @@ class TextIO(IO[str]): @abstractmethod def __enter__(self) -> TextIO: ... -class ByteString(Sequence[int], metaclass=ABCMeta): ... +ByteString: typing_extensions.TypeAlias = bytes | bytearray | memoryview # Functions @@ -744,21 +827,34 @@ else: obj: _get_type_hints_obj_allowed_types, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None ) -> dict[str, Any]: ... -if sys.version_info >= (3, 8): +def get_args(tp: Any) -> tuple[Any, ...]: ... + +if sys.version_info >= (3, 10): + @overload + def get_origin(tp: ParamSpecArgs | ParamSpecKwargs) -> ParamSpec: ... + @overload + def get_origin(tp: UnionType) -> type[UnionType]: ... + +if sys.version_info >= (3, 9): + @overload + def get_origin(tp: GenericAlias) -> type: ... + @overload + def get_origin(tp: Any) -> Any | None: ... + +else: def get_origin(tp: Any) -> Any | None: ... - def get_args(tp: Any) -> tuple[Any, ...]: ... @overload -def cast(typ: Type[_T], val: Any) -> _T: ... +def cast(typ: type[_T], val: Any) -> _T: ... @overload def cast(typ: str, val: Any) -> Any: ... @overload def cast(typ: object, val: Any) -> Any: ... if sys.version_info >= (3, 11): - def reveal_type(__obj: _T) -> _T: ... - def assert_never(__arg: Never) -> Never: ... - def assert_type(__val: _T, __typ: Any) -> _T: ... + def reveal_type(obj: _T, /) -> _T: ... + def assert_never(arg: Never, /) -> Never: ... + def assert_type(val: _T, typ: Any, /) -> _T: ... def clear_overloads() -> None: ... def get_overloads(func: Callable[..., object]) -> Sequence[Callable[..., object]]: ... def dataclass_transform( @@ -766,31 +862,33 @@ if sys.version_info >= (3, 11): eq_default: bool = True, order_default: bool = False, kw_only_default: bool = False, - field_specifiers: tuple[type[Any] | Callable[..., Any], ...] = ..., + frozen_default: bool = False, # on 3.11, runtime accepts it as part of kwargs + field_specifiers: tuple[type[Any] | Callable[..., Any], ...] = (), **kwargs: Any, ) -> IdentityFunction: ... # Type constructors class NamedTuple(tuple[Any, ...]): - if sys.version_info < (3, 8): - _field_types: collections.OrderedDict[str, type] - elif sys.version_info < (3, 9): - _field_types: dict[str, type] - _field_defaults: dict[str, Any] - _fields: tuple[str, ...] - _source: str + if sys.version_info < (3, 9): + _field_types: ClassVar[dict[str, type]] + _field_defaults: ClassVar[dict[str, Any]] + _fields: ClassVar[tuple[str, ...]] + # __orig_bases__ sometimes exists on <3.12, but not consistently + # So we only add it to the stub on 3.12+. + if sys.version_info >= (3, 12): + __orig_bases__: ClassVar[tuple[Any, ...]] + @overload - def __init__(self, typename: str, fields: Iterable[tuple[str, Any]] = ...) -> None: ... + def __init__(self, typename: str, fields: Iterable[tuple[str, Any]], /) -> None: ... @overload - def __init__(self, typename: str, fields: None = None, **kwargs: Any) -> None: ... + @typing_extensions.deprecated( + "Creating a typing.NamedTuple using keyword arguments is deprecated and support will be removed in Python 3.15" + ) + def __init__(self, typename: str, fields: None = None, /, **kwargs: Any) -> None: ... @classmethod - def _make(cls: Type[_T], iterable: Iterable[Any]) -> _T: ... - if sys.version_info >= (3, 8): - def _asdict(self) -> dict[str, Any]: ... - else: - def _asdict(self) -> collections.OrderedDict[str, Any]: ... - + def _make(cls, iterable: Iterable[Any]) -> typing_extensions.Self: ... + def _asdict(self) -> dict[str, Any]: ... def _replace(self, **kwargs: Any) -> typing_extensions.Self: ... # Internal mypy fallback type for all typed dicts (does not exist at runtime) @@ -801,22 +899,35 @@ class _TypedDict(Mapping[str, object], metaclass=ABCMeta): if sys.version_info >= (3, 9): __required_keys__: ClassVar[frozenset[str]] __optional_keys__: ClassVar[frozenset[str]] + # __orig_bases__ sometimes exists on <3.12, but not consistently, + # so we only add it to the stub on 3.12+ + if sys.version_info >= (3, 12): + __orig_bases__: ClassVar[tuple[Any, ...]] + def copy(self) -> typing_extensions.Self: ... # Using Never so that only calls using mypy plugin hook that specialize the signature # can go through. def setdefault(self, k: _Never, default: object) -> object: ... # Mypy plugin hook for 'pop' expects that 'default' has a type variable type. def pop(self, k: _Never, default: _T = ...) -> object: ... # pyright: ignore[reportInvalidTypeVarUse] - def update(self: _T, __m: _T) -> None: ... + def update(self: _T, m: _T, /) -> None: ... def __delitem__(self, k: _Never) -> None: ... def items(self) -> dict_items[str, object]: ... def keys(self) -> dict_keys[str, object]: ... def values(self) -> dict_values[str, object]: ... if sys.version_info >= (3, 9): - def __or__(self, __value: typing_extensions.Self) -> typing_extensions.Self: ... - def __ior__(self, __value: typing_extensions.Self) -> typing_extensions.Self: ... - -@_final + @overload + def __or__(self, value: typing_extensions.Self, /) -> typing_extensions.Self: ... + @overload + def __or__(self, value: dict[str, Any], /) -> dict[str, object]: ... + @overload + def __ror__(self, value: typing_extensions.Self, /) -> typing_extensions.Self: ... + @overload + def __ror__(self, value: dict[str, Any], /) -> dict[str, object]: ... + # supposedly incompatible definitions of __or__ and __ior__ + def __ior__(self, value: typing_extensions.Self, /) -> typing_extensions.Self: ... # type: ignore[misc] + +@final class ForwardRef: __forward_arg__: str __forward_code__: CodeType @@ -839,6 +950,7 @@ class ForwardRef: def _evaluate(self, globalns: dict[str, Any] | None, localns: dict[str, Any] | None) -> Any | None: ... def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... if sys.version_info >= (3, 11): def __or__(self, other: Any) -> _SpecialForm: ... def __ror__(self, other: Any) -> _SpecialForm: ... @@ -847,3 +959,29 @@ if sys.version_info >= (3, 10): def is_typeddict(tp: object) -> bool: ... def _type_repr(obj: object) -> str: ... + +if sys.version_info >= (3, 12): + def override(method: _F, /) -> _F: ... + @final + class TypeAliasType: + def __init__( + self, name: str, value: Any, *, type_params: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] = () + ) -> None: ... + @property + def __value__(self) -> Any: ... + @property + def __type_params__(self) -> tuple[TypeVar | ParamSpec | TypeVarTuple, ...]: ... + @property + def __parameters__(self) -> tuple[Any, ...]: ... + @property + def __name__(self) -> str: ... + # It's writable on types, but not on instances of TypeAliasType. + @property + def __module__(self) -> str | None: ... # type: ignore[override] + def __getitem__(self, parameters: Any) -> Any: ... + def __or__(self, right: Any) -> _SpecialForm: ... + def __ror__(self, left: Any) -> _SpecialForm: ... + +if sys.version_info >= (3, 13): + def is_protocol(tp: type, /) -> bool: ... + def get_protocol_members(tp: type, /) -> frozenset[str]: ... diff --git a/mypy/typeshed/stdlib/typing_extensions.pyi b/mypy/typeshed/stdlib/typing_extensions.pyi index bf3892d5709e..48a398ba4095 100644 --- a/mypy/typeshed/stdlib/typing_extensions.pyi +++ b/mypy/typeshed/stdlib/typing_extensions.pyi @@ -1,39 +1,82 @@ import abc -import collections import sys import typing from _collections_abc import dict_items, dict_keys, dict_values -from _typeshed import IdentityFunction, Incomplete -from collections.abc import Iterable -from typing import ( # noqa: Y022,Y039 +from _typeshed import IdentityFunction +from typing import ( # noqa: Y022,Y037,Y038,Y039 + IO as IO, TYPE_CHECKING as TYPE_CHECKING, + AbstractSet as AbstractSet, Any as Any, + AnyStr as AnyStr, AsyncContextManager as AsyncContextManager, AsyncGenerator as AsyncGenerator, AsyncIterable as AsyncIterable, AsyncIterator as AsyncIterator, Awaitable as Awaitable, - Callable, + BinaryIO as BinaryIO, + Callable as Callable, ChainMap as ChainMap, ClassVar as ClassVar, + Collection as Collection, + Container as Container, ContextManager as ContextManager, Coroutine as Coroutine, Counter as Counter, DefaultDict as DefaultDict, Deque as Deque, - Mapping, - NewType as NewType, + Dict as Dict, + ForwardRef as ForwardRef, + FrozenSet as FrozenSet, + Generator as Generator, + Generic as Generic, + Hashable as Hashable, + ItemsView as ItemsView, + Iterable as Iterable, + Iterator as Iterator, + KeysView as KeysView, + List as List, + Mapping as Mapping, + MappingView as MappingView, + Match as Match, + MutableMapping as MutableMapping, + MutableSequence as MutableSequence, + MutableSet as MutableSet, NoReturn as NoReturn, - Sequence, + Optional as Optional, + Pattern as Pattern, + Reversible as Reversible, + Sequence as Sequence, + Set as Set, + Sized as Sized, + SupportsAbs as SupportsAbs, + SupportsBytes as SupportsBytes, + SupportsComplex as SupportsComplex, + SupportsFloat as SupportsFloat, + SupportsInt as SupportsInt, + SupportsRound as SupportsRound, Text as Text, + TextIO as TextIO, + Tuple as Tuple, Type as Type, + Union as Union, + ValuesView as ValuesView, _Alias, + cast as cast, + no_type_check as no_type_check, + no_type_check_decorator as no_type_check_decorator, overload as overload, type_check_only, ) +if sys.version_info >= (3, 10): + from types import UnionType +if sys.version_info >= (3, 9): + from types import GenericAlias + __all__ = [ "Any", + "Buffer", "ClassVar", "Concatenate", "Final", @@ -61,10 +104,17 @@ __all__ = [ "OrderedDict", "TypedDict", "SupportsIndex", + "SupportsAbs", + "SupportsRound", + "SupportsBytes", + "SupportsComplex", + "SupportsFloat", + "SupportsInt", "Annotated", "assert_never", "assert_type", "dataclass_transform", + "deprecated", "final", "IntVar", "is_typeddict", @@ -78,6 +128,7 @@ __all__ = [ "runtime_checkable", "Text", "TypeAlias", + "TypeAliasType", "TypeGuard", "TYPE_CHECKING", "Never", @@ -87,13 +138,56 @@ __all__ = [ "clear_overloads", "get_args", "get_origin", + "get_original_bases", "get_overloads", "get_type_hints", + "AbstractSet", + "AnyStr", + "BinaryIO", + "Callable", + "Collection", + "Container", + "Dict", + "Doc", + "ForwardRef", + "FrozenSet", + "Generator", + "Generic", + "Hashable", + "IO", + "ItemsView", + "Iterable", + "Iterator", + "KeysView", + "List", + "Mapping", + "MappingView", + "Match", + "MutableMapping", + "MutableSequence", + "MutableSet", + "Optional", + "Pattern", + "Reversible", + "Sequence", + "Set", + "Sized", + "TextIO", + "Tuple", + "Union", + "ValuesView", + "cast", + "get_protocol_members", + "is_protocol", + "no_type_check", + "no_type_check_decorator", + "ReadOnly", + "TypeIs", ] _T = typing.TypeVar("_T") _F = typing.TypeVar("_F", bound=Callable[..., Any]) -_TC = typing.TypeVar("_TC", bound=Type[object]) +_TC = typing.TypeVar("_TC", bound=type[object]) # unfortunately we have to duplicate this class definition from typing.pyi or we break pytype class _SpecialForm: @@ -107,7 +201,7 @@ class _SpecialForm: # typing.Protocol and typing_extensions.Protocol so they can properly # warn users about potential runtime exceptions when using typing.Protocol # on older versions of Python. -Protocol: _SpecialForm = ... +Protocol: _SpecialForm def runtime_checkable(cls: _TC) -> _TC: ... @@ -128,20 +222,35 @@ class _TypedDict(Mapping[str, object], metaclass=abc.ABCMeta): __required_keys__: ClassVar[frozenset[str]] __optional_keys__: ClassVar[frozenset[str]] __total__: ClassVar[bool] + __orig_bases__: ClassVar[tuple[Any, ...]] + # PEP 705 + __readonly_keys__: ClassVar[frozenset[str]] + __mutable_keys__: ClassVar[frozenset[str]] + # PEP 728 + __closed__: ClassVar[bool] + __extra_items__: ClassVar[Any] def copy(self) -> Self: ... # Using Never so that only calls using mypy plugin hook that specialize the signature # can go through. def setdefault(self, k: Never, default: object) -> object: ... # Mypy plugin hook for 'pop' expects that 'default' has a type variable type. def pop(self, k: Never, default: _T = ...) -> object: ... # pyright: ignore[reportInvalidTypeVarUse] - def update(self: _T, __m: _T) -> None: ... + def update(self: _T, m: _T, /) -> None: ... def items(self) -> dict_items[str, object]: ... def keys(self) -> dict_keys[str, object]: ... def values(self) -> dict_values[str, object]: ... def __delitem__(self, k: Never) -> None: ... if sys.version_info >= (3, 9): - def __or__(self, __value: Self) -> Self: ... - def __ior__(self, __value: Self) -> Self: ... + @overload + def __or__(self, value: Self, /) -> Self: ... + @overload + def __or__(self, value: dict[str, Any], /) -> dict[str, object]: ... + @overload + def __ror__(self, value: Self, /) -> Self: ... + @overload + def __ror__(self, value: dict[str, Any], /) -> dict[str, object]: ... + # supposedly incompatible definitions of `__ior__` and `__or__`: + def __ior__(self, value: Self, /) -> Self: ... # type: ignore[misc] # TypedDict is a (non-subscriptable) special form. TypedDict: object @@ -155,6 +264,18 @@ def get_type_hints( include_extras: bool = False, ) -> dict[str, Any]: ... def get_args(tp: Any) -> tuple[Any, ...]: ... + +if sys.version_info >= (3, 10): + @overload + def get_origin(tp: UnionType) -> type[UnionType]: ... + +if sys.version_info >= (3, 9): + @overload + def get_origin(tp: GenericAlias) -> type: ... + +@overload +def get_origin(tp: ParamSpecArgs | ParamSpecKwargs) -> ParamSpec: ... +@overload def get_origin(tp: Any) -> Any | None: ... Annotated: _SpecialForm @@ -165,7 +286,7 @@ class SupportsIndex(Protocol, metaclass=abc.ABCMeta): @abc.abstractmethod def __index__(self) -> int: ... -# New things in 3.10 +# New and changed things in 3.10 if sys.version_info >= (3, 10): from typing import ( Concatenate as Concatenate, @@ -176,12 +297,16 @@ if sys.version_info >= (3, 10): is_typeddict as is_typeddict, ) else: + @final class ParamSpecArgs: - __origin__: ParamSpec + @property + def __origin__(self) -> ParamSpec: ... def __init__(self, origin: ParamSpec) -> None: ... + @final class ParamSpecKwargs: - __origin__: ParamSpec + @property + def __origin__(self) -> ParamSpec: ... def __init__(self, origin: ParamSpec) -> None: ... Concatenate: _SpecialForm @@ -189,13 +314,13 @@ else: TypeGuard: _SpecialForm def is_typeddict(tp: object) -> bool: ... -# New things in 3.11 -# NamedTuples are not new, but the ability to create generic NamedTuples is new in 3.11 +# New and changed things in 3.11 if sys.version_info >= (3, 11): from typing import ( LiteralString as LiteralString, NamedTuple as NamedTuple, Never as Never, + NewType as NewType, NotRequired as NotRequired, Required as Required, Self as Self, @@ -209,10 +334,10 @@ if sys.version_info >= (3, 11): ) else: Self: _SpecialForm - Never: _SpecialForm = ... - def reveal_type(__obj: _T) -> _T: ... - def assert_never(__arg: Never) -> Never: ... - def assert_type(__val: _T, __typ: Any) -> _T: ... + Never: _SpecialForm + def reveal_type(obj: _T, /) -> _T: ... + def assert_never(arg: Never, /) -> Never: ... + def assert_type(val: _T, typ: Any, /) -> _T: ... def clear_overloads() -> None: ... def get_overloads(func: Callable[..., object]) -> Sequence[Callable[..., object]]: ... @@ -226,43 +351,54 @@ else: eq_default: bool = True, order_default: bool = False, kw_only_default: bool = False, - field_specifiers: tuple[type[Any] | Callable[..., Any], ...] = ..., + frozen_default: bool = False, + field_specifiers: tuple[type[Any] | Callable[..., Any], ...] = (), **kwargs: object, ) -> IdentityFunction: ... class NamedTuple(tuple[Any, ...]): - if sys.version_info < (3, 8): - _field_types: collections.OrderedDict[str, type] - elif sys.version_info < (3, 9): - _field_types: dict[str, type] - _field_defaults: dict[str, Any] - _fields: tuple[str, ...] - _source: str + if sys.version_info < (3, 9): + _field_types: ClassVar[dict[str, type]] + _field_defaults: ClassVar[dict[str, Any]] + _fields: ClassVar[tuple[str, ...]] + __orig_bases__: ClassVar[tuple[Any, ...]] @overload def __init__(self, typename: str, fields: Iterable[tuple[str, Any]] = ...) -> None: ... @overload def __init__(self, typename: str, fields: None = None, **kwargs: Any) -> None: ... @classmethod def _make(cls, iterable: Iterable[Any]) -> Self: ... - if sys.version_info >= (3, 8): - def _asdict(self) -> dict[str, Any]: ... - else: - def _asdict(self) -> collections.OrderedDict[str, Any]: ... - + def _asdict(self) -> dict[str, Any]: ... def _replace(self, **kwargs: Any) -> Self: ... + class NewType: + def __init__(self, name: str, tp: Any) -> None: ... + def __call__(self, obj: _T, /) -> _T: ... + __supertype__: type | NewType + if sys.version_info >= (3, 10): + def __or__(self, other: Any) -> _SpecialForm: ... + def __ror__(self, other: Any) -> _SpecialForm: ... + # New things in 3.xx # The `default` parameter was added to TypeVar, ParamSpec, and TypeVarTuple (PEP 696) -# The `infer_variance` parameter was added to TypeVar (PEP 695) +# The `infer_variance` parameter was added to TypeVar in 3.12 (PEP 695) # typing_extensions.override (PEP 698) @final class TypeVar: - __name__: str - __bound__: Any | None - __constraints__: tuple[Any, ...] - __covariant__: bool - __contravariant__: bool - __default__: Any | None + @property + def __name__(self) -> str: ... + @property + def __bound__(self) -> Any | None: ... + @property + def __constraints__(self) -> tuple[Any, ...]: ... + @property + def __covariant__(self) -> bool: ... + @property + def __contravariant__(self) -> bool: ... + @property + def __infer_variance__(self) -> bool: ... + @property + def __default__(self) -> Any | None: ... def __init__( self, name: str, @@ -277,15 +413,22 @@ class TypeVar: def __or__(self, right: Any) -> _SpecialForm: ... def __ror__(self, left: Any) -> _SpecialForm: ... if sys.version_info >= (3, 11): - def __typing_subst__(self, arg: Incomplete) -> Incomplete: ... + def __typing_subst__(self, arg: Any) -> Any: ... @final class ParamSpec: - __name__: str - __bound__: type[Any] | None - __covariant__: bool - __contravariant__: bool - __default__: type[Any] | None + @property + def __name__(self) -> str: ... + @property + def __bound__(self) -> Any | None: ... + @property + def __covariant__(self) -> bool: ... + @property + def __contravariant__(self) -> bool: ... + @property + def __infer_variance__(self) -> bool: ... + @property + def __default__(self) -> Any | None: ... def __init__( self, name: str, @@ -302,9 +445,65 @@ class ParamSpec: @final class TypeVarTuple: - __name__: str - __default__: Any | None + @property + def __name__(self) -> str: ... + @property + def __default__(self) -> Any | None: ... def __init__(self, name: str, *, default: Any | None = None) -> None: ... def __iter__(self) -> Any: ... # Unpack[Self] -def override(__arg: _F) -> _F: ... +class deprecated: + message: LiteralString + category: type[Warning] | None + stacklevel: int + def __init__(self, message: LiteralString, /, *, category: type[Warning] | None = ..., stacklevel: int = 1) -> None: ... + def __call__(self, arg: _T, /) -> _T: ... + +if sys.version_info >= (3, 12): + from collections.abc import Buffer as Buffer + from types import get_original_bases as get_original_bases + from typing import TypeAliasType as TypeAliasType, override as override +else: + def override(arg: _F, /) -> _F: ... + def get_original_bases(cls: type, /) -> tuple[Any, ...]: ... + @final + class TypeAliasType: + def __init__( + self, name: str, value: Any, *, type_params: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] = () + ) -> None: ... + @property + def __value__(self) -> Any: ... + @property + def __type_params__(self) -> tuple[TypeVar | ParamSpec | TypeVarTuple, ...]: ... + @property + def __parameters__(self) -> tuple[Any, ...]: ... + @property + def __name__(self) -> str: ... + # It's writable on types, but not on instances of TypeAliasType. + @property + def __module__(self) -> str | None: ... # type: ignore[override] + def __getitem__(self, parameters: Any) -> Any: ... + if sys.version_info >= (3, 10): + def __or__(self, right: Any) -> _SpecialForm: ... + def __ror__(self, left: Any) -> _SpecialForm: ... + + @runtime_checkable + class Buffer(Protocol): + # Not actually a Protocol at runtime; see + # https://github.com/python/typeshed/issues/10224 for why we're defining it this way + def __buffer__(self, flags: int, /) -> memoryview: ... + +if sys.version_info >= (3, 13): + from typing import get_protocol_members as get_protocol_members, is_protocol as is_protocol +else: + def is_protocol(tp: type, /) -> bool: ... + def get_protocol_members(tp: type, /) -> frozenset[str]: ... + +class Doc: + documentation: str + def __init__(self, documentation: str, /) -> None: ... + def __hash__(self) -> int: ... + def __eq__(self, other: object) -> bool: ... + +ReadOnly: _SpecialForm +TypeIs: _SpecialForm diff --git a/mypy/typeshed/stdlib/unicodedata.pyi b/mypy/typeshed/stdlib/unicodedata.pyi index 5a1f7fe6638d..77d69edf06af 100644 --- a/mypy/typeshed/stdlib/unicodedata.pyi +++ b/mypy/typeshed/stdlib/unicodedata.pyi @@ -1,7 +1,7 @@ import sys from _typeshed import ReadOnlyBuffer -from typing import Any, TypeVar, overload -from typing_extensions import Literal, TypeAlias, final +from typing import Any, Literal, TypeVar, final, overload +from typing_extensions import TypeAlias ucd_3_2_0: UCD unidata_version: str @@ -11,66 +11,63 @@ if sys.version_info < (3, 10): _T = TypeVar("_T") -def bidirectional(__chr: str) -> str: ... -def category(__chr: str) -> str: ... -def combining(__chr: str) -> int: ... +_NormalizationForm: TypeAlias = Literal["NFC", "NFD", "NFKC", "NFKD"] + +def bidirectional(chr: str, /) -> str: ... +def category(chr: str, /) -> str: ... +def combining(chr: str, /) -> int: ... @overload -def decimal(__chr: str) -> int: ... +def decimal(chr: str, /) -> int: ... @overload -def decimal(__chr: str, __default: _T) -> int | _T: ... -def decomposition(__chr: str) -> str: ... +def decimal(chr: str, default: _T, /) -> int | _T: ... +def decomposition(chr: str, /) -> str: ... @overload -def digit(__chr: str) -> int: ... +def digit(chr: str, /) -> int: ... @overload -def digit(__chr: str, __default: _T) -> int | _T: ... +def digit(chr: str, default: _T, /) -> int | _T: ... _EastAsianWidth: TypeAlias = Literal["F", "H", "W", "Na", "A", "N"] -def east_asian_width(__chr: str) -> _EastAsianWidth: ... - -if sys.version_info >= (3, 8): - def is_normalized(__form: str, __unistr: str) -> bool: ... - -def lookup(__name: str | ReadOnlyBuffer) -> str: ... -def mirrored(__chr: str) -> int: ... +def east_asian_width(chr: str, /) -> _EastAsianWidth: ... +def is_normalized(form: _NormalizationForm, unistr: str, /) -> bool: ... +def lookup(name: str | ReadOnlyBuffer, /) -> str: ... +def mirrored(chr: str, /) -> int: ... @overload -def name(__chr: str) -> str: ... +def name(chr: str, /) -> str: ... @overload -def name(__chr: str, __default: _T) -> str | _T: ... -def normalize(__form: str, __unistr: str) -> str: ... +def name(chr: str, default: _T, /) -> str | _T: ... +def normalize(form: _NormalizationForm, unistr: str, /) -> str: ... @overload -def numeric(__chr: str) -> float: ... +def numeric(chr: str, /) -> float: ... @overload -def numeric(__chr: str, __default: _T) -> float | _T: ... +def numeric(chr: str, default: _T, /) -> float | _T: ... @final class UCD: # The methods below are constructed from the same array in C # (unicodedata_functions) and hence identical to the functions above. unidata_version: str - def bidirectional(self, __chr: str) -> str: ... - def category(self, __chr: str) -> str: ... - def combining(self, __chr: str) -> int: ... + def bidirectional(self, chr: str, /) -> str: ... + def category(self, chr: str, /) -> str: ... + def combining(self, chr: str, /) -> int: ... @overload - def decimal(self, __chr: str) -> int: ... + def decimal(self, chr: str, /) -> int: ... @overload - def decimal(self, __chr: str, __default: _T) -> int | _T: ... - def decomposition(self, __chr: str) -> str: ... + def decimal(self, chr: str, default: _T, /) -> int | _T: ... + def decomposition(self, chr: str, /) -> str: ... @overload - def digit(self, __chr: str) -> int: ... + def digit(self, chr: str, /) -> int: ... @overload - def digit(self, __chr: str, __default: _T) -> int | _T: ... - def east_asian_width(self, __chr: str) -> _EastAsianWidth: ... - if sys.version_info >= (3, 8): - def is_normalized(self, __form: str, __unistr: str) -> bool: ... - - def lookup(self, __name: str | ReadOnlyBuffer) -> str: ... - def mirrored(self, __chr: str) -> int: ... + def digit(self, chr: str, default: _T, /) -> int | _T: ... + def east_asian_width(self, chr: str, /) -> _EastAsianWidth: ... + def is_normalized(self, form: _NormalizationForm, unistr: str, /) -> bool: ... + def lookup(self, name: str | ReadOnlyBuffer, /) -> str: ... + def mirrored(self, chr: str, /) -> int: ... @overload - def name(self, __chr: str) -> str: ... + def name(self, chr: str, /) -> str: ... @overload - def name(self, __chr: str, __default: _T) -> str | _T: ... - def normalize(self, __form: str, __unistr: str) -> str: ... + def name(self, chr: str, default: _T, /) -> str | _T: ... + def normalize(self, form: _NormalizationForm, unistr: str, /) -> str: ... @overload - def numeric(self, __chr: str) -> float: ... + def numeric(self, chr: str, /) -> float: ... @overload - def numeric(self, __chr: str, __default: _T) -> float | _T: ... + def numeric(self, chr: str, default: _T, /) -> float | _T: ... diff --git a/mypy/typeshed/stdlib/unittest/__init__.pyi b/mypy/typeshed/stdlib/unittest/__init__.pyi index 33820c793fa5..f2532ccf7fd8 100644 --- a/mypy/typeshed/stdlib/unittest/__init__.pyi +++ b/mypy/typeshed/stdlib/unittest/__init__.pyi @@ -1,9 +1,11 @@ import sys +from unittest.async_case import * from .case import ( FunctionTestCase as FunctionTestCase, SkipTest as SkipTest, TestCase as TestCase, + addModuleCleanup as addModuleCleanup, expectedFailure as expectedFailure, skip as skip, skipIf as skipIf, @@ -27,15 +29,11 @@ from .signals import ( ) from .suite import BaseTestSuite as BaseTestSuite, TestSuite as TestSuite -if sys.version_info >= (3, 8): - from unittest.async_case import * - - from .case import addModuleCleanup as addModuleCleanup - if sys.version_info >= (3, 11): from .case import doModuleCleanups as doModuleCleanups, enterModuleContext as enterModuleContext __all__ = [ + "IsolatedAsyncioTestCase", "TestResult", "TestCase", "TestSuite", @@ -57,13 +55,13 @@ __all__ = [ "getTestCaseNames", "makeSuite", "findTestCases", + "addModuleCleanup", ] -if sys.version_info >= (3, 8): - __all__ += ["addModuleCleanup", "IsolatedAsyncioTestCase"] - if sys.version_info >= (3, 11): __all__ += ["enterModuleContext", "doModuleCleanups"] -def load_tests(loader: TestLoader, tests: TestSuite, pattern: str | None) -> TestSuite: ... +if sys.version_info < (3, 12): + def load_tests(loader: TestLoader, tests: TestSuite, pattern: str | None) -> TestSuite: ... + def __dir__() -> set[str]: ... diff --git a/mypy/typeshed/stdlib/unittest/_log.pyi b/mypy/typeshed/stdlib/unittest/_log.pyi index 4de5d502e004..011a970d8bbc 100644 --- a/mypy/typeshed/stdlib/unittest/_log.pyi +++ b/mypy/typeshed/stdlib/unittest/_log.pyi @@ -2,7 +2,7 @@ import logging import sys from types import TracebackType from typing import ClassVar, Generic, NamedTuple, TypeVar -from unittest.case import TestCase +from unittest.case import TestCase, _BaseTestCaseContext _L = TypeVar("_L", None, _LoggingWatcher) @@ -10,9 +10,8 @@ class _LoggingWatcher(NamedTuple): records: list[logging.LogRecord] output: list[str] -class _AssertLogsContext(Generic[_L]): +class _AssertLogsContext(_BaseTestCaseContext, Generic[_L]): LOGGING_FORMAT: ClassVar[str] - test_case: TestCase logger_name: str level: int msg: None diff --git a/mypy/typeshed/stdlib/unittest/async_case.pyi b/mypy/typeshed/stdlib/unittest/async_case.pyi index c1de205fbd55..12d6ef49e828 100644 --- a/mypy/typeshed/stdlib/unittest/async_case.pyi +++ b/mypy/typeshed/stdlib/unittest/async_case.pyi @@ -14,6 +14,8 @@ _P = ParamSpec("_P") class IsolatedAsyncioTestCase(TestCase): async def asyncSetUp(self) -> None: ... async def asyncTearDown(self) -> None: ... - def addAsyncCleanup(self, __func: Callable[_P, Awaitable[object]], *args: _P.args, **kwargs: _P.kwargs) -> None: ... + def addAsyncCleanup(self, func: Callable[_P, Awaitable[object]], /, *args: _P.args, **kwargs: _P.kwargs) -> None: ... if sys.version_info >= (3, 11): async def enterAsyncContext(self, cm: AbstractAsyncContextManager[_T]) -> _T: ... + if sys.version_info >= (3, 9): + def __del__(self) -> None: ... diff --git a/mypy/typeshed/stdlib/unittest/case.pyi b/mypy/typeshed/stdlib/unittest/case.pyi index 8f8cf43385a8..bd1c064f0270 100644 --- a/mypy/typeshed/stdlib/unittest/case.pyi +++ b/mypy/typeshed/stdlib/unittest/case.pyi @@ -25,8 +25,26 @@ _P = ParamSpec("_P") DIFF_OMITTED: str class _BaseTestCaseContext: + test_case: TestCase def __init__(self, test_case: TestCase) -> None: ... +class _AssertRaisesBaseContext(_BaseTestCaseContext): + expected: type[BaseException] | tuple[type[BaseException], ...] + expected_regex: Pattern[str] | None + obj_name: str | None + msg: str | None + + def __init__( + self, + expected: type[BaseException] | tuple[type[BaseException], ...], + test_case: TestCase, + expected_regex: str | Pattern[str] | None = None, + ) -> None: ... + + # This returns Self if args is the empty list, and None otherwise. + # but it's not possible to construct an overload which expresses that + def handle(self, name: str, args: list[Any], kwargs: dict[str, Any]) -> Any: ... + if sys.version_info >= (3, 9): from unittest._log import _AssertLogsContext, _LoggingWatcher else: @@ -41,19 +59,17 @@ else: class _AssertLogsContext(_BaseTestCaseContext, Generic[_L]): LOGGING_FORMAT: ClassVar[str] - test_case: TestCase logger_name: str level: int msg: None def __init__(self, test_case: TestCase, logger_name: str, level: int) -> None: ... def __enter__(self) -> _LoggingWatcher: ... def __exit__( - self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None + self, exc_type: type[BaseException] | None, exc_value: BaseException | None, tb: TracebackType | None ) -> bool | None: ... -if sys.version_info >= (3, 8): - def addModuleCleanup(__function: Callable[_P, Any], *args: _P.args, **kwargs: _P.kwargs) -> None: ... - def doModuleCleanups() -> None: ... +def addModuleCleanup(function: Callable[_P, object], /, *args: _P.args, **kwargs: _P.kwargs) -> None: ... +def doModuleCleanups() -> None: ... if sys.version_info >= (3, 11): def enterModuleContext(cm: AbstractContextManager[_T]) -> _T: ... @@ -68,10 +84,13 @@ class SkipTest(Exception): class _SupportsAbsAndDunderGE(SupportsDunderGE[Any], SupportsAbs[Any], Protocol): ... +# Keep this alias in sync with builtins._ClassInfo +# We can't import it from builtins or pytype crashes, +# due to the fact that pytype uses a custom builtins stub rather than typeshed's builtins stub if sys.version_info >= (3, 10): - _IsInstanceClassInfo: TypeAlias = type | UnionType | tuple[type | UnionType | tuple[Any, ...], ...] + _ClassInfo: TypeAlias = type | UnionType | tuple[_ClassInfo, ...] else: - _IsInstanceClassInfo: TypeAlias = type | tuple[type | tuple[Any, ...], ...] + _ClassInfo: TypeAlias = type | tuple[_ClassInfo, ...] class TestCase: failureException: type[BaseException] @@ -83,6 +102,7 @@ class TestCase: _testMethodDoc: str def __init__(self, methodName: str = "runTest") -> None: ... def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... def setUp(self) -> None: ... def tearDown(self) -> None: ... @classmethod @@ -107,8 +127,8 @@ class TestCase: def assertIsNotNone(self, obj: object, msg: Any = None) -> None: ... def assertIn(self, member: Any, container: Iterable[Any] | Container[Any], msg: Any = None) -> None: ... def assertNotIn(self, member: Any, container: Iterable[Any] | Container[Any], msg: Any = None) -> None: ... - def assertIsInstance(self, obj: object, cls: _IsInstanceClassInfo, msg: Any = None) -> None: ... - def assertNotIsInstance(self, obj: object, cls: _IsInstanceClassInfo, msg: Any = None) -> None: ... + def assertIsInstance(self, obj: object, cls: _ClassInfo, msg: Any = None) -> None: ... + def assertNotIsInstance(self, obj: object, cls: _ClassInfo, msg: Any = None) -> None: ... @overload def assertGreater(self, a: SupportsDunderGT[_T], b: _T, msg: Any = None) -> None: ... @overload @@ -122,17 +142,17 @@ class TestCase: @overload def assertLess(self, a: _T, b: SupportsDunderGT[_T], msg: Any = None) -> None: ... @overload - def assertLessEqual(self, a: SupportsDunderLT[_T], b: _T, msg: Any = None) -> None: ... + def assertLessEqual(self, a: SupportsDunderLE[_T], b: _T, msg: Any = None) -> None: ... @overload - def assertLessEqual(self, a: _T, b: SupportsDunderGT[_T], msg: Any = None) -> None: ... + def assertLessEqual(self, a: _T, b: SupportsDunderGE[_T], msg: Any = None) -> None: ... # `assertRaises`, `assertRaisesRegex`, and `assertRaisesRegexp` # are not using `ParamSpec` intentionally, # because they might be used with explicitly wrong arg types to raise some error in tests. @overload - def assertRaises( # type: ignore[misc] + def assertRaises( self, expected_exception: type[BaseException] | tuple[type[BaseException], ...], - callable: Callable[..., Any], + callable: Callable[..., object], *args: Any, **kwargs: Any, ) -> None: ... @@ -141,11 +161,11 @@ class TestCase: self, expected_exception: type[_E] | tuple[type[_E], ...], *, msg: Any = ... ) -> _AssertRaisesContext[_E]: ... @overload - def assertRaisesRegex( # type: ignore[misc] + def assertRaisesRegex( self, expected_exception: type[BaseException] | tuple[type[BaseException], ...], expected_regex: str | Pattern[str], - callable: Callable[..., Any], + callable: Callable[..., object], *args: Any, **kwargs: Any, ) -> None: ... @@ -154,10 +174,10 @@ class TestCase: self, expected_exception: type[_E] | tuple[type[_E], ...], expected_regex: str | Pattern[str], *, msg: Any = ... ) -> _AssertRaisesContext[_E]: ... @overload - def assertWarns( # type: ignore[misc] + def assertWarns( self, expected_warning: type[Warning] | tuple[type[Warning], ...], - callable: Callable[_P, Any], + callable: Callable[_P, object], *args: _P.args, **kwargs: _P.kwargs, ) -> None: ... @@ -166,11 +186,11 @@ class TestCase: self, expected_warning: type[Warning] | tuple[type[Warning], ...], *, msg: Any = ... ) -> _AssertWarnsContext: ... @overload - def assertWarnsRegex( # type: ignore[misc] + def assertWarnsRegex( self, expected_warning: type[Warning] | tuple[type[Warning], ...], expected_regex: str | Pattern[str], - callable: Callable[_P, Any], + callable: Callable[_P, object], *args: _P.args, **kwargs: _P.kwargs, ) -> None: ... @@ -245,26 +265,24 @@ class TestCase: def assertListEqual(self, list1: list[Any], list2: list[Any], msg: Any = None) -> None: ... def assertTupleEqual(self, tuple1: tuple[Any, ...], tuple2: tuple[Any, ...], msg: Any = None) -> None: ... def assertSetEqual(self, set1: AbstractSet[object], set2: AbstractSet[object], msg: Any = None) -> None: ... + # assertDictEqual accepts only true dict instances. We can't use that here, since that would make + # assertDictEqual incompatible with TypedDict. def assertDictEqual(self, d1: Mapping[Any, object], d2: Mapping[Any, object], msg: Any = None) -> None: ... def fail(self, msg: Any = None) -> NoReturn: ... def countTestCases(self) -> int: ... def defaultTestResult(self) -> unittest.result.TestResult: ... def id(self) -> str: ... def shortDescription(self) -> str | None: ... - if sys.version_info >= (3, 8): - def addCleanup(self, __function: Callable[_P, Any], *args: _P.args, **kwargs: _P.kwargs) -> None: ... - else: - def addCleanup(self, function: Callable[_P, Any], *args: _P.args, **kwargs: _P.kwargs) -> None: ... + def addCleanup(self, function: Callable[_P, object], /, *args: _P.args, **kwargs: _P.kwargs) -> None: ... if sys.version_info >= (3, 11): def enterContext(self, cm: AbstractContextManager[_T]) -> _T: ... def doCleanups(self) -> None: ... - if sys.version_info >= (3, 8): - @classmethod - def addClassCleanup(cls, __function: Callable[_P, Any], *args: _P.args, **kwargs: _P.kwargs) -> None: ... - @classmethod - def doClassCleanups(cls) -> None: ... + @classmethod + def addClassCleanup(cls, function: Callable[_P, object], /, *args: _P.args, **kwargs: _P.kwargs) -> None: ... + @classmethod + def doClassCleanups(cls) -> None: ... if sys.version_info >= (3, 11): @classmethod @@ -295,14 +313,16 @@ class TestCase: class FunctionTestCase(TestCase): def __init__( self, - testFunc: Callable[[], Any], - setUp: Callable[[], Any] | None = None, - tearDown: Callable[[], Any] | None = None, + testFunc: Callable[[], object], + setUp: Callable[[], object] | None = None, + tearDown: Callable[[], object] | None = None, description: str | None = None, ) -> None: ... def runTest(self) -> None: ... + def __hash__(self) -> int: ... + def __eq__(self, other: object) -> bool: ... -class _AssertRaisesContext(Generic[_E]): +class _AssertRaisesContext(_AssertRaisesBaseContext, Generic[_E]): exception: _E def __enter__(self) -> Self: ... def __exit__( @@ -311,7 +331,7 @@ class _AssertRaisesContext(Generic[_E]): if sys.version_info >= (3, 9): def __class_getitem__(cls, item: Any) -> GenericAlias: ... -class _AssertWarnsContext: +class _AssertWarnsContext(_AssertRaisesBaseContext): warning: WarningMessage filename: str lineno: int diff --git a/mypy/typeshed/stdlib/unittest/loader.pyi b/mypy/typeshed/stdlib/unittest/loader.pyi index f3850c939d07..202309ac1d93 100644 --- a/mypy/typeshed/stdlib/unittest/loader.pyi +++ b/mypy/typeshed/stdlib/unittest/loader.pyi @@ -1,3 +1,4 @@ +import sys import unittest.case import unittest.suite from collections.abc import Callable, Sequence @@ -18,7 +19,11 @@ class TestLoader: testNamePatterns: list[str] | None suiteClass: _SuiteClass def loadTestsFromTestCase(self, testCaseClass: type[unittest.case.TestCase]) -> unittest.suite.TestSuite: ... - def loadTestsFromModule(self, module: ModuleType, *args: Any, pattern: Any = None) -> unittest.suite.TestSuite: ... + if sys.version_info >= (3, 12): + def loadTestsFromModule(self, module: ModuleType, *, pattern: str | None = None) -> unittest.suite.TestSuite: ... + else: + def loadTestsFromModule(self, module: ModuleType, *args: Any, pattern: str | None = None) -> unittest.suite.TestSuite: ... + def loadTestsFromName(self, name: str, module: ModuleType | None = None) -> unittest.suite.TestSuite: ... def loadTestsFromNames(self, names: Sequence[str], module: ModuleType | None = None) -> unittest.suite.TestSuite: ... def getTestCaseNames(self, testCaseClass: type[unittest.case.TestCase]) -> Sequence[str]: ... diff --git a/mypy/typeshed/stdlib/unittest/main.pyi b/mypy/typeshed/stdlib/unittest/main.pyi index 6d970c920096..55bc1ec741db 100644 --- a/mypy/typeshed/stdlib/unittest/main.pyi +++ b/mypy/typeshed/stdlib/unittest/main.pyi @@ -1,3 +1,4 @@ +import sys import unittest.case import unittest.loader import unittest.result @@ -10,7 +11,7 @@ MAIN_EXAMPLES: str MODULE_EXAMPLES: str class _TestRunner(Protocol): - def run(self, test: unittest.suite.TestSuite | unittest.case.TestCase) -> unittest.result.TestResult: ... + def run(self, test: unittest.suite.TestSuite | unittest.case.TestCase, /) -> unittest.result.TestResult: ... # not really documented class TestProgram: @@ -23,22 +24,43 @@ class TestProgram: progName: str | None warnings: str | None testNamePatterns: list[str] | None - def __init__( - self, - module: None | str | ModuleType = "__main__", - defaultTest: str | Iterable[str] | None = None, - argv: list[str] | None = None, - testRunner: type[_TestRunner] | _TestRunner | None = None, - testLoader: unittest.loader.TestLoader = ..., - exit: bool = True, - verbosity: int = 1, - failfast: bool | None = None, - catchbreak: bool | None = None, - buffer: bool | None = None, - warnings: str | None = None, - *, - tb_locals: bool = False, - ) -> None: ... + if sys.version_info >= (3, 12): + durations: unittest.result._DurationsType | None + def __init__( + self, + module: None | str | ModuleType = "__main__", + defaultTest: str | Iterable[str] | None = None, + argv: list[str] | None = None, + testRunner: type[_TestRunner] | _TestRunner | None = None, + testLoader: unittest.loader.TestLoader = ..., + exit: bool = True, + verbosity: int = 1, + failfast: bool | None = None, + catchbreak: bool | None = None, + buffer: bool | None = None, + warnings: str | None = None, + *, + tb_locals: bool = False, + durations: unittest.result._DurationsType | None = None, + ) -> None: ... + else: + def __init__( + self, + module: None | str | ModuleType = "__main__", + defaultTest: str | Iterable[str] | None = None, + argv: list[str] | None = None, + testRunner: type[_TestRunner] | _TestRunner | None = None, + testLoader: unittest.loader.TestLoader = ..., + exit: bool = True, + verbosity: int = 1, + failfast: bool | None = None, + catchbreak: bool | None = None, + buffer: bool | None = None, + warnings: str | None = None, + *, + tb_locals: bool = False, + ) -> None: ... + def usageExit(self, msg: Any = None) -> None: ... def parseArgs(self, argv: list[str]) -> None: ... def createTests(self, from_discovery: bool = False, Loader: unittest.loader.TestLoader | None = None) -> None: ... diff --git a/mypy/typeshed/stdlib/unittest/mock.pyi b/mypy/typeshed/stdlib/unittest/mock.pyi index f0345c903a3b..dd61b83a658a 100644 --- a/mypy/typeshed/stdlib/unittest/mock.pyi +++ b/mypy/typeshed/stdlib/unittest/mock.pyi @@ -2,52 +2,36 @@ import sys from collections.abc import Awaitable, Callable, Coroutine, Iterable, Mapping, Sequence from contextlib import _GeneratorContextManager from types import TracebackType -from typing import Any, Generic, TypeVar, overload -from typing_extensions import Final, Literal, Self, TypeAlias +from typing import Any, Final, Generic, Literal, TypeVar, overload +from typing_extensions import ParamSpec, Self, TypeAlias _T = TypeVar("_T") _TT = TypeVar("_TT", bound=type[Any]) _R = TypeVar("_R") _F = TypeVar("_F", bound=Callable[..., Any]) _AF = TypeVar("_AF", bound=Callable[..., Coroutine[Any, Any, Any]]) - -if sys.version_info >= (3, 8): - __all__ = ( - "Mock", - "MagicMock", - "patch", - "sentinel", - "DEFAULT", - "ANY", - "call", - "create_autospec", - "AsyncMock", - "FILTER_DIR", - "NonCallableMock", - "NonCallableMagicMock", - "mock_open", - "PropertyMock", - "seal", - ) -else: - __all__ = ( - "Mock", - "MagicMock", - "patch", - "sentinel", - "DEFAULT", - "ANY", - "call", - "create_autospec", - "FILTER_DIR", - "NonCallableMock", - "NonCallableMagicMock", - "mock_open", - "PropertyMock", - "seal", - ) - -__version__: Final[str] +_P = ParamSpec("_P") + +__all__ = ( + "Mock", + "MagicMock", + "patch", + "sentinel", + "DEFAULT", + "ANY", + "call", + "create_autospec", + "AsyncMock", + "FILTER_DIR", + "NonCallableMock", + "NonCallableMagicMock", + "mock_open", + "PropertyMock", + "seal", +) + +if sys.version_info < (3, 9): + __version__: Final[str] FILTER_DIR: Any @@ -67,30 +51,28 @@ _CallValue: TypeAlias = str | tuple[Any, ...] | Mapping[str, Any] | _ArgsKwargs class _Call(tuple[Any, ...]): def __new__( - cls, value: _CallValue = ..., name: str | None = "", parent: Any | None = None, two: bool = False, from_kall: bool = True + cls, value: _CallValue = (), name: str | None = "", parent: Any | None = None, two: bool = False, from_kall: bool = True ) -> Self: ... name: Any parent: Any from_kall: Any def __init__( self, - value: _CallValue = ..., + value: _CallValue = (), name: str | None = None, parent: Any | None = None, two: bool = False, from_kall: bool = True, ) -> None: ... def __eq__(self, other: object) -> bool: ... - def __ne__(self, __other: object) -> bool: ... + def __ne__(self, value: object, /) -> bool: ... def __call__(self, *args: Any, **kwargs: Any) -> _Call: ... def __getattr__(self, attr: str) -> Any: ... def __getattribute__(self, attr: str) -> Any: ... - if sys.version_info >= (3, 8): - @property - def args(self) -> tuple[Any, ...]: ... - @property - def kwargs(self) -> Mapping[str, Any]: ... - + @property + def args(self) -> tuple[Any, ...]: ... + @property + def kwargs(self) -> Mapping[str, Any]: ... def call_list(self) -> Any: ... call: _Call @@ -104,7 +86,25 @@ class Base: # We subclass with "Any" because mocks are explicitly designed to stand in for other types, # something that can't be expressed with our static type system. class NonCallableMock(Base, Any): - def __new__(__cls, *args: Any, **kw: Any) -> Self: ... + if sys.version_info >= (3, 12): + def __new__( + cls, + spec: list[str] | object | type[object] | None = None, + wraps: Any | None = None, + name: str | None = None, + spec_set: list[str] | object | type[object] | None = None, + parent: NonCallableMock | None = None, + _spec_state: Any | None = None, + _new_name: str = "", + _new_parent: NonCallableMock | None = None, + _spec_as_instance: bool = False, + _eat_self: bool | None = None, + unsafe: bool = False, + **kwargs: Any, + ) -> Self: ... + else: + def __new__(cls, /, *args: Any, **kw: Any) -> Self: ... + def __init__( self, spec: list[str] | object | type[object] | None = None, @@ -124,24 +124,13 @@ class NonCallableMock(Base, Any): def __delattr__(self, name: str) -> None: ... def __setattr__(self, name: str, value: Any) -> None: ... def __dir__(self) -> list[str]: ... - if sys.version_info >= (3, 8): - def _calls_repr(self, prefix: str = "Calls") -> str: ... - def assert_called_with(self, *args: Any, **kwargs: Any) -> None: ... - def assert_not_called(self) -> None: ... - def assert_called_once_with(self, *args: Any, **kwargs: Any) -> None: ... - def _format_mock_failure_message(self, args: Any, kwargs: Any, action: str = "call") -> str: ... - else: - def assert_called_with(_mock_self, *args: Any, **kwargs: Any) -> None: ... - def assert_not_called(_mock_self) -> None: ... - def assert_called_once_with(_mock_self, *args: Any, **kwargs: Any) -> None: ... - def _format_mock_failure_message(self, args: Any, kwargs: Any) -> str: ... - if sys.version_info >= (3, 8): - def assert_called(self) -> None: ... - def assert_called_once(self) -> None: ... - else: - def assert_called(_mock_self) -> None: ... - def assert_called_once(_mock_self) -> None: ... - + def _calls_repr(self, prefix: str = "Calls") -> str: ... + def assert_called_with(self, *args: Any, **kwargs: Any) -> None: ... + def assert_not_called(self) -> None: ... + def assert_called_once_with(self, *args: Any, **kwargs: Any) -> None: ... + def _format_mock_failure_message(self, args: Any, kwargs: Any, action: str = "call") -> str: ... + def assert_called(self) -> None: ... + def assert_called_once(self) -> None: ... def reset_mock(self, visited: Any = None, *, return_value: bool = False, side_effect: bool = False) -> None: ... def _extract_mock_name(self) -> str: ... def _get_call_signature_from_name(self, name: str) -> Any: ... @@ -178,10 +167,7 @@ class CallableMixin(Base): _new_parent: Any | None = None, **kwargs: Any, ) -> None: ... - if sys.version_info >= (3, 8): - def __call__(self, *args: Any, **kwargs: Any) -> Any: ... - else: - def __call__(_mock_self, *args: Any, **kwargs: Any) -> Any: ... + def __call__(self, *args: Any, **kwargs: Any) -> Any: ... class Mock(CallableMixin, NonCallableMock): ... @@ -202,7 +188,7 @@ class _patch(Generic[_T]): # but that's impossible with the current type system. if sys.version_info >= (3, 10): def __init__( - self: _patch[_T], + self: _patch[_T], # pyright: ignore[reportInvalidTypeVarUse] #11780 getter: Callable[[], Any], attribute: str, new: _T, @@ -217,7 +203,7 @@ class _patch(Generic[_T]): ) -> None: ... else: def __init__( - self: _patch[_T], + self: _patch[_T], # pyright: ignore[reportInvalidTypeVarUse] #11780 getter: Callable[[], Any], attribute: str, new: _T, @@ -232,34 +218,43 @@ class _patch(Generic[_T]): def copy(self) -> _patch[_T]: ... @overload def __call__(self, func: _TT) -> _TT: ... + # If new==DEFAULT, this should add a MagicMock parameter to the function + # arguments. See the _patch_default_new class below for this functionality. @overload - def __call__(self, func: Callable[..., _R]) -> Callable[..., _R]: ... - if sys.version_info >= (3, 8): - def decoration_helper( - self, patched: _patch[Any], args: Sequence[Any], keywargs: Any - ) -> _GeneratorContextManager[tuple[Sequence[Any], Any]]: ... - + def __call__(self, func: Callable[_P, _R]) -> Callable[_P, _R]: ... + def decoration_helper( + self, patched: _patch[Any], args: Sequence[Any], keywargs: Any + ) -> _GeneratorContextManager[tuple[Sequence[Any], Any]]: ... def decorate_class(self, klass: _TT) -> _TT: ... def decorate_callable(self, func: Callable[..., _R]) -> Callable[..., _R]: ... - if sys.version_info >= (3, 8): - def decorate_async_callable(self, func: Callable[..., Awaitable[_R]]) -> Callable[..., Awaitable[_R]]: ... - + def decorate_async_callable(self, func: Callable[..., Awaitable[_R]]) -> Callable[..., Awaitable[_R]]: ... def get_original(self) -> tuple[Any, bool]: ... target: Any temp_original: Any is_local: bool def __enter__(self) -> _T: ... def __exit__( - self, __exc_type: type[BaseException] | None, __exc_value: BaseException | None, __traceback: TracebackType | None + self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, / ) -> None: ... def start(self) -> _T: ... def stop(self) -> None: ... +# This class does not exist at runtime, it's a hack to make this work: +# @patch("foo") +# def bar(..., mock: MagicMock) -> None: ... +class _patch_default_new(_patch[MagicMock | AsyncMock]): + @overload + def __call__(self, func: _TT) -> _TT: ... + # Can't use the following as ParamSpec is only allowed as last parameter: + # def __call__(self, func: Callable[_P, _R]) -> Callable[Concatenate[_P, MagicMock], _R]: ... + @overload + def __call__(self, func: Callable[..., _R]) -> Callable[..., _R]: ... + class _patch_dict: in_dict: Any values: Any clear: Any - def __init__(self, in_dict: Any, values: Any = ..., clear: Any = False, **kwargs: Any) -> None: ... + def __init__(self, in_dict: Any, values: Any = (), clear: Any = False, **kwargs: Any) -> None: ... def __call__(self, f: Any) -> Any: ... if sys.version_info >= (3, 10): def decorate_callable(self, f: _F) -> _F: ... @@ -271,11 +266,8 @@ class _patch_dict: start: Any stop: Any -if sys.version_info >= (3, 8): - _Mock: TypeAlias = MagicMock | AsyncMock -else: - _Mock: TypeAlias = MagicMock - +# This class does not exist at runtime, it's a hack to add methods to the +# patch() function. class _patcher: TEST_PREFIX: str dict: type[_patch_dict] @@ -283,7 +275,7 @@ class _patcher: # Ideally we'd be able to add an overload for it so that the return type is _patch[MagicMock], # but that's impossible with the current type system. @overload - def __call__( # type: ignore[misc] + def __call__( # type: ignore[overload-overlap] self, target: str, new: _T, @@ -305,10 +297,10 @@ class _patcher: autospec: Any | None = ..., new_callable: Any | None = ..., **kwargs: Any, - ) -> _patch[_Mock]: ... + ) -> _patch_default_new: ... @overload @staticmethod - def object( # type: ignore[misc] + def object( target: Any, attribute: str, new: _T, @@ -331,7 +323,7 @@ class _patcher: autospec: Any | None = ..., new_callable: Any | None = ..., **kwargs: Any, - ) -> _patch[_Mock]: ... + ) -> _patch[MagicMock | AsyncMock]: ... @staticmethod def multiple( target: Any, @@ -353,34 +345,34 @@ class MagicMixin: class NonCallableMagicMock(MagicMixin, NonCallableMock): ... class MagicMock(MagicMixin, Mock): ... -if sys.version_info >= (3, 8): - class AsyncMockMixin(Base): - def __init__(self, *args: Any, **kwargs: Any) -> None: ... - async def _execute_mock_call(self, *args: Any, **kwargs: Any) -> Any: ... - def assert_awaited(self) -> None: ... - def assert_awaited_once(self) -> None: ... - def assert_awaited_with(self, *args: Any, **kwargs: Any) -> None: ... - def assert_awaited_once_with(self, *args: Any, **kwargs: Any) -> None: ... - def assert_any_await(self, *args: Any, **kwargs: Any) -> None: ... - def assert_has_awaits(self, calls: Iterable[_Call], any_order: bool = False) -> None: ... - def assert_not_awaited(self) -> None: ... - def reset_mock(self, *args: Any, **kwargs: Any) -> None: ... - await_count: int - await_args: _Call | None - await_args_list: _CallList - - class AsyncMagicMixin(MagicMixin): - def __init__(self, *args: Any, **kw: Any) -> None: ... - - class AsyncMock(AsyncMockMixin, AsyncMagicMixin, Mock): ... +class AsyncMockMixin(Base): + def __init__(self, *args: Any, **kwargs: Any) -> None: ... + async def _execute_mock_call(self, *args: Any, **kwargs: Any) -> Any: ... + def assert_awaited(self) -> None: ... + def assert_awaited_once(self) -> None: ... + def assert_awaited_with(self, *args: Any, **kwargs: Any) -> None: ... + def assert_awaited_once_with(self, *args: Any, **kwargs: Any) -> None: ... + def assert_any_await(self, *args: Any, **kwargs: Any) -> None: ... + def assert_has_awaits(self, calls: Iterable[_Call], any_order: bool = False) -> None: ... + def assert_not_awaited(self) -> None: ... + def reset_mock(self, *args: Any, **kwargs: Any) -> None: ... + await_count: int + await_args: _Call | None + await_args_list: _CallList + +class AsyncMagicMixin(MagicMixin): + def __init__(self, *args: Any, **kw: Any) -> None: ... + +class AsyncMock(AsyncMockMixin, AsyncMagicMixin, Mock): + # Improving the `reset_mock` signature. + # It is defined on `AsyncMockMixin` with `*args, **kwargs`, which is not ideal. + # But, `NonCallableMock` super-class has the better version. + def reset_mock(self, visited: Any = None, *, return_value: bool = False, side_effect: bool = False) -> None: ... class MagicProxy: name: str parent: Any def __init__(self, name: str, parent: Any) -> None: ... - if sys.version_info < (3, 8): - def __call__(self, *args: Any, **kwargs: Any) -> Any: ... - def create_mock(self) -> Any: ... def __get__(self, obj: Any, _type: Any | None = None) -> Any: ... @@ -432,11 +424,7 @@ class _SpecState: def mock_open(mock: Any | None = None, read_data: Any = "") -> Any: ... class PropertyMock(Mock): - if sys.version_info >= (3, 8): - def __get__(self, obj: _T, obj_type: type[_T] | None = None) -> Self: ... - else: - def __get__(self, obj: _T, obj_type: type[_T] | None) -> Self: ... - - def __set__(self, obj: Any, value: Any) -> None: ... + def __get__(self, obj: _T, obj_type: type[_T] | None = None) -> Self: ... + def __set__(self, obj: Any, val: Any) -> None: ... def seal(mock: Any) -> None: ... diff --git a/mypy/typeshed/stdlib/unittest/result.pyi b/mypy/typeshed/stdlib/unittest/result.pyi index 8d78bc0f7dcf..436fabf20c65 100644 --- a/mypy/typeshed/stdlib/unittest/result.pyi +++ b/mypy/typeshed/stdlib/unittest/result.pyi @@ -1,9 +1,12 @@ +import sys import unittest.case from _typeshed import OptExcInfo from collections.abc import Callable from typing import Any, TextIO, TypeVar +from typing_extensions import TypeAlias _F = TypeVar("_F", bound=Callable[..., Any]) +_DurationsType: TypeAlias = list[tuple[str, float]] STDOUT_LINE: str STDERR_LINE: str @@ -22,6 +25,9 @@ class TestResult: buffer: bool failfast: bool tb_locals: bool + if sys.version_info >= (3, 12): + collectedDurations: _DurationsType + def __init__(self, stream: TextIO | None = None, descriptions: bool | None = None, verbosity: int | None = None) -> None: ... def printErrors(self) -> None: ... def wasSuccessful(self) -> bool: ... @@ -37,3 +43,5 @@ class TestResult: def addExpectedFailure(self, test: unittest.case.TestCase, err: OptExcInfo) -> None: ... def addUnexpectedSuccess(self, test: unittest.case.TestCase) -> None: ... def addSubTest(self, test: unittest.case.TestCase, subtest: unittest.case.TestCase, err: OptExcInfo | None) -> None: ... + if sys.version_info >= (3, 12): + def addDuration(self, test: unittest.case.TestCase, elapsed: float) -> None: ... diff --git a/mypy/typeshed/stdlib/unittest/runner.pyi b/mypy/typeshed/stdlib/unittest/runner.pyi index c0ddcdb49208..0033083ac406 100644 --- a/mypy/typeshed/stdlib/unittest/runner.pyi +++ b/mypy/typeshed/stdlib/unittest/runner.pyi @@ -1,6 +1,8 @@ +import sys import unittest.case import unittest.result import unittest.suite +from _typeshed import Incomplete from collections.abc import Callable, Iterable from typing import TextIO from typing_extensions import TypeAlias @@ -14,23 +16,57 @@ class TextTestResult(unittest.result.TestResult): separator2: str showAll: bool # undocumented stream: TextIO # undocumented - def __init__(self, stream: TextIO, descriptions: bool, verbosity: int) -> None: ... + if sys.version_info >= (3, 12): + durations: unittest.result._DurationsType | None + def __init__( + self, stream: TextIO, descriptions: bool, verbosity: int, *, durations: unittest.result._DurationsType | None = None + ) -> None: ... + else: + def __init__(self, stream: TextIO, descriptions: bool, verbosity: int) -> None: ... + def getDescription(self, test: unittest.case.TestCase) -> str: ... def printErrorList(self, flavour: str, errors: Iterable[tuple[unittest.case.TestCase, str]]) -> None: ... class TextTestRunner: resultclass: _ResultClassType - def __init__( - self, - stream: TextIO | None = None, - descriptions: bool = True, - verbosity: int = 1, - failfast: bool = False, - buffer: bool = False, - resultclass: _ResultClassType | None = None, - warnings: type[Warning] | None = None, - *, - tb_locals: bool = False, - ) -> None: ... + # TODO: add `_WritelnDecorator` type + # stream: _WritelnDecorator + stream: Incomplete + descriptions: bool + verbosity: int + failfast: bool + buffer: bool + warnings: str | None + tb_locals: bool + + if sys.version_info >= (3, 12): + durations: unittest.result._DurationsType | None + def __init__( + self, + stream: TextIO | None = None, + descriptions: bool = True, + verbosity: int = 1, + failfast: bool = False, + buffer: bool = False, + resultclass: _ResultClassType | None = None, + warnings: str | None = None, + *, + tb_locals: bool = False, + durations: unittest.result._DurationsType | None = None, + ) -> None: ... + else: + def __init__( + self, + stream: TextIO | None = None, + descriptions: bool = True, + verbosity: int = 1, + failfast: bool = False, + buffer: bool = False, + resultclass: _ResultClassType | None = None, + warnings: str | None = None, + *, + tb_locals: bool = False, + ) -> None: ... + def _makeResult(self) -> unittest.result.TestResult: ... def run(self, test: unittest.suite.TestSuite | unittest.case.TestCase) -> unittest.result.TestResult: ... diff --git a/mypy/typeshed/stdlib/unittest/suite.pyi b/mypy/typeshed/stdlib/unittest/suite.pyi index f6b8ef003518..c10cbc75d7fd 100644 --- a/mypy/typeshed/stdlib/unittest/suite.pyi +++ b/mypy/typeshed/stdlib/unittest/suite.pyi @@ -8,7 +8,7 @@ _TestType: TypeAlias = unittest.case.TestCase | TestSuite class BaseTestSuite(Iterable[_TestType]): _tests: list[unittest.case.TestCase] _removed_tests: int - def __init__(self, tests: Iterable[_TestType] = ...) -> None: ... + def __init__(self, tests: Iterable[_TestType] = ()) -> None: ... def __call__(self, result: unittest.result.TestResult) -> unittest.result.TestResult: ... def addTest(self, test: _TestType) -> None: ... def addTests(self, tests: Iterable[_TestType]) -> None: ... diff --git a/mypy/typeshed/stdlib/unittest/util.pyi b/mypy/typeshed/stdlib/unittest/util.pyi index 845accfebedd..c42d1346e4b7 100644 --- a/mypy/typeshed/stdlib/unittest/util.pyi +++ b/mypy/typeshed/stdlib/unittest/util.pyi @@ -1,4 +1,4 @@ -from collections.abc import Sequence +from collections.abc import MutableSequence, Sequence from typing import Any, TypeVar from typing_extensions import TypeAlias @@ -17,7 +17,7 @@ def _common_shorten_repr(*args: str) -> tuple[str, ...]: ... def safe_repr(obj: object, short: bool = False) -> str: ... def strclass(cls: type) -> str: ... def sorted_list_difference(expected: Sequence[_T], actual: Sequence[_T]) -> tuple[list[_T], list[_T]]: ... -def unorderable_list_difference(expected: Sequence[_T], actual: Sequence[_T]) -> tuple[list[_T], list[_T]]: ... +def unorderable_list_difference(expected: MutableSequence[_T], actual: MutableSequence[_T]) -> tuple[list[_T], list[_T]]: ... def three_way_cmp(x: Any, y: Any) -> int: ... def _count_diff_all_purpose(actual: Sequence[_T], expected: Sequence[_T]) -> list[_Mismatch[_T]]: ... def _count_diff_hashable(actual: Sequence[_T], expected: Sequence[_T]) -> list[_Mismatch[_T]]: ... diff --git a/mypy/typeshed/stdlib/urllib/parse.pyi b/mypy/typeshed/stdlib/urllib/parse.pyi index cd1d9347d6f7..ed1929b26501 100644 --- a/mypy/typeshed/stdlib/urllib/parse.pyi +++ b/mypy/typeshed/stdlib/urllib/parse.pyi @@ -1,7 +1,7 @@ import sys from collections.abc import Callable, Iterable, Mapping, Sequence -from typing import Any, AnyStr, Generic, NamedTuple, TypeVar, overload -from typing_extensions import Literal, TypeAlias +from typing import Any, AnyStr, Generic, Literal, NamedTuple, TypeVar, overload +from typing_extensions import TypeAlias if sys.version_info >= (3, 9): from types import GenericAlias @@ -40,13 +40,10 @@ scheme_chars: str if sys.version_info < (3, 11): MAX_CACHE_SIZE: int -class _ResultMixinBase(Generic[AnyStr]): - def geturl(self) -> AnyStr: ... - -class _ResultMixinStr(_ResultMixinBase[str]): +class _ResultMixinStr: def encode(self, encoding: str = "ascii", errors: str = "strict") -> _ResultMixinBytes: ... -class _ResultMixinBytes(_ResultMixinBase[bytes]): +class _ResultMixinBytes: def decode(self, encoding: str = "ascii", errors: str = "strict") -> _ResultMixinStr: ... class _NetlocResultMixinBase(Generic[AnyStr]): @@ -64,55 +61,44 @@ class _NetlocResultMixinBase(Generic[AnyStr]): class _NetlocResultMixinStr(_NetlocResultMixinBase[str], _ResultMixinStr): ... class _NetlocResultMixinBytes(_NetlocResultMixinBase[bytes], _ResultMixinBytes): ... -# Ideally this would be a generic fixed-length tuple, -# but mypy doesn't support that yet: https://github.com/python/mypy/issues/685#issuecomment-992014179 -class _DefragResultBase(tuple[AnyStr, ...], Generic[AnyStr]): - if sys.version_info >= (3, 10): - __match_args__ = ("url", "fragment") - @property - def url(self) -> AnyStr: ... - @property - def fragment(self) -> AnyStr: ... - -class _SplitResultBase(NamedTuple): - scheme: str - netloc: str - path: str - query: str - fragment: str - -class _SplitResultBytesBase(NamedTuple): - scheme: bytes - netloc: bytes - path: bytes - query: bytes - fragment: bytes - -class _ParseResultBase(NamedTuple): - scheme: str - netloc: str - path: str - params: str - query: str - fragment: str - -class _ParseResultBytesBase(NamedTuple): - scheme: bytes - netloc: bytes - path: bytes - params: bytes - query: bytes - fragment: bytes +class _DefragResultBase(NamedTuple, Generic[AnyStr]): + url: AnyStr + fragment: AnyStr + +class _SplitResultBase(NamedTuple, Generic[AnyStr]): + scheme: AnyStr + netloc: AnyStr + path: AnyStr + query: AnyStr + fragment: AnyStr + +class _ParseResultBase(NamedTuple, Generic[AnyStr]): + scheme: AnyStr + netloc: AnyStr + path: AnyStr + params: AnyStr + query: AnyStr + fragment: AnyStr # Structured result objects for string data -class DefragResult(_DefragResultBase[str], _ResultMixinStr): ... -class SplitResult(_SplitResultBase, _NetlocResultMixinStr): ... -class ParseResult(_ParseResultBase, _NetlocResultMixinStr): ... +class DefragResult(_DefragResultBase[str], _ResultMixinStr): + def geturl(self) -> str: ... + +class SplitResult(_SplitResultBase[str], _NetlocResultMixinStr): + def geturl(self) -> str: ... + +class ParseResult(_ParseResultBase[str], _NetlocResultMixinStr): + def geturl(self) -> str: ... # Structured result objects for bytes data -class DefragResultBytes(_DefragResultBase[bytes], _ResultMixinBytes): ... -class SplitResultBytes(_SplitResultBytesBase, _NetlocResultMixinBytes): ... -class ParseResultBytes(_ParseResultBytesBase, _NetlocResultMixinBytes): ... +class DefragResultBytes(_DefragResultBase[bytes], _ResultMixinBytes): + def geturl(self) -> bytes: ... + +class SplitResultBytes(_SplitResultBase[bytes], _NetlocResultMixinBytes): + def geturl(self) -> bytes: ... + +class ParseResultBytes(_ParseResultBase[bytes], _NetlocResultMixinBytes): + def geturl(self) -> bytes: ... def parse_qs( qs: AnyStr | None, @@ -192,36 +178,33 @@ def urljoin(base: AnyStr, url: AnyStr | None, allow_fragments: bool = True) -> A @overload def urlparse(url: str, scheme: str = "", allow_fragments: bool = True) -> ParseResult: ... @overload -def urlparse(url: bytes | bytearray, scheme: bytes | bytearray | None, allow_fragments: bool = True) -> ParseResultBytes: ... -@overload def urlparse( - url: None, scheme: bytes | bytearray | None | Literal[""] = "", allow_fragments: bool = True + url: bytes | bytearray | None, scheme: bytes | bytearray | None | Literal[""] = "", allow_fragments: bool = True ) -> ParseResultBytes: ... @overload def urlsplit(url: str, scheme: str = "", allow_fragments: bool = True) -> SplitResult: ... if sys.version_info >= (3, 11): @overload - def urlsplit(url: bytes, scheme: bytes | None, allow_fragments: bool = True) -> SplitResultBytes: ... - @overload - def urlsplit(url: None, scheme: bytes | None | Literal[""] = "", allow_fragments: bool = True) -> SplitResultBytes: ... + def urlsplit( + url: bytes | None, scheme: bytes | None | Literal[""] = "", allow_fragments: bool = True + ) -> SplitResultBytes: ... else: - @overload - def urlsplit(url: bytes | bytearray, scheme: bytes | bytearray | None, allow_fragments: bool = True) -> SplitResultBytes: ... @overload def urlsplit( - url: None, scheme: bytes | bytearray | None | Literal[""] = "", allow_fragments: bool = True + url: bytes | bytearray | None, scheme: bytes | bytearray | None | Literal[""] = "", allow_fragments: bool = True ) -> SplitResultBytes: ... +# Requires an iterable of length 6 @overload -def urlunparse( - components: tuple[AnyStr | None, AnyStr | None, AnyStr | None, AnyStr | None, AnyStr | None, AnyStr | None] -) -> AnyStr: ... +def urlunparse(components: Iterable[None]) -> Literal[b""]: ... @overload -def urlunparse(components: Sequence[AnyStr | None]) -> AnyStr: ... +def urlunparse(components: Iterable[AnyStr | None]) -> AnyStr: ... + +# Requires an iterable of length 5 @overload -def urlunsplit(components: tuple[AnyStr | None, AnyStr | None, AnyStr | None, AnyStr | None, AnyStr | None]) -> AnyStr: ... +def urlunsplit(components: Iterable[None]) -> Literal[b""]: ... @overload -def urlunsplit(components: Sequence[AnyStr | None]) -> AnyStr: ... +def urlunsplit(components: Iterable[AnyStr | None]) -> AnyStr: ... def unwrap(url: str) -> str: ... diff --git a/mypy/typeshed/stdlib/urllib/request.pyi b/mypy/typeshed/stdlib/urllib/request.pyi index 09ce27961999..3442be8b8ea4 100644 --- a/mypy/typeshed/stdlib/urllib/request.pyi +++ b/mypy/typeshed/stdlib/urllib/request.pyi @@ -102,7 +102,7 @@ class Request: self, url: str, data: _DataType = None, - headers: MutableMapping[str, str] = ..., + headers: MutableMapping[str, str] = {}, origin_req_host: str | None = None, unverifiable: bool = False, method: str | None = None, @@ -173,7 +173,7 @@ class HTTPPasswordMgr: def add_password(self, realm: str, uri: str | Sequence[str], user: str, passwd: str) -> None: ... def find_user_password(self, realm: str, authuri: str) -> tuple[str | None, str | None]: ... def is_suburi(self, base: str, test: str) -> bool: ... # undocumented - def reduce_uri(self, uri: str, default_port: bool = True) -> str: ... # undocumented + def reduce_uri(self, uri: str, default_port: bool = True) -> tuple[str, str]: ... # undocumented class HTTPPasswordMgrWithDefaultRealm(HTTPPasswordMgr): def add_password(self, realm: str | None, uri: str | Sequence[str], user: str, passwd: str) -> None: ... @@ -184,7 +184,7 @@ class HTTPPasswordMgrWithPriorAuth(HTTPPasswordMgrWithDefaultRealm): self, realm: str | None, uri: str | Sequence[str], user: str, passwd: str, is_authenticated: bool = False ) -> None: ... def update_authenticated(self, uri: str | Sequence[str], is_authenticated: bool = False) -> None: ... - def is_authenticated(self, authuri: str) -> bool: ... + def is_authenticated(self, authuri: str) -> bool | None: ... class AbstractBasicAuthHandler: rx: ClassVar[Pattern[str]] # undocumented @@ -212,7 +212,7 @@ class AbstractDigestAuthHandler: def http_error_auth_reqed(self, auth_header: str, host: str, req: Request, headers: HTTPMessage) -> None: ... def retry_http_digest_auth(self, req: Request, auth: str) -> _UrlopenRet | None: ... def get_cnonce(self, nonce: str) -> str: ... - def get_authorization(self, req: Request, chal: Mapping[str, str]) -> str: ... + def get_authorization(self, req: Request, chal: Mapping[str, str]) -> str | None: ... def get_algorithm_impls(self, algorithm: str) -> tuple[Callable[[str], str], Callable[[str, str], str]]: ... def get_entity_digest(self, data: ReadableBuffer | None, chal: Mapping[str, str]) -> str | None: ... @@ -228,6 +228,8 @@ class _HTTPConnectionProtocol(Protocol): def __call__( self, host: str, + /, + *, port: int | None = ..., timeout: float = ..., source_address: tuple[str, int] | None = ..., @@ -235,7 +237,11 @@ class _HTTPConnectionProtocol(Protocol): ) -> HTTPConnection: ... class AbstractHTTPHandler(BaseHandler): # undocumented - def __init__(self, debuglevel: int = 0) -> None: ... + if sys.version_info >= (3, 12): + def __init__(self, debuglevel: int | None = None) -> None: ... + else: + def __init__(self, debuglevel: int = 0) -> None: ... + def set_http_debuglevel(self, level: int) -> None: ... def do_request_(self, request: Request) -> Request: ... def do_open(self, http_class: _HTTPConnectionProtocol, req: Request, **http_conn_args: Any) -> HTTPResponse: ... @@ -245,9 +251,15 @@ class HTTPHandler(AbstractHTTPHandler): def http_request(self, request: Request) -> Request: ... # undocumented class HTTPSHandler(AbstractHTTPHandler): - def __init__( - self, debuglevel: int = 0, context: ssl.SSLContext | None = None, check_hostname: bool | None = None - ) -> None: ... + if sys.version_info >= (3, 12): + def __init__( + self, debuglevel: int | None = None, context: ssl.SSLContext | None = None, check_hostname: bool | None = None + ) -> None: ... + else: + def __init__( + self, debuglevel: int = 0, context: ssl.SSLContext | None = None, check_hostname: bool | None = None + ) -> None: ... + def https_open(self, req: Request) -> HTTPResponse: ... def https_request(self, request: Request) -> Request: ... # undocumented @@ -269,7 +281,7 @@ class ftpwrapper: # undocumented def file_close(self) -> None: ... def init(self) -> None: ... def real_close(self) -> None: ... - def retrfile(self, file: str, type: str) -> tuple[addclosehook, int]: ... + def retrfile(self, file: str, type: str) -> tuple[addclosehook, int | None]: ... class FTPHandler(BaseHandler): def ftp_open(self, req: Request) -> addinfourl: ... @@ -326,6 +338,7 @@ class URLopener: def open_https(self, url: str, data: ReadableBuffer | None = None) -> _UrlopenRet: ... # undocumented def open_local_file(self, url: str) -> addinfourl: ... # undocumented def open_unknown_proxy(self, proxy: str, fullurl: str, data: ReadableBuffer | None = None) -> None: ... # undocumented + def __del__(self) -> None: ... class FancyURLopener(URLopener): def prompt_user_passwd(self, host: str, realm: str) -> tuple[str, str]: ... diff --git a/mypy/typeshed/stdlib/urllib/response.pyi b/mypy/typeshed/stdlib/urllib/response.pyi index 61ba687076b2..bbec4cacc750 100644 --- a/mypy/typeshed/stdlib/urllib/response.pyi +++ b/mypy/typeshed/stdlib/urllib/response.pyi @@ -1,39 +1,23 @@ import sys +import tempfile from _typeshed import ReadableBuffer from collections.abc import Callable, Iterable from email.message import Message from types import TracebackType -from typing import IO, Any, BinaryIO -from typing_extensions import Self +from typing import IO, Any __all__ = ["addbase", "addclosehook", "addinfo", "addinfourl"] -class addbase(BinaryIO): +class addbase(tempfile._TemporaryFileWrapper[bytes]): fp: IO[bytes] def __init__(self, fp: IO[bytes]) -> None: ... - def __enter__(self) -> Self: ... def __exit__( self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None ) -> None: ... - def __iter__(self) -> Self: ... - def __next__(self) -> bytes: ... - def close(self) -> None: ... # These methods don't actually exist, but the class inherits at runtime from # tempfile._TemporaryFileWrapper, which uses __getattr__ to delegate to the # underlying file object. To satisfy the BinaryIO interface, we pretend that this # class has these additional methods. - def fileno(self) -> int: ... - def flush(self) -> None: ... - def isatty(self) -> bool: ... - def read(self, n: int = ...) -> bytes: ... - def readable(self) -> bool: ... - def readline(self, limit: int = ...) -> bytes: ... - def readlines(self, hint: int = ...) -> list[bytes]: ... - def seek(self, offset: int, whence: int = ...) -> int: ... - def seekable(self) -> bool: ... - def tell(self) -> int: ... - def truncate(self, size: int | None = ...) -> int: ... - def writable(self) -> bool: ... def write(self, s: ReadableBuffer) -> int: ... def writelines(self, lines: Iterable[ReadableBuffer]) -> None: ... diff --git a/mypy/typeshed/stdlib/urllib/robotparser.pyi b/mypy/typeshed/stdlib/urllib/robotparser.pyi index d218c3dc6c0f..14ceef550dab 100644 --- a/mypy/typeshed/stdlib/urllib/robotparser.pyi +++ b/mypy/typeshed/stdlib/urllib/robotparser.pyi @@ -1,4 +1,3 @@ -import sys from collections.abc import Iterable from typing import NamedTuple @@ -18,5 +17,4 @@ class RobotFileParser: def modified(self) -> None: ... def crawl_delay(self, useragent: str) -> str | None: ... def request_rate(self, useragent: str) -> RequestRate | None: ... - if sys.version_info >= (3, 8): - def site_maps(self) -> list[str] | None: ... + def site_maps(self) -> list[str] | None: ... diff --git a/mypy/typeshed/stdlib/uuid.pyi b/mypy/typeshed/stdlib/uuid.pyi index 249257783626..1be7a5ef009f 100644 --- a/mypy/typeshed/stdlib/uuid.pyi +++ b/mypy/typeshed/stdlib/uuid.pyi @@ -1,70 +1,69 @@ +import builtins import sys from _typeshed import Unused from enum import Enum from typing_extensions import TypeAlias -# Because UUID has properties called int and bytes we need to rename these temporarily. -_Int: TypeAlias = int -_Bytes: TypeAlias = bytes _FieldsType: TypeAlias = tuple[int, int, int, int, int, int] class SafeUUID(Enum): - safe: int - unsafe: int - unknown: None + safe = 0 + unsafe = -1 + unknown = None class UUID: def __init__( self, hex: str | None = None, - bytes: _Bytes | None = None, - bytes_le: _Bytes | None = None, + bytes: builtins.bytes | None = None, + bytes_le: builtins.bytes | None = None, fields: _FieldsType | None = None, - int: _Int | None = None, - version: _Int | None = None, + int: builtins.int | None = None, + version: builtins.int | None = None, *, is_safe: SafeUUID = ..., ) -> None: ... @property def is_safe(self) -> SafeUUID: ... @property - def bytes(self) -> _Bytes: ... + def bytes(self) -> builtins.bytes: ... @property - def bytes_le(self) -> _Bytes: ... + def bytes_le(self) -> builtins.bytes: ... @property - def clock_seq(self) -> _Int: ... + def clock_seq(self) -> builtins.int: ... @property - def clock_seq_hi_variant(self) -> _Int: ... + def clock_seq_hi_variant(self) -> builtins.int: ... @property - def clock_seq_low(self) -> _Int: ... + def clock_seq_low(self) -> builtins.int: ... @property def fields(self) -> _FieldsType: ... @property def hex(self) -> str: ... @property - def int(self) -> _Int: ... + def int(self) -> builtins.int: ... @property - def node(self) -> _Int: ... + def node(self) -> builtins.int: ... @property - def time(self) -> _Int: ... + def time(self) -> builtins.int: ... @property - def time_hi_version(self) -> _Int: ... + def time_hi_version(self) -> builtins.int: ... @property - def time_low(self) -> _Int: ... + def time_low(self) -> builtins.int: ... @property - def time_mid(self) -> _Int: ... + def time_mid(self) -> builtins.int: ... @property def urn(self) -> str: ... @property def variant(self) -> str: ... @property - def version(self) -> _Int | None: ... - def __int__(self) -> _Int: ... + def version(self) -> builtins.int | None: ... + def __int__(self) -> builtins.int: ... def __eq__(self, other: object) -> bool: ... def __lt__(self, other: UUID) -> bool: ... def __le__(self, other: UUID) -> bool: ... def __gt__(self, other: UUID) -> bool: ... def __ge__(self, other: UUID) -> bool: ... + def __hash__(self) -> builtins.int: ... if sys.version_info >= (3, 9): def getnode() -> int: ... @@ -72,10 +71,21 @@ if sys.version_info >= (3, 9): else: def getnode(*, getters: Unused = None) -> int: ... # undocumented -def uuid1(node: _Int | None = None, clock_seq: _Int | None = None) -> UUID: ... -def uuid3(namespace: UUID, name: str) -> UUID: ... +def uuid1(node: int | None = None, clock_seq: int | None = None) -> UUID: ... + +if sys.version_info >= (3, 12): + def uuid3(namespace: UUID, name: str | bytes) -> UUID: ... + +else: + def uuid3(namespace: UUID, name: str) -> UUID: ... + def uuid4() -> UUID: ... -def uuid5(namespace: UUID, name: str) -> UUID: ... + +if sys.version_info >= (3, 12): + def uuid5(namespace: UUID, name: str | bytes) -> UUID: ... + +else: + def uuid5(namespace: UUID, name: str) -> UUID: ... NAMESPACE_DNS: UUID NAMESPACE_URL: UUID @@ -85,3 +95,6 @@ RESERVED_NCS: str RFC_4122: str RESERVED_MICROSOFT: str RESERVED_FUTURE: str + +if sys.version_info >= (3, 12): + def main() -> None: ... diff --git a/mypy/typeshed/stdlib/warnings.pyi b/mypy/typeshed/stdlib/warnings.pyi index 6222eb65918a..12afea9337e7 100644 --- a/mypy/typeshed/stdlib/warnings.pyi +++ b/mypy/typeshed/stdlib/warnings.pyi @@ -2,8 +2,8 @@ import sys from _warnings import warn as warn, warn_explicit as warn_explicit from collections.abc import Sequence from types import ModuleType, TracebackType -from typing import Any, Generic, TextIO, TypeVar, overload -from typing_extensions import Literal, TypeAlias +from typing import Any, Generic, Literal, TextIO, TypeVar, overload +from typing_extensions import TypeAlias __all__ = [ "warn", diff --git a/mypy/typeshed/stdlib/wave.pyi b/mypy/typeshed/stdlib/wave.pyi index 0d004d6b2d8a..9137f1e47643 100644 --- a/mypy/typeshed/stdlib/wave.pyi +++ b/mypy/typeshed/stdlib/wave.pyi @@ -1,7 +1,7 @@ import sys from _typeshed import ReadableBuffer, Unused -from typing import IO, Any, BinaryIO, NamedTuple, NoReturn, overload -from typing_extensions import Literal, Self, TypeAlias +from typing import IO, Any, BinaryIO, Literal, NamedTuple, NoReturn, overload +from typing_extensions import Self, TypeAlias, deprecated if sys.version_info >= (3, 9): __all__ = ["open", "Error", "Wave_read", "Wave_write"] @@ -26,6 +26,7 @@ class Wave_read: def __init__(self, f: _File) -> None: ... def __enter__(self) -> Self: ... def __exit__(self, *args: Unused) -> None: ... + def __del__(self) -> None: ... def getfp(self) -> BinaryIO | None: ... def rewind(self) -> None: ... def close(self) -> None: ... @@ -37,7 +38,9 @@ class Wave_read: def getcomptype(self) -> str: ... def getcompname(self) -> str: ... def getparams(self) -> _wave_params: ... + @deprecated("Deprecated in Python 3.13; removal scheduled for Python 3.15") def getmarkers(self) -> None: ... + @deprecated("Deprecated in Python 3.13; removal scheduled for Python 3.15") def getmark(self, id: Any) -> NoReturn: ... def setpos(self, pos: int) -> None: ... def readframes(self, nframes: int) -> bytes: ... @@ -46,6 +49,7 @@ class Wave_write: def __init__(self, f: _File) -> None: ... def __enter__(self) -> Self: ... def __exit__(self, *args: Unused) -> None: ... + def __del__(self) -> None: ... def setnchannels(self, nchannels: int) -> None: ... def getnchannels(self) -> int: ... def setsampwidth(self, sampwidth: int) -> None: ... @@ -59,8 +63,11 @@ class Wave_write: def getcompname(self) -> str: ... def setparams(self, params: _wave_params | tuple[int, int, int, int, str, str]) -> None: ... def getparams(self) -> _wave_params: ... + @deprecated("Deprecated in Python 3.13; removal scheduled for Python 3.15") def setmark(self, id: Any, pos: Any, name: Any) -> NoReturn: ... + @deprecated("Deprecated in Python 3.13; removal scheduled for Python 3.15") def getmark(self, id: Any) -> NoReturn: ... + @deprecated("Deprecated in Python 3.13; removal scheduled for Python 3.15") def getmarkers(self) -> None: ... def tell(self) -> int: ... def writeframesraw(self, data: ReadableBuffer) -> None: ... diff --git a/mypy/typeshed/stdlib/weakref.pyi b/mypy/typeshed/stdlib/weakref.pyi index 1e0aac814dfb..e345124237da 100644 --- a/mypy/typeshed/stdlib/weakref.pyi +++ b/mypy/typeshed/stdlib/weakref.pyi @@ -40,20 +40,28 @@ _P = ParamSpec("_P") ProxyTypes: tuple[type[Any], ...] -class WeakMethod(ref[_CallableT], Generic[_CallableT]): - def __new__(cls, meth: _CallableT, callback: Callable[[_CallableT], object] | None = None) -> Self: ... +class WeakMethod(ref[_CallableT]): + def __new__(cls, meth: _CallableT, callback: Callable[[Self], object] | None = None) -> Self: ... def __call__(self) -> _CallableT | None: ... def __eq__(self, other: object) -> bool: ... def __ne__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... class WeakValueDictionary(MutableMapping[_KT, _VT]): @overload def __init__(self) -> None: ... @overload - def __init__(self: WeakValueDictionary[_KT, _VT], __other: Mapping[_KT, _VT] | Iterable[tuple[_KT, _VT]]) -> None: ... + def __init__( + self: WeakValueDictionary[_KT, _VT], # pyright: ignore[reportInvalidTypeVarUse] #11780 + other: Mapping[_KT, _VT] | Iterable[tuple[_KT, _VT]], + /, + ) -> None: ... @overload def __init__( - self: WeakValueDictionary[str, _VT], __other: Mapping[str, _VT] | Iterable[tuple[str, _VT]] = ..., **kwargs: _VT + self: WeakValueDictionary[str, _VT], # pyright: ignore[reportInvalidTypeVarUse] #11780 + other: Mapping[str, _VT] | Iterable[tuple[str, _VT]] = (), + /, + **kwargs: _VT, ) -> None: ... def __len__(self) -> int: ... def __getitem__(self, key: _KT) -> _VT: ... @@ -64,17 +72,23 @@ class WeakValueDictionary(MutableMapping[_KT, _VT]): def copy(self) -> WeakValueDictionary[_KT, _VT]: ... __copy__ = copy def __deepcopy__(self, memo: Any) -> Self: ... + @overload + def get(self, key: _KT, default: None = None) -> _VT | None: ... + @overload + def get(self, key: _KT, default: _T) -> _VT | _T: ... # These are incompatible with Mapping def keys(self) -> Iterator[_KT]: ... # type: ignore[override] def values(self) -> Iterator[_VT]: ... # type: ignore[override] def items(self) -> Iterator[tuple[_KT, _VT]]: ... # type: ignore[override] def itervaluerefs(self) -> Iterator[KeyedRef[_KT, _VT]]: ... def valuerefs(self) -> list[KeyedRef[_KT, _VT]]: ... - def setdefault(self, key: _KT, default: _VT) -> _VT: ... # type: ignore[override] + def setdefault(self, key: _KT, default: _VT) -> _VT: ... @overload def pop(self, key: _KT) -> _VT: ... @overload - def pop(self, key: _KT, default: _VT | _T = ...) -> _VT | _T: ... + def pop(self, key: _KT, default: _VT) -> _VT: ... + @overload + def pop(self, key: _KT, default: _T) -> _VT | _T: ... if sys.version_info >= (3, 9): def __or__(self, other: Mapping[_T1, _T2]) -> WeakValueDictionary[_KT | _T1, _VT | _T2]: ... def __ror__(self, other: Mapping[_T1, _T2]) -> WeakValueDictionary[_KT | _T1, _VT | _T2]: ... @@ -86,7 +100,6 @@ class WeakValueDictionary(MutableMapping[_KT, _VT]): class KeyedRef(ref[_T], Generic[_KT, _T]): key: _KT - # This __new__ method uses a non-standard name for the "cls" parameter def __new__(type, ob: _T, callback: Callable[[_T], Any], key: _KT) -> Self: ... def __init__(self, ob: _T, callback: Callable[[_T], Any], key: _KT) -> None: ... @@ -104,6 +117,10 @@ class WeakKeyDictionary(MutableMapping[_KT, _VT]): def copy(self) -> WeakKeyDictionary[_KT, _VT]: ... __copy__ = copy def __deepcopy__(self, memo: Any) -> Self: ... + @overload + def get(self, key: _KT, default: None = None) -> _VT | None: ... + @overload + def get(self, key: _KT, default: _T) -> _VT | _T: ... # These are incompatible with Mapping def keys(self) -> Iterator[_KT]: ... # type: ignore[override] def values(self) -> Iterator[_VT]: ... # type: ignore[override] @@ -117,7 +134,9 @@ class WeakKeyDictionary(MutableMapping[_KT, _VT]): @overload def pop(self, key: _KT) -> _VT: ... @overload - def pop(self, key: _KT, default: _VT | _T = ...) -> _VT | _T: ... + def pop(self, key: _KT, default: _VT) -> _VT: ... + @overload + def pop(self, key: _KT, default: _T) -> _VT | _T: ... if sys.version_info >= (3, 9): def __or__(self, other: Mapping[_T1, _T2]) -> WeakKeyDictionary[_KT | _T1, _VT | _T2]: ... def __ror__(self, other: Mapping[_T1, _T2]) -> WeakKeyDictionary[_KT | _T1, _VT | _T2]: ... @@ -128,7 +147,7 @@ class WeakKeyDictionary(MutableMapping[_KT, _VT]): def __ior__(self, other: Iterable[tuple[_KT, _VT]]) -> Self: ... class finalize: # TODO: This is a good candidate for to be a `Generic[_P, _T]` class - def __init__(self, __obj: object, __func: Callable[_P, Any], *args: _P.args, **kwargs: _P.kwargs) -> None: ... + def __init__(self, obj: object, func: Callable[_P, Any], /, *args: _P.args, **kwargs: _P.kwargs) -> None: ... def __call__(self, _: Any = None) -> Any | None: ... def detach(self) -> tuple[Any, Any, tuple[Any, ...], dict[str, Any]] | None: ... def peek(self) -> tuple[Any, Any, tuple[Any, ...], dict[str, Any]] | None: ... diff --git a/mypy/typeshed/stdlib/webbrowser.pyi b/mypy/typeshed/stdlib/webbrowser.pyi index 02edd42e7d59..2b3f978c814b 100644 --- a/mypy/typeshed/stdlib/webbrowser.pyi +++ b/mypy/typeshed/stdlib/webbrowser.pyi @@ -1,7 +1,7 @@ import sys from abc import abstractmethod from collections.abc import Callable, Sequence -from typing_extensions import Literal +from typing import Literal __all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"] @@ -43,8 +43,12 @@ class UnixBrowser(BaseBrowser): class Mozilla(UnixBrowser): ... -class Galeon(UnixBrowser): - raise_opts: list[str] +if sys.version_info < (3, 12): + class Galeon(UnixBrowser): + raise_opts: list[str] + + class Grail(BaseBrowser): + def open(self, url: str, new: int = 0, autoraise: bool = True) -> bool: ... class Chrome(UnixBrowser): ... class Opera(UnixBrowser): ... @@ -53,9 +57,6 @@ class Elinks(UnixBrowser): ... class Konqueror(BaseBrowser): def open(self, url: str, new: int = 0, autoraise: bool = True) -> bool: ... -class Grail(BaseBrowser): - def open(self, url: str, new: int = 0, autoraise: bool = True) -> bool: ... - if sys.platform == "win32": class WindowsDefault(BaseBrowser): def open(self, url: str, new: int = 0, autoraise: bool = True) -> bool: ... diff --git a/mypy/typeshed/stdlib/winreg.pyi b/mypy/typeshed/stdlib/winreg.pyi index 5b2d09a3bebc..d4d04817d7e0 100644 --- a/mypy/typeshed/stdlib/winreg.pyi +++ b/mypy/typeshed/stdlib/winreg.pyi @@ -1,35 +1,64 @@ import sys +from _typeshed import ReadableBuffer, Unused from types import TracebackType -from typing import Any -from typing_extensions import Literal, Self, TypeAlias, final +from typing import Any, Final, Literal, final, overload +from typing_extensions import Self, TypeAlias if sys.platform == "win32": _KeyType: TypeAlias = HKEYType | int - def CloseKey(__hkey: _KeyType) -> None: ... - def ConnectRegistry(__computer_name: str | None, __key: _KeyType) -> HKEYType: ... - def CreateKey(__key: _KeyType, __sub_key: str | None) -> HKEYType: ... + def CloseKey(hkey: _KeyType, /) -> None: ... + def ConnectRegistry(computer_name: str | None, key: _KeyType, /) -> HKEYType: ... + def CreateKey(key: _KeyType, sub_key: str | None, /) -> HKEYType: ... def CreateKeyEx(key: _KeyType, sub_key: str | None, reserved: int = 0, access: int = 131078) -> HKEYType: ... - def DeleteKey(__key: _KeyType, __sub_key: str) -> None: ... + def DeleteKey(key: _KeyType, sub_key: str, /) -> None: ... def DeleteKeyEx(key: _KeyType, sub_key: str, access: int = 256, reserved: int = 0) -> None: ... - def DeleteValue(__key: _KeyType, __value: str) -> None: ... - def EnumKey(__key: _KeyType, __index: int) -> str: ... - def EnumValue(__key: _KeyType, __index: int) -> tuple[str, Any, int]: ... - def ExpandEnvironmentStrings(__str: str) -> str: ... - def FlushKey(__key: _KeyType) -> None: ... - def LoadKey(__key: _KeyType, __sub_key: str, __file_name: str) -> None: ... + def DeleteValue(key: _KeyType, value: str, /) -> None: ... + def EnumKey(key: _KeyType, index: int, /) -> str: ... + def EnumValue(key: _KeyType, index: int, /) -> tuple[str, Any, int]: ... + def ExpandEnvironmentStrings(string: str, /) -> str: ... + def FlushKey(key: _KeyType, /) -> None: ... + def LoadKey(key: _KeyType, sub_key: str, file_name: str, /) -> None: ... def OpenKey(key: _KeyType, sub_key: str, reserved: int = 0, access: int = 131097) -> HKEYType: ... def OpenKeyEx(key: _KeyType, sub_key: str, reserved: int = 0, access: int = 131097) -> HKEYType: ... - def QueryInfoKey(__key: _KeyType) -> tuple[int, int, int]: ... - def QueryValue(__key: _KeyType, __sub_key: str | None) -> str: ... - def QueryValueEx(__key: _KeyType, __name: str) -> tuple[Any, int]: ... - def SaveKey(__key: _KeyType, __file_name: str) -> None: ... - def SetValue(__key: _KeyType, __sub_key: str, __type: int, __value: str) -> None: ... + def QueryInfoKey(key: _KeyType, /) -> tuple[int, int, int]: ... + def QueryValue(key: _KeyType, sub_key: str | None, /) -> str: ... + def QueryValueEx(key: _KeyType, name: str, /) -> tuple[Any, int]: ... + def SaveKey(key: _KeyType, file_name: str, /) -> None: ... + def SetValue(key: _KeyType, sub_key: str, type: int, value: str, /) -> None: ... + @overload # type=REG_DWORD|REG_QWORD def SetValueEx( - __key: _KeyType, __value_name: str | None, __reserved: Any, __type: int, __value: str | int - ) -> None: ... # reserved is ignored - def DisableReflectionKey(__key: _KeyType) -> None: ... - def EnableReflectionKey(__key: _KeyType) -> None: ... - def QueryReflectionKey(__key: _KeyType) -> bool: ... + key: _KeyType, value_name: str | None, reserved: Unused, type: Literal[4, 5], value: int | None, / + ) -> None: ... + @overload # type=REG_SZ|REG_EXPAND_SZ + def SetValueEx( + key: _KeyType, value_name: str | None, reserved: Unused, type: Literal[1, 2], value: str | None, / + ) -> None: ... + @overload # type=REG_MULTI_SZ + def SetValueEx( + key: _KeyType, value_name: str | None, reserved: Unused, type: Literal[7], value: list[str] | None, / + ) -> None: ... + @overload # type=REG_BINARY and everything else + def SetValueEx( + key: _KeyType, + value_name: str | None, + reserved: Unused, + type: Literal[0, 3, 8, 9, 10, 11], + value: ReadableBuffer | None, + /, + ) -> None: ... + @overload # Unknown or undocumented + def SetValueEx( + key: _KeyType, + value_name: str | None, + reserved: Unused, + type: int, + value: int | str | list[str] | ReadableBuffer | None, + /, + ) -> None: ... + def DisableReflectionKey(key: _KeyType, /) -> None: ... + def EnableReflectionKey(key: _KeyType, /) -> None: ... + def QueryReflectionKey(key: _KeyType, /) -> bool: ... + HKEY_CLASSES_ROOT: int HKEY_CURRENT_USER: int HKEY_LOCAL_MACHINE: int @@ -38,52 +67,52 @@ if sys.platform == "win32": HKEY_CURRENT_CONFIG: int HKEY_DYN_DATA: int - KEY_ALL_ACCESS: Literal[983103] - KEY_WRITE: Literal[131078] - KEY_READ: Literal[131097] - KEY_EXECUTE: Literal[131097] - KEY_QUERY_VALUE: Literal[1] - KEY_SET_VALUE: Literal[2] - KEY_CREATE_SUB_KEY: Literal[4] - KEY_ENUMERATE_SUB_KEYS: Literal[8] - KEY_NOTIFY: Literal[16] - KEY_CREATE_LINK: Literal[32] + KEY_ALL_ACCESS: Final = 983103 + KEY_WRITE: Final = 131078 + KEY_READ: Final = 131097 + KEY_EXECUTE: Final = 131097 + KEY_QUERY_VALUE: Final = 1 + KEY_SET_VALUE: Final = 2 + KEY_CREATE_SUB_KEY: Final = 4 + KEY_ENUMERATE_SUB_KEYS: Final = 8 + KEY_NOTIFY: Final = 16 + KEY_CREATE_LINK: Final = 32 - KEY_WOW64_64KEY: Literal[256] - KEY_WOW64_32KEY: Literal[512] + KEY_WOW64_64KEY: Final = 256 + KEY_WOW64_32KEY: Final = 512 - REG_BINARY: Literal[3] - REG_DWORD: Literal[4] - REG_DWORD_LITTLE_ENDIAN: Literal[4] - REG_DWORD_BIG_ENDIAN: Literal[5] - REG_EXPAND_SZ: Literal[2] - REG_LINK: Literal[6] - REG_MULTI_SZ: Literal[7] - REG_NONE: Literal[0] - REG_QWORD: Literal[11] - REG_QWORD_LITTLE_ENDIAN: Literal[11] - REG_RESOURCE_LIST: Literal[8] - REG_FULL_RESOURCE_DESCRIPTOR: Literal[9] - REG_RESOURCE_REQUIREMENTS_LIST: Literal[10] - REG_SZ: Literal[1] + REG_BINARY: Final = 3 + REG_DWORD: Final = 4 + REG_DWORD_LITTLE_ENDIAN: Final = 4 + REG_DWORD_BIG_ENDIAN: Final = 5 + REG_EXPAND_SZ: Final = 2 + REG_LINK: Final = 6 + REG_MULTI_SZ: Final = 7 + REG_NONE: Final = 0 + REG_QWORD: Final = 11 + REG_QWORD_LITTLE_ENDIAN: Final = 11 + REG_RESOURCE_LIST: Final = 8 + REG_FULL_RESOURCE_DESCRIPTOR: Final = 9 + REG_RESOURCE_REQUIREMENTS_LIST: Final = 10 + REG_SZ: Final = 1 - REG_CREATED_NEW_KEY: int # undocumented - REG_LEGAL_CHANGE_FILTER: int # undocumented - REG_LEGAL_OPTION: int # undocumented - REG_NOTIFY_CHANGE_ATTRIBUTES: int # undocumented - REG_NOTIFY_CHANGE_LAST_SET: int # undocumented - REG_NOTIFY_CHANGE_NAME: int # undocumented - REG_NOTIFY_CHANGE_SECURITY: int # undocumented - REG_NO_LAZY_FLUSH: int # undocumented - REG_OPENED_EXISTING_KEY: int # undocumented - REG_OPTION_BACKUP_RESTORE: int # undocumented - REG_OPTION_CREATE_LINK: int # undocumented - REG_OPTION_NON_VOLATILE: int # undocumented - REG_OPTION_OPEN_LINK: int # undocumented - REG_OPTION_RESERVED: int # undocumented - REG_OPTION_VOLATILE: int # undocumented - REG_REFRESH_HIVE: int # undocumented - REG_WHOLE_HIVE_VOLATILE: int # undocumented + REG_CREATED_NEW_KEY: Final = 1 # undocumented + REG_LEGAL_CHANGE_FILTER: Final = 268435471 # undocumented + REG_LEGAL_OPTION: Final = 31 # undocumented + REG_NOTIFY_CHANGE_ATTRIBUTES: Final = 2 # undocumented + REG_NOTIFY_CHANGE_LAST_SET: Final = 4 # undocumented + REG_NOTIFY_CHANGE_NAME: Final = 1 # undocumented + REG_NOTIFY_CHANGE_SECURITY: Final = 8 # undocumented + REG_NO_LAZY_FLUSH: Final = 4 # undocumented + REG_OPENED_EXISTING_KEY: Final = 2 # undocumented + REG_OPTION_BACKUP_RESTORE: Final = 4 # undocumented + REG_OPTION_CREATE_LINK: Final = 2 # undocumented + REG_OPTION_NON_VOLATILE: Final = 0 # undocumented + REG_OPTION_OPEN_LINK: Final = 8 # undocumented + REG_OPTION_RESERVED: Final = 0 # undocumented + REG_OPTION_VOLATILE: Final = 1 # undocumented + REG_REFRESH_HIVE: Final = 2 # undocumented + REG_WHOLE_HIVE_VOLATILE: Final = 1 # undocumented error = OSError @@ -98,3 +127,6 @@ if sys.platform == "win32": ) -> bool | None: ... def Close(self) -> None: ... def Detach(self) -> int: ... + def __hash__(self) -> int: ... + @property + def handle(self) -> int: ... diff --git a/mypy/typeshed/stdlib/winsound.pyi b/mypy/typeshed/stdlib/winsound.pyi index 9b2b57a38986..bacc5302826f 100644 --- a/mypy/typeshed/stdlib/winsound.pyi +++ b/mypy/typeshed/stdlib/winsound.pyi @@ -1,9 +1,9 @@ import sys from _typeshed import ReadableBuffer -from typing import overload -from typing_extensions import Literal +from typing import Literal, overload if sys.platform == "win32": + SND_APPLICATION: Literal[128] SND_FILENAME: Literal[131072] SND_ALIAS: Literal[65536] SND_LOOP: Literal[8] diff --git a/mypy/typeshed/stdlib/wsgiref/types.pyi b/mypy/typeshed/stdlib/wsgiref/types.pyi index 4e8f47264f3a..86212df8ccdc 100644 --- a/mypy/typeshed/stdlib/wsgiref/types.pyi +++ b/mypy/typeshed/stdlib/wsgiref/types.pyi @@ -7,26 +7,26 @@ __all__ = ["StartResponse", "WSGIEnvironment", "WSGIApplication", "InputStream", class StartResponse(Protocol): def __call__( - self, __status: str, __headers: list[tuple[str, str]], __exc_info: _OptExcInfo | None = ... + self, status: str, headers: list[tuple[str, str]], exc_info: _OptExcInfo | None = ..., / ) -> Callable[[bytes], object]: ... WSGIEnvironment: TypeAlias = dict[str, Any] WSGIApplication: TypeAlias = Callable[[WSGIEnvironment, StartResponse], Iterable[bytes]] class InputStream(Protocol): - def read(self, __size: int = ...) -> bytes: ... - def readline(self, __size: int = ...) -> bytes: ... - def readlines(self, __hint: int = ...) -> list[bytes]: ... + def read(self, size: int = ..., /) -> bytes: ... + def readline(self, size: int = ..., /) -> bytes: ... + def readlines(self, hint: int = ..., /) -> list[bytes]: ... def __iter__(self) -> Iterator[bytes]: ... class ErrorStream(Protocol): def flush(self) -> object: ... - def write(self, __s: str) -> object: ... - def writelines(self, __seq: list[str]) -> object: ... + def write(self, s: str, /) -> object: ... + def writelines(self, seq: list[str], /) -> object: ... class _Readable(Protocol): - def read(self, __size: int = ...) -> bytes: ... + def read(self, size: int = ..., /) -> bytes: ... # Optional: def close(self) -> object: ... class FileWrapper(Protocol): - def __call__(self, __file: _Readable, __block_size: int = ...) -> Iterable[bytes]: ... + def __call__(self, file: _Readable, block_size: int = ..., /) -> Iterable[bytes]: ... diff --git a/mypy/typeshed/stdlib/xml/dom/domreg.pyi b/mypy/typeshed/stdlib/xml/dom/domreg.pyi index a46d3ff090e6..346a4bf63bd4 100644 --- a/mypy/typeshed/stdlib/xml/dom/domreg.pyi +++ b/mypy/typeshed/stdlib/xml/dom/domreg.pyi @@ -5,6 +5,4 @@ well_known_implementations: dict[str, str] registered: dict[str, Callable[[], DOMImplementation]] def registerDOMImplementation(name: str, factory: Callable[[], DOMImplementation]) -> None: ... -def getDOMImplementation( - name: str | None = None, features: str | Iterable[tuple[str, str | None]] = ... -) -> DOMImplementation: ... +def getDOMImplementation(name: str | None = None, features: str | Iterable[tuple[str, str | None]] = ()) -> DOMImplementation: ... diff --git a/mypy/typeshed/stdlib/xml/dom/minicompat.pyi b/mypy/typeshed/stdlib/xml/dom/minicompat.pyi index 4507b3d98ee7..162f60254a58 100644 --- a/mypy/typeshed/stdlib/xml/dom/minicompat.pyi +++ b/mypy/typeshed/stdlib/xml/dom/minicompat.pyi @@ -1,5 +1,5 @@ from collections.abc import Iterable -from typing import Any, TypeVar +from typing import Any, Literal, TypeVar __all__ = ["NodeList", "EmptyNodeList", "StringTypes", "defproperty"] @@ -8,11 +8,13 @@ _T = TypeVar("_T") StringTypes: tuple[type[str]] class NodeList(list[_T]): - length: int + @property + def length(self) -> int: ... def item(self, index: int) -> _T | None: ... -class EmptyNodeList(tuple[Any, ...]): - length: int +class EmptyNodeList(tuple[()]): + @property + def length(self) -> Literal[0]: ... def item(self, index: int) -> None: ... def __add__(self, other: Iterable[_T]) -> NodeList[_T]: ... # type: ignore[override] def __radd__(self, other: Iterable[_T]) -> NodeList[_T]: ... diff --git a/mypy/typeshed/stdlib/xml/dom/minidom.pyi b/mypy/typeshed/stdlib/xml/dom/minidom.pyi index 7bbffb88c8f7..fae2c4d98714 100644 --- a/mypy/typeshed/stdlib/xml/dom/minidom.pyi +++ b/mypy/typeshed/stdlib/xml/dom/minidom.pyi @@ -1,12 +1,18 @@ import sys import xml.dom from _typeshed import Incomplete, ReadableBuffer, SupportsRead, SupportsWrite -from typing_extensions import Literal, Self +from typing import Literal, NoReturn, TypeVar, overload +from typing_extensions import Self +from xml.dom.minicompat import NodeList from xml.dom.xmlbuilder import DocumentLS, DOMImplementationLS from xml.sax.xmlreader import XMLReader -def parse(file: str | SupportsRead[ReadableBuffer | str], parser: XMLReader | None = None, bufsize: int | None = None): ... -def parseString(string: str | ReadableBuffer, parser: XMLReader | None = None): ... +_N = TypeVar("_N", bound=Node) + +def parse( + file: str | SupportsRead[ReadableBuffer | str], parser: XMLReader | None = None, bufsize: int | None = None +) -> Document: ... +def parseString(string: str | ReadableBuffer, parser: XMLReader | None = None) -> Document: ... def getDOMImplementation(features=None) -> DOMImplementation | None: ... class Node(xml.dom.Node): @@ -24,17 +30,73 @@ class Node(xml.dom.Node): def localName(self) -> str | None: ... def __bool__(self) -> Literal[True]: ... if sys.version_info >= (3, 9): - def toxml(self, encoding: str | None = None, standalone: bool | None = None): ... + @overload + def toxml(self, encoding: str, standalone: bool | None = None) -> bytes: ... + @overload + def toxml(self, encoding: None = None, standalone: bool | None = None) -> str: ... + @overload + def toprettyxml( + self, + indent: str = "\t", + newl: str = "\n", + # Handle any case where encoding is not provided or where it is passed with None + encoding: None = None, + standalone: bool | None = None, + ) -> str: ... + @overload def toprettyxml( - self, indent: str = "\t", newl: str = "\n", encoding: str | None = None, standalone: bool | None = None - ): ... + self, + indent: str, + newl: str, + # Handle cases where encoding is passed as str *positionally* + encoding: str, + standalone: bool | None = None, + ) -> bytes: ... + @overload + def toprettyxml( + self, + indent: str = "\t", + newl: str = "\n", + # Handle all cases where encoding is passed as a keyword argument; because standalone + # comes after, it will also have to be a keyword arg if encoding is + *, + encoding: str, + standalone: bool | None = None, + ) -> bytes: ... else: - def toxml(self, encoding: str | None = None): ... - def toprettyxml(self, indent: str = "\t", newl: str = "\n", encoding: str | None = None): ... + @overload + def toxml(self, encoding: str) -> bytes: ... + @overload + def toxml(self, encoding: None = None) -> str: ... + @overload + def toprettyxml( + self, + indent: str = "\t", + newl: str = "\n", + # Handle any case where encoding is not provided or where it is passed with None + encoding: None = None, + ) -> str: ... + @overload + def toprettyxml( + self, + indent: str, + newl: str, + # Handle cases where encoding is passed as str *positionally* + encoding: str, + ) -> bytes: ... + @overload + def toprettyxml( + self, + indent: str = "\t", + newl: str = "\n", + # Handle all cases where encoding is passed as a keyword argument + *, + encoding: str, + ) -> bytes: ... def hasChildNodes(self) -> bool: ... def insertBefore(self, newChild, refChild): ... - def appendChild(self, node): ... + def appendChild(self, node: _N) -> _N: ... def replaceChild(self, newChild, oldChild): ... def removeChild(self, oldChild): ... def normalize(self) -> None: ... @@ -143,8 +205,8 @@ class Element(Node): removeAttributeNodeNS: Incomplete def hasAttribute(self, name: str) -> bool: ... def hasAttributeNS(self, namespaceURI: str, localName) -> bool: ... - def getElementsByTagName(self, name: str): ... - def getElementsByTagNameNS(self, namespaceURI: str, localName): ... + def getElementsByTagName(self, name: str) -> NodeList[Element]: ... + def getElementsByTagNameNS(self, namespaceURI: str, localName: str) -> NodeList[Element]: ... def writexml(self, writer: SupportsWrite[str], indent: str = "", addindent: str = "", newl: str = "") -> None: ... def hasAttributes(self) -> bool: ... def setIdAttribute(self, name) -> None: ... @@ -158,12 +220,12 @@ class Childless: childNodes: Incomplete firstChild: Incomplete lastChild: Incomplete - def appendChild(self, node) -> None: ... + def appendChild(self, node) -> NoReturn: ... def hasChildNodes(self) -> bool: ... - def insertBefore(self, newChild, refChild) -> None: ... - def removeChild(self, oldChild) -> None: ... + def insertBefore(self, newChild, refChild) -> NoReturn: ... + def removeChild(self, oldChild) -> NoReturn: ... def normalize(self) -> None: ... - def replaceChild(self, newChild, oldChild) -> None: ... + def replaceChild(self, newChild, oldChild) -> NoReturn: ... class ProcessingInstruction(Childless, Node): nodeType: int @@ -194,9 +256,9 @@ class Text(CharacterData): nodeName: str attributes: Incomplete data: Incomplete - def splitText(self, offset): ... + def splitText(self, offset: int) -> Self: ... def writexml(self, writer: SupportsWrite[str], indent: str = "", addindent: str = "", newl: str = "") -> None: ... - def replaceWholeText(self, content): ... + def replaceWholeText(self, content) -> Self | None: ... @property def isWhitespaceInElementContent(self) -> bool: ... @property @@ -214,7 +276,7 @@ class CDATASection(Text): def writexml(self, writer: SupportsWrite[str], indent: str = "", addindent: str = "", newl: str = "") -> None: ... class ReadOnlySequentialNamedNodeMap: - def __init__(self, seq=...) -> None: ... + def __init__(self, seq=()) -> None: ... def __len__(self) -> int: ... def getNamedItem(self, name): ... def getNamedItemNS(self, namespaceURI: str, localName): ... @@ -254,10 +316,10 @@ class Entity(Identified, Node): notationName: Incomplete childNodes: Incomplete def __init__(self, name, publicId, systemId, notation) -> None: ... - def appendChild(self, newChild) -> None: ... - def insertBefore(self, newChild, refChild) -> None: ... - def removeChild(self, oldChild) -> None: ... - def replaceChild(self, newChild, oldChild) -> None: ... + def appendChild(self, newChild) -> NoReturn: ... + def insertBefore(self, newChild, refChild) -> NoReturn: ... + def removeChild(self, oldChild) -> NoReturn: ... + def replaceChild(self, newChild, oldChild) -> NoReturn: ... class Notation(Identified, Childless, Node): nodeType: int @@ -300,7 +362,7 @@ class Document(Node, DocumentLS): doctype: DocumentType | None childNodes: Incomplete def __init__(self) -> None: ... - def appendChild(self, node): ... + def appendChild(self, node: _N) -> _N: ... documentElement: Incomplete def removeChild(self, oldChild): ... def unlink(self) -> None: ... @@ -314,9 +376,9 @@ class Document(Node, DocumentLS): def createAttribute(self, qName) -> Attr: ... def createElementNS(self, namespaceURI: str, qualifiedName: str): ... def createAttributeNS(self, namespaceURI: str, qualifiedName: str) -> Attr: ... - def getElementById(self, id): ... - def getElementsByTagName(self, name: str): ... - def getElementsByTagNameNS(self, namespaceURI: str, localName): ... + def getElementById(self, id: str) -> Element | None: ... + def getElementsByTagName(self, name: str) -> NodeList[Element]: ... + def getElementsByTagNameNS(self, namespaceURI: str, localName: str) -> NodeList[Element]: ... def isSupported(self, feature: str, version: str | None) -> bool: ... def importNode(self, node, deep): ... if sys.version_info >= (3, 9): diff --git a/mypy/typeshed/stdlib/xml/dom/pulldom.pyi b/mypy/typeshed/stdlib/xml/dom/pulldom.pyi index 920905160e43..95436ab5dd38 100644 --- a/mypy/typeshed/stdlib/xml/dom/pulldom.pyi +++ b/mypy/typeshed/stdlib/xml/dom/pulldom.pyi @@ -1,7 +1,8 @@ import sys from _typeshed import Incomplete, SupportsRead from collections.abc import Sequence -from typing_extensions import Literal, TypeAlias +from typing import Literal +from typing_extensions import TypeAlias from xml.dom.minidom import Document, DOMImplementation, Element, Text from xml.sax.handler import ContentHandler from xml.sax.xmlreader import XMLReader diff --git a/mypy/typeshed/stdlib/xml/dom/xmlbuilder.pyi b/mypy/typeshed/stdlib/xml/dom/xmlbuilder.pyi index c07e4ba2465e..ab76d362e23f 100644 --- a/mypy/typeshed/stdlib/xml/dom/xmlbuilder.pyi +++ b/mypy/typeshed/stdlib/xml/dom/xmlbuilder.pyi @@ -1,6 +1,6 @@ from _typeshed import Incomplete, Unused -from typing import Any, NoReturn -from typing_extensions import Literal, TypeAlias +from typing import Any, Literal, NoReturn +from typing_extensions import TypeAlias from urllib.request import OpenerDirector from xml.dom.expatbuilder import ExpatBuilder, ExpatBuilderNS from xml.dom.minidom import Node @@ -60,7 +60,7 @@ class DOMBuilder: def supportsFeature(self, name: str) -> bool: ... def canSetFeature(self, name: str, state: int) -> bool: ... # getFeature could return any attribute from an instance of `Options` - def getFeature(self, name: str) -> Incomplete: ... + def getFeature(self, name: str) -> Any: ... def parseURI(self, uri: str) -> ExpatBuilder | ExpatBuilderNS: ... def parse(self, input: DOMInputSource) -> ExpatBuilder | ExpatBuilderNS: ... # `input` and `cnode` argtypes for `parseWithContext` are unknowable diff --git a/mypy/typeshed/stdlib/xml/etree/ElementTree.pyi b/mypy/typeshed/stdlib/xml/etree/ElementTree.pyi index db33b2d673d7..9198bd3322d9 100644 --- a/mypy/typeshed/stdlib/xml/etree/ElementTree.pyi +++ b/mypy/typeshed/stdlib/xml/etree/ElementTree.pyi @@ -2,14 +2,16 @@ import sys from _collections_abc import dict_keys from _typeshed import FileDescriptorOrPath, ReadableBuffer, SupportsRead, SupportsWrite from collections.abc import Callable, Generator, ItemsView, Iterable, Iterator, Mapping, Sequence -from typing import Any, TypeVar, overload -from typing_extensions import Literal, SupportsIndex, TypeAlias, TypeGuard +from typing import Any, Literal, SupportsIndex, TypeVar, overload +from typing_extensions import TypeAlias, TypeGuard, deprecated __all__ = [ + "C14NWriterTarget", "Comment", "dump", "Element", "ElementTree", + "canonicalize", "fromstring", "fromstringlist", "iselement", @@ -31,9 +33,6 @@ __all__ = [ "register_namespace", ] -if sys.version_info >= (3, 8): - __all__ += ["C14NWriterTarget", "canonicalize"] - if sys.version_info >= (3, 9): __all__ += ["indent"] @@ -50,46 +49,44 @@ class ParseError(SyntaxError): # In reality it works based on `.tag` attribute duck typing. def iselement(element: object) -> TypeGuard[Element]: ... - -if sys.version_info >= (3, 8): - @overload - def canonicalize( - xml_data: str | ReadableBuffer | None = None, - *, - out: None = None, - from_file: _FileRead | None = None, - with_comments: bool = ..., - strip_text: bool = ..., - rewrite_prefixes: bool = ..., - qname_aware_tags: Iterable[str] | None = ..., - qname_aware_attrs: Iterable[str] | None = ..., - exclude_attrs: Iterable[str] | None = ..., - exclude_tags: Iterable[str] | None = ..., - ) -> str: ... - @overload - def canonicalize( - xml_data: str | ReadableBuffer | None = None, - *, - out: SupportsWrite[str], - from_file: _FileRead | None = None, - with_comments: bool = ..., - strip_text: bool = ..., - rewrite_prefixes: bool = ..., - qname_aware_tags: Iterable[str] | None = ..., - qname_aware_attrs: Iterable[str] | None = ..., - exclude_attrs: Iterable[str] | None = ..., - exclude_tags: Iterable[str] | None = ..., - ) -> None: ... +@overload +def canonicalize( + xml_data: str | ReadableBuffer | None = None, + *, + out: None = None, + from_file: _FileRead | None = None, + with_comments: bool = False, + strip_text: bool = False, + rewrite_prefixes: bool = False, + qname_aware_tags: Iterable[str] | None = None, + qname_aware_attrs: Iterable[str] | None = None, + exclude_attrs: Iterable[str] | None = None, + exclude_tags: Iterable[str] | None = None, +) -> str: ... +@overload +def canonicalize( + xml_data: str | ReadableBuffer | None = None, + *, + out: SupportsWrite[str], + from_file: _FileRead | None = None, + with_comments: bool = False, + strip_text: bool = False, + rewrite_prefixes: bool = False, + qname_aware_tags: Iterable[str] | None = None, + qname_aware_attrs: Iterable[str] | None = None, + exclude_attrs: Iterable[str] | None = None, + exclude_tags: Iterable[str] | None = None, +) -> None: ... class Element: tag: str attrib: dict[str, str] text: str | None tail: str | None - def __init__(self, tag: str | Callable[..., Element], attrib: dict[str, str] = ..., **extra: str) -> None: ... - def append(self, __subelement: Element) -> None: ... + def __init__(self, tag: str, attrib: dict[str, str] = ..., **extra: str) -> None: ... + def append(self, subelement: Element, /) -> None: ... def clear(self) -> None: ... - def extend(self, __elements: Iterable[Element]) -> None: ... + def extend(self, elements: Iterable[Element], /) -> None: ... def find(self, path: str, namespaces: dict[str, str] | None = None) -> Element | None: ... def findall(self, path: str, namespaces: dict[str, str] | None = None) -> list[Element]: ... @overload @@ -100,30 +97,34 @@ class Element: def get(self, key: str, default: None = None) -> str | None: ... @overload def get(self, key: str, default: _T) -> str | _T: ... - def insert(self, __index: int, __subelement: Element) -> None: ... + def insert(self, index: int, subelement: Element, /) -> None: ... def items(self) -> ItemsView[str, str]: ... def iter(self, tag: str | None = None) -> Generator[Element, None, None]: ... def iterfind(self, path: str, namespaces: dict[str, str] | None = None) -> Generator[Element, None, None]: ... def itertext(self) -> Generator[str, None, None]: ... def keys(self) -> dict_keys[str, str]: ... # makeelement returns the type of self in Python impl, but not in C impl - def makeelement(self, __tag: str, __attrib: dict[str, str]) -> Element: ... - def remove(self, __subelement: Element) -> None: ... - def set(self, __key: str, __value: str) -> None: ... + def makeelement(self, tag: str, attrib: dict[str, str], /) -> Element: ... + def remove(self, subelement: Element, /) -> None: ... + def set(self, key: str, value: str, /) -> None: ... def __copy__(self) -> Element: ... # returns the type of self in Python impl, but not in C impl - def __deepcopy__(self, __memo: Any) -> Element: ... # Only exists in C impl - def __delitem__(self, __i: SupportsIndex | slice) -> None: ... + def __deepcopy__(self, memo: Any, /) -> Element: ... # Only exists in C impl + def __delitem__(self, key: SupportsIndex | slice, /) -> None: ... @overload - def __getitem__(self, __i: SupportsIndex) -> Element: ... + def __getitem__(self, key: SupportsIndex, /) -> Element: ... @overload - def __getitem__(self, __s: slice) -> list[Element]: ... + def __getitem__(self, key: slice, /) -> list[Element]: ... def __len__(self) -> int: ... # Doesn't actually exist at runtime, but instance of the class are indeed iterable due to __getitem__. def __iter__(self) -> Iterator[Element]: ... @overload - def __setitem__(self, __i: SupportsIndex, __o: Element) -> None: ... + def __setitem__(self, key: SupportsIndex, value: Element, /) -> None: ... @overload - def __setitem__(self, __s: slice, __o: Iterable[Element]) -> None: ... + def __setitem__(self, key: slice, value: Iterable[Element], /) -> None: ... + + # Doesn't really exist in earlier versions, where __len__ is called implicitly instead + @deprecated("Testing an element's truth value is deprecated.") + def __bool__(self) -> bool: ... if sys.version_info < (3, 9): def getchildren(self) -> list[Element]: ... def getiterator(self, tag: str | None = None) -> list[Element]: ... @@ -132,7 +133,7 @@ def SubElement(parent: Element, tag: str, attrib: dict[str, str] = ..., **extra: def Comment(text: str | None = None) -> Element: ... def ProcessingInstruction(target: str, text: str | None = None) -> Element: ... -PI: Callable[..., Element] +PI = ProcessingInstruction class QName: text: str @@ -142,6 +143,7 @@ class QName: def __gt__(self, other: QName | str) -> bool: ... def __ge__(self, other: QName | str) -> bool: ... def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... class ElementTree: def __init__(self, element: Element | None = None, file: _FileRead | None = None) -> None: ... @@ -171,93 +173,66 @@ class ElementTree: def write_c14n(self, file: _FileWriteC14N) -> None: ... def register_namespace(prefix: str, uri: str) -> None: ... - -if sys.version_info >= (3, 8): - @overload - def tostring( - element: Element, - encoding: None = None, - method: str | None = None, - *, - xml_declaration: bool | None = None, - default_namespace: str | None = None, - short_empty_elements: bool = True, - ) -> bytes: ... - @overload - def tostring( - element: Element, - encoding: Literal["unicode"], - method: str | None = None, - *, - xml_declaration: bool | None = None, - default_namespace: str | None = None, - short_empty_elements: bool = True, - ) -> str: ... - @overload - def tostring( - element: Element, - encoding: str, - method: str | None = None, - *, - xml_declaration: bool | None = None, - default_namespace: str | None = None, - short_empty_elements: bool = True, - ) -> Any: ... - @overload - def tostringlist( - element: Element, - encoding: None = None, - method: str | None = None, - *, - xml_declaration: bool | None = None, - default_namespace: str | None = None, - short_empty_elements: bool = True, - ) -> list[bytes]: ... - @overload - def tostringlist( - element: Element, - encoding: Literal["unicode"], - method: str | None = None, - *, - xml_declaration: bool | None = None, - default_namespace: str | None = None, - short_empty_elements: bool = True, - ) -> list[str]: ... - @overload - def tostringlist( - element: Element, - encoding: str, - method: str | None = None, - *, - xml_declaration: bool | None = None, - default_namespace: str | None = None, - short_empty_elements: bool = True, - ) -> list[Any]: ... - -else: - @overload - def tostring( - element: Element, encoding: None = None, method: str | None = None, *, short_empty_elements: bool = True - ) -> bytes: ... - @overload - def tostring( - element: Element, encoding: Literal["unicode"], method: str | None = None, *, short_empty_elements: bool = True - ) -> str: ... - @overload - def tostring(element: Element, encoding: str, method: str | None = None, *, short_empty_elements: bool = True) -> Any: ... - @overload - def tostringlist( - element: Element, encoding: None = None, method: str | None = None, *, short_empty_elements: bool = True - ) -> list[bytes]: ... - @overload - def tostringlist( - element: Element, encoding: Literal["unicode"], method: str | None = None, *, short_empty_elements: bool = True - ) -> list[str]: ... - @overload - def tostringlist( - element: Element, encoding: str, method: str | None = None, *, short_empty_elements: bool = True - ) -> list[Any]: ... - +@overload +def tostring( + element: Element, + encoding: None = None, + method: str | None = None, + *, + xml_declaration: bool | None = None, + default_namespace: str | None = None, + short_empty_elements: bool = True, +) -> bytes: ... +@overload +def tostring( + element: Element, + encoding: Literal["unicode"], + method: str | None = None, + *, + xml_declaration: bool | None = None, + default_namespace: str | None = None, + short_empty_elements: bool = True, +) -> str: ... +@overload +def tostring( + element: Element, + encoding: str, + method: str | None = None, + *, + xml_declaration: bool | None = None, + default_namespace: str | None = None, + short_empty_elements: bool = True, +) -> Any: ... +@overload +def tostringlist( + element: Element, + encoding: None = None, + method: str | None = None, + *, + xml_declaration: bool | None = None, + default_namespace: str | None = None, + short_empty_elements: bool = True, +) -> list[bytes]: ... +@overload +def tostringlist( + element: Element, + encoding: Literal["unicode"], + method: str | None = None, + *, + xml_declaration: bool | None = None, + default_namespace: str | None = None, + short_empty_elements: bool = True, +) -> list[str]: ... +@overload +def tostringlist( + element: Element, + encoding: str, + method: str | None = None, + *, + xml_declaration: bool | None = None, + default_namespace: str | None = None, + short_empty_elements: bool = True, +) -> list[Any]: ... def dump(elem: Element) -> None: ... if sys.version_info >= (3, 9): @@ -275,6 +250,7 @@ class XMLPullParser: # Second element in the tuple could be `Element`, `tuple[str, str]` or `None`. # Use `Any` to avoid false-positive errors. def read_events(self) -> Iterator[tuple[str, Any]]: ... + def flush(self) -> None: ... def XML(text: str | ReadableBuffer, parser: XMLParser | None = None) -> Element: ... def XMLID(text: str | ReadableBuffer, parser: XMLParser | None = None) -> tuple[Element, dict[str, Element]]: ... @@ -296,53 +272,48 @@ def fromstringlist(sequence: Sequence[str | ReadableBuffer], parser: XMLParser | _ElementFactory: TypeAlias = Callable[[Any, dict[Any, Any]], Element] class TreeBuilder: - if sys.version_info >= (3, 8): - # comment_factory can take None because passing None to Comment is not an error - def __init__( - self, - element_factory: _ElementFactory | None = ..., - *, - comment_factory: Callable[[str | None], Element] | None = ..., - pi_factory: Callable[[str, str | None], Element] | None = ..., - insert_comments: bool = ..., - insert_pis: bool = ..., - ) -> None: ... - insert_comments: bool - insert_pis: bool - else: - def __init__(self, element_factory: _ElementFactory | None = ...) -> None: ... + # comment_factory can take None because passing None to Comment is not an error + def __init__( + self, + element_factory: _ElementFactory | None = ..., + *, + comment_factory: Callable[[str | None], Element] | None = ..., + pi_factory: Callable[[str, str | None], Element] | None = ..., + insert_comments: bool = ..., + insert_pis: bool = ..., + ) -> None: ... + insert_comments: bool + insert_pis: bool def close(self) -> Element: ... - def data(self, __data: str) -> None: ... + def data(self, data: str, /) -> None: ... # tag and attrs are passed to the element_factory, so they could be anything # depending on what the particular factory supports. - def start(self, __tag: Any, __attrs: dict[Any, Any]) -> Element: ... - def end(self, __tag: str) -> Element: ... - if sys.version_info >= (3, 8): - # These two methods have pos-only parameters in the C implementation - def comment(self, __text: str | None) -> Element: ... - def pi(self, __target: str, __text: str | None = None) -> Element: ... + def start(self, tag: Any, attrs: dict[Any, Any], /) -> Element: ... + def end(self, tag: str, /) -> Element: ... + # These two methods have pos-only parameters in the C implementation + def comment(self, text: str | None, /) -> Element: ... + def pi(self, target: str, text: str | None = None, /) -> Element: ... -if sys.version_info >= (3, 8): - class C14NWriterTarget: - def __init__( - self, - write: Callable[[str], object], - *, - with_comments: bool = False, - strip_text: bool = False, - rewrite_prefixes: bool = False, - qname_aware_tags: Iterable[str] | None = None, - qname_aware_attrs: Iterable[str] | None = None, - exclude_attrs: Iterable[str] | None = None, - exclude_tags: Iterable[str] | None = None, - ) -> None: ... - def data(self, data: str) -> None: ... - def start_ns(self, prefix: str, uri: str) -> None: ... - def start(self, tag: str, attrs: Mapping[str, str]) -> None: ... - def end(self, tag: str) -> None: ... - def comment(self, text: str) -> None: ... - def pi(self, target: str, data: str) -> None: ... +class C14NWriterTarget: + def __init__( + self, + write: Callable[[str], object], + *, + with_comments: bool = False, + strip_text: bool = False, + rewrite_prefixes: bool = False, + qname_aware_tags: Iterable[str] | None = None, + qname_aware_attrs: Iterable[str] | None = None, + exclude_attrs: Iterable[str] | None = None, + exclude_tags: Iterable[str] | None = None, + ) -> None: ... + def data(self, data: str) -> None: ... + def start_ns(self, prefix: str, uri: str) -> None: ... + def start(self, tag: str, attrs: Mapping[str, str]) -> None: ... + def end(self, tag: str) -> None: ... + def comment(self, text: str) -> None: ... + def pi(self, target: str, data: str) -> None: ... class XMLParser: parser: Any @@ -350,11 +321,7 @@ class XMLParser: # TODO-what is entity used for??? entity: Any version: str - if sys.version_info >= (3, 8): - def __init__(self, *, target: Any = ..., encoding: str | None = ...) -> None: ... - else: - def __init__(self, html: int = ..., target: Any = ..., encoding: str | None = ...) -> None: ... - def doctype(self, __name: str, __pubid: str, __system: str) -> None: ... - + def __init__(self, *, target: Any = ..., encoding: str | None = ...) -> None: ... def close(self) -> Any: ... - def feed(self, __data: str | ReadableBuffer) -> None: ... + def feed(self, data: str | ReadableBuffer, /) -> None: ... + def flush(self) -> None: ... diff --git a/mypy/typeshed/stdlib/xml/sax/__init__.pyi b/mypy/typeshed/stdlib/xml/sax/__init__.pyi index ca981a00d25f..a2eecc5a7864 100644 --- a/mypy/typeshed/stdlib/xml/sax/__init__.pyi +++ b/mypy/typeshed/stdlib/xml/sax/__init__.pyi @@ -1,47 +1,25 @@ -import sys from _typeshed import ReadableBuffer, StrPath, SupportsRead, _T_co from collections.abc import Iterable -from typing import Any, NoReturn, Protocol +from typing import Protocol +from typing_extensions import TypeAlias +from xml.sax._exceptions import ( + SAXException as SAXException, + SAXNotRecognizedException as SAXNotRecognizedException, + SAXNotSupportedException as SAXNotSupportedException, + SAXParseException as SAXParseException, + SAXReaderNotAvailable as SAXReaderNotAvailable, +) from xml.sax.handler import ContentHandler as ContentHandler, ErrorHandler as ErrorHandler -from xml.sax.xmlreader import Locator, XMLReader +from xml.sax.xmlreader import XMLReader class _SupportsReadClose(SupportsRead[_T_co], Protocol[_T_co]): def close(self) -> None: ... -class SAXException(Exception): - def __init__(self, msg: str, exception: Exception | None = None) -> None: ... - def getMessage(self) -> str: ... - def getException(self) -> Exception: ... - def __getitem__(self, ix: Any) -> NoReturn: ... - -class SAXParseException(SAXException): - def __init__(self, msg: str, exception: Exception | None, locator: Locator) -> None: ... - def getColumnNumber(self) -> int: ... - def getLineNumber(self) -> int: ... - def getPublicId(self): ... - def getSystemId(self): ... - -class SAXNotRecognizedException(SAXException): ... -class SAXNotSupportedException(SAXException): ... -class SAXReaderNotAvailable(SAXNotSupportedException): ... +_Source: TypeAlias = StrPath | _SupportsReadClose[bytes] | _SupportsReadClose[str] default_parser_list: list[str] -if sys.version_info >= (3, 8): - def make_parser(parser_list: Iterable[str] = ...) -> XMLReader: ... - def parse( - source: StrPath | _SupportsReadClose[bytes] | _SupportsReadClose[str], - handler: ContentHandler, - errorHandler: ErrorHandler = ..., - ) -> None: ... - -else: - def make_parser(parser_list: list[str] = ...) -> XMLReader: ... - def parse( - source: str | _SupportsReadClose[bytes] | _SupportsReadClose[str], - handler: ContentHandler, - errorHandler: ErrorHandler = ..., - ) -> None: ... - +def make_parser(parser_list: Iterable[str] = ()) -> XMLReader: ... +def parse(source: _Source, handler: ContentHandler, errorHandler: ErrorHandler = ...) -> None: ... def parseString(string: ReadableBuffer | str, handler: ContentHandler, errorHandler: ErrorHandler | None = ...) -> None: ... def _create_parser(parser_name: str) -> XMLReader: ... diff --git a/mypy/typeshed/stdlib/xml/sax/_exceptions.pyi b/mypy/typeshed/stdlib/xml/sax/_exceptions.pyi new file mode 100644 index 000000000000..8a437a971f13 --- /dev/null +++ b/mypy/typeshed/stdlib/xml/sax/_exceptions.pyi @@ -0,0 +1,19 @@ +from typing import NoReturn +from xml.sax.xmlreader import Locator + +class SAXException(Exception): + def __init__(self, msg: str, exception: Exception | None = None) -> None: ... + def getMessage(self) -> str: ... + def getException(self) -> Exception: ... + def __getitem__(self, ix: object) -> NoReturn: ... + +class SAXParseException(SAXException): + def __init__(self, msg: str, exception: Exception | None, locator: Locator) -> None: ... + def getColumnNumber(self) -> int: ... + def getLineNumber(self) -> int: ... + def getPublicId(self): ... + def getSystemId(self): ... + +class SAXNotRecognizedException(SAXException): ... +class SAXNotSupportedException(SAXException): ... +class SAXReaderNotAvailable(SAXNotSupportedException): ... diff --git a/mypy/typeshed/stdlib/xml/sax/handler.pyi b/mypy/typeshed/stdlib/xml/sax/handler.pyi index 63b725bd6da6..30fe31d51374 100644 --- a/mypy/typeshed/stdlib/xml/sax/handler.pyi +++ b/mypy/typeshed/stdlib/xml/sax/handler.pyi @@ -1,5 +1,6 @@ import sys from typing import NoReturn +from xml.sax import xmlreader version: str @@ -9,19 +10,19 @@ class ErrorHandler: def warning(self, exception: BaseException) -> None: ... class ContentHandler: - def setDocumentLocator(self, locator): ... - def startDocument(self): ... - def endDocument(self): ... - def startPrefixMapping(self, prefix, uri): ... - def endPrefixMapping(self, prefix): ... - def startElement(self, name, attrs): ... - def endElement(self, name): ... - def startElementNS(self, name, qname, attrs): ... - def endElementNS(self, name, qname): ... - def characters(self, content): ... - def ignorableWhitespace(self, whitespace): ... - def processingInstruction(self, target, data): ... - def skippedEntity(self, name): ... + def setDocumentLocator(self, locator: xmlreader.Locator) -> None: ... + def startDocument(self) -> None: ... + def endDocument(self) -> None: ... + def startPrefixMapping(self, prefix: str | None, uri: str) -> None: ... + def endPrefixMapping(self, prefix) -> None: ... + def startElement(self, name: str, attrs: xmlreader.AttributesImpl) -> None: ... + def endElement(self, name: str) -> None: ... + def startElementNS(self, name: tuple[str, str], qname: str, attrs: xmlreader.AttributesNSImpl) -> None: ... + def endElementNS(self, name: tuple[str, str], qname: str) -> None: ... + def characters(self, content: str) -> None: ... + def ignorableWhitespace(self, whitespace: str) -> None: ... + def processingInstruction(self, target: str, data: str) -> None: ... + def skippedEntity(self, name: str) -> None: ... class DTDHandler: def notationDecl(self, name, publicId, systemId): ... diff --git a/mypy/typeshed/stdlib/xml/sax/saxutils.pyi b/mypy/typeshed/stdlib/xml/sax/saxutils.pyi index 67a06d2fcda2..528f35963947 100644 --- a/mypy/typeshed/stdlib/xml/sax/saxutils.pyi +++ b/mypy/typeshed/stdlib/xml/sax/saxutils.pyi @@ -2,59 +2,59 @@ from _typeshed import SupportsWrite from codecs import StreamReaderWriter, StreamWriter from collections.abc import Mapping from io import RawIOBase, TextIOBase -from xml.sax import handler, xmlreader +from xml.sax import _Source, handler, xmlreader -def escape(data: str, entities: Mapping[str, str] = ...) -> str: ... -def unescape(data: str, entities: Mapping[str, str] = ...) -> str: ... -def quoteattr(data: str, entities: Mapping[str, str] = ...) -> str: ... +def escape(data: str, entities: Mapping[str, str] = {}) -> str: ... +def unescape(data: str, entities: Mapping[str, str] = {}) -> str: ... +def quoteattr(data: str, entities: Mapping[str, str] = {}) -> str: ... class XMLGenerator(handler.ContentHandler): def __init__( self, - out: TextIOBase | RawIOBase | StreamWriter | StreamReaderWriter | SupportsWrite[str] | None = None, + out: TextIOBase | RawIOBase | StreamWriter | StreamReaderWriter | SupportsWrite[bytes] | None = None, encoding: str = "iso-8859-1", short_empty_elements: bool = False, ) -> None: ... - def startDocument(self): ... - def endDocument(self): ... - def startPrefixMapping(self, prefix, uri): ... - def endPrefixMapping(self, prefix): ... - def startElement(self, name, attrs): ... - def endElement(self, name): ... - def startElementNS(self, name, qname, attrs): ... - def endElementNS(self, name, qname): ... - def characters(self, content): ... - def ignorableWhitespace(self, content): ... - def processingInstruction(self, target, data): ... + def startDocument(self) -> None: ... + def endDocument(self) -> None: ... + def startPrefixMapping(self, prefix: str | None, uri: str) -> None: ... + def endPrefixMapping(self, prefix: str | None) -> None: ... + def startElement(self, name: str, attrs: xmlreader.AttributesImpl) -> None: ... + def endElement(self, name: str) -> None: ... + def startElementNS(self, name: tuple[str, str], qname: str, attrs: xmlreader.AttributesNSImpl) -> None: ... + def endElementNS(self, name: tuple[str, str], qname: str) -> None: ... + def characters(self, content: str) -> None: ... + def ignorableWhitespace(self, content: str) -> None: ... + def processingInstruction(self, target: str, data: str) -> None: ... class XMLFilterBase(xmlreader.XMLReader): def __init__(self, parent: xmlreader.XMLReader | None = None) -> None: ... def error(self, exception): ... def fatalError(self, exception): ... def warning(self, exception): ... - def setDocumentLocator(self, locator): ... - def startDocument(self): ... - def endDocument(self): ... - def startPrefixMapping(self, prefix, uri): ... - def endPrefixMapping(self, prefix): ... - def startElement(self, name, attrs): ... - def endElement(self, name): ... - def startElementNS(self, name, qname, attrs): ... - def endElementNS(self, name, qname): ... - def characters(self, content): ... - def ignorableWhitespace(self, chars): ... - def processingInstruction(self, target, data): ... - def skippedEntity(self, name): ... + def setDocumentLocator(self, locator: xmlreader.Locator) -> None: ... + def startDocument(self) -> None: ... + def endDocument(self) -> None: ... + def startPrefixMapping(self, prefix: str | None, uri: str) -> None: ... + def endPrefixMapping(self, prefix: str | None) -> None: ... + def startElement(self, name: str, attrs: xmlreader.AttributesImpl) -> None: ... + def endElement(self, name: str) -> None: ... + def startElementNS(self, name: tuple[str, str], qname: str, attrs: xmlreader.AttributesNSImpl) -> None: ... + def endElementNS(self, name: tuple[str, str], qname: str) -> None: ... + def characters(self, content: str) -> None: ... + def ignorableWhitespace(self, chars: str) -> None: ... + def processingInstruction(self, target: str, data: str) -> None: ... + def skippedEntity(self, name: str) -> None: ... def notationDecl(self, name, publicId, systemId): ... def unparsedEntityDecl(self, name, publicId, systemId, ndata): ... def resolveEntity(self, publicId, systemId): ... - def parse(self, source): ... + def parse(self, source: _Source) -> None: ... def setLocale(self, locale): ... - def getFeature(self, name): ... - def setFeature(self, name, state): ... - def getProperty(self, name): ... - def setProperty(self, name, value): ... - def getParent(self): ... - def setParent(self, parent): ... + def getFeature(self, name: str) -> object: ... + def setFeature(self, name: str, state: object) -> None: ... + def getProperty(self, name: str) -> object: ... + def setProperty(self, name: str, value: object) -> None: ... + def getParent(self) -> xmlreader.XMLReader: ... + def setParent(self, parent: xmlreader.XMLReader) -> None: ... def prepare_input_source(source, base=""): ... diff --git a/mypy/typeshed/stdlib/xml/sax/xmlreader.pyi b/mypy/typeshed/stdlib/xml/sax/xmlreader.pyi index 0bf167b04a37..2ccbc95bbef0 100644 --- a/mypy/typeshed/stdlib/xml/sax/xmlreader.pyi +++ b/mypy/typeshed/stdlib/xml/sax/xmlreader.pyi @@ -1,20 +1,23 @@ from collections.abc import Mapping +from typing import overload +from typing_extensions import Self, TypeAlias +from xml.sax.handler import ContentHandler, DTDHandler, EntityResolver, ErrorHandler class XMLReader: def parse(self, source): ... - def getContentHandler(self): ... - def setContentHandler(self, handler): ... - def getDTDHandler(self): ... - def setDTDHandler(self, handler): ... - def getEntityResolver(self): ... - def setEntityResolver(self, resolver): ... - def getErrorHandler(self): ... - def setErrorHandler(self, handler): ... + def getContentHandler(self) -> ContentHandler: ... + def setContentHandler(self, handler: ContentHandler) -> None: ... + def getDTDHandler(self) -> DTDHandler: ... + def setDTDHandler(self, handler: DTDHandler) -> None: ... + def getEntityResolver(self) -> EntityResolver: ... + def setEntityResolver(self, resolver: EntityResolver) -> None: ... + def getErrorHandler(self) -> ErrorHandler: ... + def setErrorHandler(self, handler: ErrorHandler) -> None: ... def setLocale(self, locale): ... - def getFeature(self, name): ... - def setFeature(self, name, state): ... - def getProperty(self, name): ... - def setProperty(self, name, value): ... + def getFeature(self, name: str) -> object: ... + def setFeature(self, name: str, state: object) -> None: ... + def getProperty(self, name: str) -> object: ... + def setProperty(self, name: str, value: object) -> None: ... class IncrementalParser(XMLReader): def __init__(self, bufsize: int = 65536) -> None: ... @@ -45,27 +48,40 @@ class InputSource: class AttributesImpl: def __init__(self, attrs: Mapping[str, str]) -> None: ... - def getLength(self): ... - def getType(self, name): ... - def getValue(self, name): ... - def getValueByQName(self, name): ... - def getNameByQName(self, name): ... - def getQNameByName(self, name): ... - def getNames(self): ... - def getQNames(self): ... + def getLength(self) -> int: ... + def getType(self, name: str) -> str: ... + def getValue(self, name: str) -> str: ... + def getValueByQName(self, name: str) -> str: ... + def getNameByQName(self, name: str) -> str: ... + def getQNameByName(self, name: str) -> str: ... + def getNames(self) -> list[str]: ... + def getQNames(self) -> list[str]: ... def __len__(self) -> int: ... - def __getitem__(self, name): ... - def keys(self): ... - def __contains__(self, name): ... - def get(self, name, alternative=None): ... - def copy(self): ... - def items(self): ... - def values(self): ... + def __getitem__(self, name: str) -> str: ... + def keys(self) -> list[str]: ... + def __contains__(self, name: str) -> bool: ... + @overload + def get(self, name: str, alternative: None = None) -> str | None: ... + @overload + def get(self, name: str, alternative: str) -> str: ... + def copy(self) -> Self: ... + def items(self) -> list[tuple[str, str]]: ... + def values(self) -> list[str]: ... + +_NSName: TypeAlias = tuple[str | None, str] class AttributesNSImpl(AttributesImpl): - def __init__(self, attrs: Mapping[tuple[str, str], str], qnames: Mapping[tuple[str, str], str]) -> None: ... - def getValueByQName(self, name): ... - def getNameByQName(self, name): ... - def getQNameByName(self, name): ... - def getQNames(self): ... - def copy(self): ... + def __init__(self, attrs: Mapping[_NSName, str], qnames: Mapping[_NSName, str]) -> None: ... + def getType(self, name: _NSName) -> str: ... # type: ignore[override] + def getValue(self, name: _NSName) -> str: ... # type: ignore[override] + def getNameByQName(self, name: str) -> _NSName: ... # type: ignore[override] + def getQNameByName(self, name: _NSName) -> str: ... # type: ignore[override] + def getNames(self) -> list[_NSName]: ... # type: ignore[override] + def __getitem__(self, name: _NSName) -> str: ... # type: ignore[override] + def keys(self) -> list[_NSName]: ... # type: ignore[override] + def __contains__(self, name: _NSName) -> bool: ... # type: ignore[override] + @overload # type: ignore[override] + def get(self, name: _NSName, alternative: None = None) -> str | None: ... + @overload + def get(self, name: _NSName, alternative: str) -> str: ... + def items(self) -> list[tuple[_NSName, str]]: ... # type: ignore[override] diff --git a/mypy/typeshed/stdlib/xmlrpc/client.pyi b/mypy/typeshed/stdlib/xmlrpc/client.pyi index 7bf701ae716d..2be5f7df2d7d 100644 --- a/mypy/typeshed/stdlib/xmlrpc/client.pyi +++ b/mypy/typeshed/stdlib/xmlrpc/client.pyi @@ -1,14 +1,13 @@ import gzip import http.client -import sys import time -from _typeshed import ReadableBuffer, SupportsRead, SupportsWrite, _BufferWithLen +from _typeshed import ReadableBuffer, SizedBuffer, SupportsRead, SupportsWrite from collections.abc import Callable, Iterable, Mapping from datetime import datetime from io import BytesIO from types import TracebackType -from typing import Any, Protocol, overload -from typing_extensions import Literal, Self, TypeAlias +from typing import Any, Literal, Protocol, overload +from typing_extensions import Self, TypeAlias class _SupportsTimeTuple(Protocol): def timetuple(self) -> time.struct_time: ... @@ -228,45 +227,35 @@ class Transport: _headers: list[tuple[str, str]] _extra_headers: list[tuple[str, str]] - if sys.version_info >= (3, 8): - def __init__( - self, use_datetime: bool = False, use_builtin_types: bool = False, *, headers: Iterable[tuple[str, str]] = ... - ) -> None: ... - else: - def __init__(self, use_datetime: bool = False, use_builtin_types: bool = False) -> None: ... - + def __init__( + self, use_datetime: bool = False, use_builtin_types: bool = False, *, headers: Iterable[tuple[str, str]] = () + ) -> None: ... def request( - self, host: _HostType, handler: str, request_body: _BufferWithLen, verbose: bool = False + self, host: _HostType, handler: str, request_body: SizedBuffer, verbose: bool = False ) -> tuple[_Marshallable, ...]: ... def single_request( - self, host: _HostType, handler: str, request_body: _BufferWithLen, verbose: bool = False + self, host: _HostType, handler: str, request_body: SizedBuffer, verbose: bool = False ) -> tuple[_Marshallable, ...]: ... def getparser(self) -> tuple[ExpatParser, Unmarshaller]: ... def get_host_info(self, host: _HostType) -> tuple[str, list[tuple[str, str]], dict[str, str]]: ... def make_connection(self, host: _HostType) -> http.client.HTTPConnection: ... def close(self) -> None: ... def send_request( - self, host: _HostType, handler: str, request_body: _BufferWithLen, debug: bool + self, host: _HostType, handler: str, request_body: SizedBuffer, debug: bool ) -> http.client.HTTPConnection: ... def send_headers(self, connection: http.client.HTTPConnection, headers: list[tuple[str, str]]) -> None: ... - def send_content(self, connection: http.client.HTTPConnection, request_body: _BufferWithLen) -> None: ... + def send_content(self, connection: http.client.HTTPConnection, request_body: SizedBuffer) -> None: ... def parse_response(self, response: http.client.HTTPResponse) -> tuple[_Marshallable, ...]: ... class SafeTransport(Transport): - if sys.version_info >= (3, 8): - def __init__( - self, - use_datetime: bool = False, - use_builtin_types: bool = False, - *, - headers: Iterable[tuple[str, str]] = ..., - context: Any | None = None, - ) -> None: ... - else: - def __init__( - self, use_datetime: bool = False, use_builtin_types: bool = False, *, context: Any | None = None - ) -> None: ... - + def __init__( + self, + use_datetime: bool = False, + use_builtin_types: bool = False, + *, + headers: Iterable[tuple[str, str]] = (), + context: Any | None = None, + ) -> None: ... def make_connection(self, host: _HostType) -> http.client.HTTPSConnection: ... class ServerProxy: @@ -277,34 +266,19 @@ class ServerProxy: __verbose: bool __allow_none: bool - if sys.version_info >= (3, 8): - def __init__( - self, - uri: str, - transport: Transport | None = None, - encoding: str | None = None, - verbose: bool = False, - allow_none: bool = False, - use_datetime: bool = False, - use_builtin_types: bool = False, - *, - headers: Iterable[tuple[str, str]] = ..., - context: Any | None = None, - ) -> None: ... - else: - def __init__( - self, - uri: str, - transport: Transport | None = None, - encoding: str | None = None, - verbose: bool = False, - allow_none: bool = False, - use_datetime: bool = False, - use_builtin_types: bool = False, - *, - context: Any | None = None, - ) -> None: ... - + def __init__( + self, + uri: str, + transport: Transport | None = None, + encoding: str | None = None, + verbose: bool = False, + allow_none: bool = False, + use_datetime: bool = False, + use_builtin_types: bool = False, + *, + headers: Iterable[tuple[str, str]] = (), + context: Any | None = None, + ) -> None: ... def __getattr__(self, name: str) -> _Method: ... @overload def __call__(self, attr: Literal["close"]) -> Callable[[], None]: ... diff --git a/mypy/typeshed/stdlib/xmlrpc/server.pyi b/mypy/typeshed/stdlib/xmlrpc/server.pyi index 800c205513c6..8ca3a4d1a33c 100644 --- a/mypy/typeshed/stdlib/xmlrpc/server.pyi +++ b/mypy/typeshed/stdlib/xmlrpc/server.pyi @@ -12,17 +12,17 @@ class _DispatchArity0(Protocol): def __call__(self) -> _Marshallable: ... class _DispatchArity1(Protocol): - def __call__(self, __arg1: _Marshallable) -> _Marshallable: ... + def __call__(self, arg1: _Marshallable, /) -> _Marshallable: ... class _DispatchArity2(Protocol): - def __call__(self, __arg1: _Marshallable, __arg2: _Marshallable) -> _Marshallable: ... + def __call__(self, arg1: _Marshallable, arg2: _Marshallable, /) -> _Marshallable: ... class _DispatchArity3(Protocol): - def __call__(self, __arg1: _Marshallable, __arg2: _Marshallable, __arg3: _Marshallable) -> _Marshallable: ... + def __call__(self, arg1: _Marshallable, arg2: _Marshallable, arg3: _Marshallable, /) -> _Marshallable: ... class _DispatchArity4(Protocol): def __call__( - self, __arg1: _Marshallable, __arg2: _Marshallable, __arg3: _Marshallable, __arg4: _Marshallable + self, arg1: _Marshallable, arg2: _Marshallable, arg3: _Marshallable, arg4: _Marshallable, / ) -> _Marshallable: ... class _DispatchArityN(Protocol): @@ -108,9 +108,9 @@ class ServerHTMLDoc(pydoc.HTMLDoc): # undocumented object: object, name: str, mod: str | None = None, - funcs: Mapping[str, str] = ..., - classes: Mapping[str, str] = ..., - methods: Mapping[str, str] = ..., + funcs: Mapping[str, str] = {}, + classes: Mapping[str, str] = {}, + methods: Mapping[str, str] = {}, cl: type | None = None, ) -> str: ... def docserver(self, server_name: str, package_documentation: str, methods: dict[str, str]) -> str: ... diff --git a/mypy/typeshed/stdlib/xxlimited.pyi b/mypy/typeshed/stdlib/xxlimited.pyi index b2fb72ad2c0b..6bae87a8db2a 100644 --- a/mypy/typeshed/stdlib/xxlimited.pyi +++ b/mypy/typeshed/stdlib/xxlimited.pyi @@ -1,21 +1,22 @@ import sys -from typing import Any -from typing_extensions import final +from typing import Any, final -class Str: ... +class Str(str): ... @final class Xxo: def demo(self) -> None: ... + if sys.version_info >= (3, 11) and sys.platform != "win32": + x_exports: int -def foo(__i: int, __j: int) -> Any: ... +def foo(i: int, j: int, /) -> Any: ... def new() -> Xxo: ... if sys.version_info >= (3, 10): - class Error: ... + class Error(Exception): ... else: - class error: ... + class error(Exception): ... class Null: ... - def roj(__b: Any) -> None: ... + def roj(b: Any, /) -> None: ... diff --git a/mypy/typeshed/stdlib/zipfile.pyi b/mypy/typeshed/stdlib/zipfile/__init__.pyi similarity index 74% rename from mypy/typeshed/stdlib/zipfile.pyi rename to mypy/typeshed/stdlib/zipfile/__init__.pyi index b969d0cf9e6a..b61e07f8b90d 100644 --- a/mypy/typeshed/stdlib/zipfile.pyi +++ b/mypy/typeshed/stdlib/zipfile/__init__.pyi @@ -1,15 +1,17 @@ import io import sys -from _typeshed import StrOrBytesPath, StrPath, _BufferWithLen +from _typeshed import SizedBuffer, StrOrBytesPath, StrPath from collections.abc import Callable, Iterable, Iterator +from io import TextIOWrapper from os import PathLike from types import TracebackType -from typing import IO, Any, Protocol, overload -from typing_extensions import Literal, Self, TypeAlias +from typing import IO, Literal, Protocol, overload +from typing_extensions import Self, TypeAlias __all__ = [ "BadZipFile", "BadZipfile", + "Path", "error", "ZIP_STORED", "ZIP_DEFLATED", @@ -22,13 +24,13 @@ __all__ = [ "LargeZipFile", ] -if sys.version_info >= (3, 8): - __all__ += ["Path"] +# TODO: use TypeAlias for these two when mypy bugs are fixed +# https://github.com/python/mypy/issues/16581 +_DateTuple = tuple[int, int, int, int, int, int] # noqa: Y026 +_ZipFileMode = Literal["r", "w", "x", "a"] # noqa: Y026 -_DateTuple: TypeAlias = tuple[int, int, int, int, int, int] _ReadWriteMode: TypeAlias = Literal["r", "w"] _ReadWriteBinaryMode: TypeAlias = Literal["r", "w", "rb", "wb"] -_ZipFileMode: TypeAlias = Literal["r", "w", "x", "a"] class BadZipFile(Exception): ... @@ -38,16 +40,16 @@ error = BadZipfile class LargeZipFile(Exception): ... class _ZipStream(Protocol): - def read(self, __n: int) -> bytes: ... + def read(self, n: int, /) -> bytes: ... # The following methods are optional: # def seekable(self) -> bool: ... # def tell(self) -> int: ... - # def seek(self, __n: int) -> object: ... + # def seek(self, n: int, /) -> object: ... # Stream shape as required by _EndRecData() and _EndRecData64(). class _SupportsReadSeekTell(Protocol): - def read(self, __n: int = ...) -> bytes: ... - def seek(self, __cookie: int, __whence: int) -> object: ... + def read(self, n: int = ..., /) -> bytes: ... + def seek(self, cookie: int, whence: int, /) -> object: ... def tell(self) -> int: ... class _ClosableZipStream(_ZipStream, Protocol): @@ -90,7 +92,7 @@ class ZipExtFile(io.BufferedIOBase): def seek(self, offset: int, whence: int = 0) -> int: ... class _Writer(Protocol): - def write(self, __s: str) -> object: ... + def write(self, s: str, /) -> object: ... class ZipFile: filename: str | None @@ -129,7 +131,7 @@ class ZipFile: strict_timestamps: bool = True, metadata_encoding: None = None, ) -> None: ... - elif sys.version_info >= (3, 8): + else: def __init__( self, file: StrPath | IO[bytes], @@ -140,15 +142,6 @@ class ZipFile: *, strict_timestamps: bool = True, ) -> None: ... - else: - def __init__( - self, - file: StrPath | IO[bytes], - mode: _ZipFileMode = "r", - compression: int = 0, - allowZip64: bool = True, - compresslevel: int | None = None, - ) -> None: ... def __enter__(self) -> Self: ... def __exit__( @@ -179,13 +172,15 @@ class ZipFile: def writestr( self, zinfo_or_arcname: str | ZipInfo, - data: _BufferWithLen | str, + data: SizedBuffer | str, compress_type: int | None = None, compresslevel: int | None = None, ) -> None: ... if sys.version_info >= (3, 11): def mkdir(self, zinfo_or_directory_name: str | ZipInfo, mode: int = 0o777) -> None: ... + def __del__(self) -> None: ... + class PyZipFile(ZipFile): def __init__( self, file: str | IO[bytes], mode: _ZipFileMode = "r", compression: int = 0, allowZip64: bool = True, optimize: int = -1 @@ -211,22 +206,28 @@ class ZipInfo: compress_size: int file_size: int orig_filename: str # undocumented - def __init__(self, filename: str = "NoName", date_time: _DateTuple = ...) -> None: ... - if sys.version_info >= (3, 8): - @classmethod - def from_file(cls, filename: StrPath, arcname: StrPath | None = None, *, strict_timestamps: bool = True) -> Self: ... - else: - @classmethod - def from_file(cls, filename: StrPath, arcname: StrPath | None = None) -> Self: ... - + def __init__(self, filename: str = "NoName", date_time: _DateTuple = (1980, 1, 1, 0, 0, 0)) -> None: ... + @classmethod + def from_file(cls, filename: StrPath, arcname: StrPath | None = None, *, strict_timestamps: bool = True) -> Self: ... def is_dir(self) -> bool: ... def FileHeader(self, zip64: bool | None = None) -> bytes: ... -class _PathOpenProtocol(Protocol): - def __call__(self, mode: _ReadWriteMode = ..., pwd: bytes | None = ..., *, force_zip64: bool = ...) -> IO[bytes]: ... +if sys.version_info >= (3, 12): + from zipfile._path import CompleteDirs as CompleteDirs, Path as Path + +else: + class CompleteDirs(ZipFile): + def resolve_dir(self, name: str) -> str: ... + @overload + @classmethod + def make(cls, source: ZipFile) -> CompleteDirs: ... + @overload + @classmethod + def make(cls, source: StrPath | IO[bytes]) -> Self: ... -if sys.version_info >= (3, 8): class Path: + root: CompleteDirs + def __init__(self, root: ZipFile | StrPath | IO[bytes], at: str = "") -> None: ... @property def name(self) -> str: ... @property @@ -242,16 +243,31 @@ if sys.version_info >= (3, 8): @property def stem(self) -> str: ... - def __init__(self, root: ZipFile | StrPath | IO[bytes], at: str = "") -> None: ... if sys.version_info >= (3, 9): + @overload + def open( + self, + mode: Literal["r", "w"] = "r", + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + line_buffering: bool = ..., + write_through: bool = ..., + *, + pwd: bytes | None = None, + ) -> TextIOWrapper: ... + @overload + def open(self, mode: Literal["rb", "wb"], *, pwd: bytes | None = None) -> IO[bytes]: ... + else: def open( - self, mode: _ReadWriteBinaryMode = "r", *args: Any, pwd: bytes | None = None, **kwargs: Any + self, mode: _ReadWriteBinaryMode = "r", pwd: bytes | None = None, *, force_zip64: bool = False ) -> IO[bytes]: ... + + if sys.version_info >= (3, 10): + def iterdir(self) -> Iterator[Self]: ... else: - @property - def open(self) -> _PathOpenProtocol: ... + def iterdir(self) -> Iterator[Path]: ... - def iterdir(self) -> Iterator[Path]: ... def is_dir(self) -> bool: ... def is_file(self) -> bool: ... def exists(self) -> bool: ... @@ -268,6 +284,14 @@ if sys.version_info >= (3, 8): def joinpath(self, *other: StrPath) -> Path: ... else: def joinpath(self, add: StrPath) -> Path: ... # undocumented + if sys.version_info >= (3, 12): + def glob(self, pattern: str) -> Iterator[Self]: ... + def rglob(self, pattern: str) -> Iterator[Self]: ... + def is_symlink(self) -> Literal[False]: ... + def relative_to(self, other: Path, *extra: StrPath) -> str: ... + def match(self, path_pattern: str) -> bool: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... def __truediv__(self, add: StrPath) -> Path: ... diff --git a/mypy/typeshed/stdlib/zipfile/_path.pyi b/mypy/typeshed/stdlib/zipfile/_path.pyi new file mode 100644 index 000000000000..0398824e1fd2 --- /dev/null +++ b/mypy/typeshed/stdlib/zipfile/_path.pyi @@ -0,0 +1,95 @@ +import sys +from _typeshed import StrPath +from collections.abc import Iterator, Sequence +from io import TextIOWrapper +from os import PathLike +from typing import IO, Literal, overload +from typing_extensions import Self, TypeAlias +from zipfile import ZipFile + +_ReadWriteBinaryMode: TypeAlias = Literal["r", "w", "rb", "wb"] + +if sys.version_info >= (3, 12): + class InitializedState: + def __init__(self, *args: object, **kwargs: object) -> None: ... + def __getstate__(self) -> tuple[list[object], dict[object, object]]: ... + def __setstate__(self, state: Sequence[tuple[list[object], dict[object, object]]]) -> None: ... + + class CompleteDirs(InitializedState, ZipFile): + def resolve_dir(self, name: str) -> str: ... + @overload + @classmethod + def make(cls, source: ZipFile) -> CompleteDirs: ... + @overload + @classmethod + def make(cls, source: StrPath | IO[bytes]) -> Self: ... + + class Path: + root: CompleteDirs + def __init__(self, root: ZipFile | StrPath | IO[bytes], at: str = "") -> None: ... + @property + def name(self) -> str: ... + @property + def parent(self) -> PathLike[str]: ... # undocumented + if sys.version_info >= (3, 10): + @property + def filename(self) -> PathLike[str]: ... # undocumented + if sys.version_info >= (3, 11): + @property + def suffix(self) -> str: ... + @property + def suffixes(self) -> list[str]: ... + @property + def stem(self) -> str: ... + + if sys.version_info >= (3, 9): + @overload + def open( + self, + mode: Literal["r", "w"] = "r", + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + line_buffering: bool = ..., + write_through: bool = ..., + *, + pwd: bytes | None = None, + ) -> TextIOWrapper: ... + @overload + def open(self, mode: Literal["rb", "wb"], *, pwd: bytes | None = None) -> IO[bytes]: ... + else: + def open( + self, mode: _ReadWriteBinaryMode = "r", pwd: bytes | None = None, *, force_zip64: bool = False + ) -> IO[bytes]: ... + + if sys.version_info >= (3, 10): + def iterdir(self) -> Iterator[Self]: ... + else: + def iterdir(self) -> Iterator[Path]: ... + + def is_dir(self) -> bool: ... + def is_file(self) -> bool: ... + def exists(self) -> bool: ... + def read_text( + self, + encoding: str | None = ..., + errors: str | None = ..., + newline: str | None = ..., + line_buffering: bool = ..., + write_through: bool = ..., + ) -> str: ... + def read_bytes(self) -> bytes: ... + if sys.version_info >= (3, 10): + def joinpath(self, *other: StrPath) -> Path: ... + else: + def joinpath(self, add: StrPath) -> Path: ... # undocumented + if sys.version_info >= (3, 12): + def glob(self, pattern: str) -> Iterator[Self]: ... + def rglob(self, pattern: str) -> Iterator[Self]: ... + def is_symlink(self) -> Literal[False]: ... + def relative_to(self, other: Path, *extra: StrPath) -> str: ... + def match(self, path_pattern: str) -> bool: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + + def __truediv__(self, add: StrPath) -> Path: ... diff --git a/mypy/typeshed/stdlib/zipimport.pyi b/mypy/typeshed/stdlib/zipimport.pyi index ee97faace379..158d573cac74 100644 --- a/mypy/typeshed/stdlib/zipimport.pyi +++ b/mypy/typeshed/stdlib/zipimport.pyi @@ -4,8 +4,7 @@ from importlib.abc import ResourceReader from importlib.machinery import ModuleSpec from types import CodeType, ModuleType -if sys.version_info >= (3, 8): - __all__ = ["ZipImportError", "zipimporter"] +__all__ = ["ZipImportError", "zipimporter"] class ZipImportError(ImportError): ... @@ -17,8 +16,10 @@ class zipimporter: else: def __init__(self, path: StrOrBytesPath) -> None: ... - def find_loader(self, fullname: str, path: str | None = None) -> tuple[zipimporter | None, list[str]]: ... # undocumented - def find_module(self, fullname: str, path: str | None = None) -> zipimporter | None: ... + if sys.version_info < (3, 12): + def find_loader(self, fullname: str, path: str | None = None) -> tuple[zipimporter | None, list[str]]: ... # undocumented + def find_module(self, fullname: str, path: str | None = None) -> zipimporter | None: ... + def get_code(self, fullname: str) -> CodeType: ... def get_data(self, pathname: str) -> bytes: ... def get_filename(self, fullname: str) -> str: ... diff --git a/mypy/typeshed/stdlib/zlib.pyi b/mypy/typeshed/stdlib/zlib.pyi index c3419af0de3f..234770172d40 100644 --- a/mypy/typeshed/stdlib/zlib.pyi +++ b/mypy/typeshed/stdlib/zlib.pyi @@ -1,6 +1,6 @@ import sys from _typeshed import ReadableBuffer -from typing_extensions import Literal +from typing import Literal DEFLATED: Literal[8] DEF_MEM_LEVEL: int # can change @@ -40,17 +40,17 @@ class _Decompress: def flush(self, length: int = ...) -> bytes: ... def copy(self) -> _Decompress: ... -def adler32(__data: ReadableBuffer, __value: int = 1) -> int: ... +def adler32(data: ReadableBuffer, value: int = 1, /) -> int: ... if sys.version_info >= (3, 11): - def compress(__data: ReadableBuffer, level: int = -1, wbits: int = 15) -> bytes: ... + def compress(data: ReadableBuffer, /, level: int = -1, wbits: int = 15) -> bytes: ... else: - def compress(__data: ReadableBuffer, level: int = -1) -> bytes: ... + def compress(data: ReadableBuffer, /, level: int = -1) -> bytes: ... def compressobj( level: int = -1, method: int = 8, wbits: int = 15, memLevel: int = 8, strategy: int = 0, zdict: ReadableBuffer | None = None ) -> _Compress: ... -def crc32(__data: ReadableBuffer, __value: int = 0) -> int: ... -def decompress(__data: ReadableBuffer, wbits: int = 15, bufsize: int = 16384) -> bytes: ... +def crc32(data: ReadableBuffer, value: int = 0, /) -> int: ... +def decompress(data: ReadableBuffer, /, wbits: int = 15, bufsize: int = 16384) -> bytes: ... def decompressobj(wbits: int = 15, zdict: ReadableBuffer = b"") -> _Decompress: ... diff --git a/mypy/typeshed/stdlib/zoneinfo/__init__.pyi b/mypy/typeshed/stdlib/zoneinfo/__init__.pyi index fe994be3e8ff..77930ac79dd5 100644 --- a/mypy/typeshed/stdlib/zoneinfo/__init__.pyi +++ b/mypy/typeshed/stdlib/zoneinfo/__init__.pyi @@ -7,8 +7,8 @@ from typing_extensions import Self __all__ = ["ZoneInfo", "reset_tzpath", "available_timezones", "TZPATH", "ZoneInfoNotFoundError", "InvalidTZPathWarning"] class _IOBytes(Protocol): - def read(self, __size: int) -> bytes: ... - def seek(self, __size: int, __whence: int = ...) -> Any: ... + def read(self, size: int, /) -> bytes: ... + def seek(self, size: int, whence: int = ..., /) -> Any: ... class ZoneInfo(tzinfo): @property @@ -17,12 +17,12 @@ class ZoneInfo(tzinfo): @classmethod def no_cache(cls, key: str) -> Self: ... @classmethod - def from_file(cls, __fobj: _IOBytes, key: str | None = ...) -> Self: ... + def from_file(cls, fobj: _IOBytes, /, key: str | None = None) -> Self: ... @classmethod - def clear_cache(cls, *, only_keys: Iterable[str] | None = ...) -> None: ... - def tzname(self, __dt: datetime | None) -> str | None: ... - def utcoffset(self, __dt: datetime | None) -> timedelta | None: ... - def dst(self, __dt: datetime | None) -> timedelta | None: ... + def clear_cache(cls, *, only_keys: Iterable[str] | None = None) -> None: ... + def tzname(self, dt: datetime | None, /) -> str | None: ... + def utcoffset(self, dt: datetime | None, /) -> timedelta | None: ... + def dst(self, dt: datetime | None, /) -> timedelta | None: ... # Note: Both here and in clear_cache, the types allow the use of `str` where # a sequence of strings is required. This should be remedied if a solution @@ -30,7 +30,7 @@ class ZoneInfo(tzinfo): def reset_tzpath(to: Sequence[StrPath] | None = None) -> None: ... def available_timezones() -> set[str]: ... -TZPATH: Sequence[str] +TZPATH: tuple[str, ...] class ZoneInfoNotFoundError(KeyError): ... class InvalidTZPathWarning(RuntimeWarning): ... diff --git a/mypy/typeshed/stubs/mypy-extensions/mypy_extensions.pyi b/mypy/typeshed/stubs/mypy-extensions/mypy_extensions.pyi index 40e24645fb77..b6358a0022f3 100644 --- a/mypy/typeshed/stubs/mypy-extensions/mypy_extensions.pyi +++ b/mypy/typeshed/stubs/mypy-extensions/mypy_extensions.pyi @@ -146,3 +146,73 @@ class i32: def __ge__(self, x: i32) -> bool: ... def __gt__(self, x: i32) -> bool: ... def __index__(self) -> int: ... + +class i16: + @overload + def __new__(cls, __x: str | ReadableBuffer | SupportsInt | SupportsIndex | SupportsTrunc = ...) -> i16: ... + @overload + def __new__(cls, __x: str | bytes | bytearray, base: SupportsIndex) -> i16: ... + + def __add__(self, x: i16) -> i16: ... + def __radd__(self, x: i16) -> i16: ... + def __sub__(self, x: i16) -> i16: ... + def __rsub__(self, x: i16) -> i16: ... + def __mul__(self, x: i16) -> i16: ... + def __rmul__(self, x: i16) -> i16: ... + def __floordiv__(self, x: i16) -> i16: ... + def __rfloordiv__(self, x: i16) -> i16: ... + def __mod__(self, x: i16) -> i16: ... + def __rmod__(self, x: i16) -> i16: ... + def __and__(self, x: i16) -> i16: ... + def __rand__(self, x: i16) -> i16: ... + def __or__(self, x: i16) -> i16: ... + def __ror__(self, x: i16) -> i16: ... + def __xor__(self, x: i16) -> i16: ... + def __rxor__(self, x: i16) -> i16: ... + def __lshift__(self, x: i16) -> i16: ... + def __rlshift__(self, x: i16) -> i16: ... + def __rshift__(self, x: i16) -> i16: ... + def __rrshift__(self, x: i16) -> i16: ... + def __neg__(self) -> i16: ... + def __invert__(self) -> i16: ... + def __pos__(self) -> i16: ... + def __lt__(self, x: i16) -> bool: ... + def __le__(self, x: i16) -> bool: ... + def __ge__(self, x: i16) -> bool: ... + def __gt__(self, x: i16) -> bool: ... + def __index__(self) -> int: ... + +class u8: + @overload + def __new__(cls, __x: str | ReadableBuffer | SupportsInt | SupportsIndex | SupportsTrunc = ...) -> u8: ... + @overload + def __new__(cls, __x: str | bytes | bytearray, base: SupportsIndex) -> u8: ... + + def __add__(self, x: u8) -> u8: ... + def __radd__(self, x: u8) -> u8: ... + def __sub__(self, x: u8) -> u8: ... + def __rsub__(self, x: u8) -> u8: ... + def __mul__(self, x: u8) -> u8: ... + def __rmul__(self, x: u8) -> u8: ... + def __floordiv__(self, x: u8) -> u8: ... + def __rfloordiv__(self, x: u8) -> u8: ... + def __mod__(self, x: u8) -> u8: ... + def __rmod__(self, x: u8) -> u8: ... + def __and__(self, x: u8) -> u8: ... + def __rand__(self, x: u8) -> u8: ... + def __or__(self, x: u8) -> u8: ... + def __ror__(self, x: u8) -> u8: ... + def __xor__(self, x: u8) -> u8: ... + def __rxor__(self, x: u8) -> u8: ... + def __lshift__(self, x: u8) -> u8: ... + def __rlshift__(self, x: u8) -> u8: ... + def __rshift__(self, x: u8) -> u8: ... + def __rrshift__(self, x: u8) -> u8: ... + def __neg__(self) -> u8: ... + def __invert__(self) -> u8: ... + def __pos__(self) -> u8: ... + def __lt__(self, x: u8) -> bool: ... + def __le__(self, x: u8) -> bool: ... + def __ge__(self, x: u8) -> bool: ... + def __gt__(self, x: u8) -> bool: ... + def __index__(self) -> int: ... diff --git a/mypy/typestate.py b/mypy/typestate.py index 9cbad17aa7bd..0082c5564705 100644 --- a/mypy/typestate.py +++ b/mypy/typestate.py @@ -5,12 +5,15 @@ from __future__ import annotations -from typing import Dict, Set, Tuple -from typing_extensions import Final, TypeAlias as _TypeAlias +from typing import Dict, Final, Set, Tuple +from typing_extensions import TypeAlias as _TypeAlias -from mypy.nodes import TypeInfo +from mypy.nodes import VARIANCE_NOT_READY, TypeInfo from mypy.server.trigger import make_trigger -from mypy.types import Instance, Type, TypeVarId, get_proper_type +from mypy.types import Instance, Type, TypeVarId, TypeVarType, get_proper_type + +MAX_NEGATIVE_CACHE_TYPES: Final = 1000 +MAX_NEGATIVE_CACHE_ENTRIES: Final = 10000 # Represents that the 'left' instance is a subtype of the 'right' instance SubtypeRelationship: _TypeAlias = Tuple[Instance, Instance] @@ -42,6 +45,9 @@ class TypeState: # We need the caches, since subtype checks for structural types are very slow. _subtype_caches: Final[SubtypeCache] + # Same as above but for negative subtyping results. + _negative_subtype_caches: Final[SubtypeCache] + # This contains protocol dependencies generated after running a full build, # or after an update. These dependencies are special because: # * They are a global property of the program; i.e. some dependencies for imported @@ -87,6 +93,9 @@ class TypeState: inferring: Final[list[tuple[Type, Type]]] # Whether to use joins or unions when solving constraints, see checkexpr.py for details. infer_unions: bool + # Whether to use new type inference algorithm that can infer polymorphic types. + # This is temporary and will be removed soon when new algorithm is more polished. + infer_polymorphic: bool # N.B: We do all of the accesses to these properties through # TypeState, instead of making these classmethods and accessing @@ -95,6 +104,7 @@ class TypeState: def __init__(self) -> None: self._subtype_caches = {} + self._negative_subtype_caches = {} self.proto_deps = {} self._attempted_protocols = {} self._checked_against_members = {} @@ -103,9 +113,10 @@ def __init__(self) -> None: self._assuming_proper = [] self.inferring = [] self.infer_unions = False + self.infer_polymorphic = False def is_assumed_subtype(self, left: Type, right: Type) -> bool: - for (l, r) in reversed(self._assuming): + for l, r in reversed(self._assuming): if get_proper_type(l) == get_proper_type(left) and get_proper_type( r ) == get_proper_type(right): @@ -113,7 +124,7 @@ def is_assumed_subtype(self, left: Type, right: Type) -> bool: return False def is_assumed_proper_subtype(self, left: Type, right: Type) -> bool: - for (l, r) in reversed(self._assuming_proper): + for l, r in reversed(self._assuming_proper): if get_proper_type(l) == get_proper_type(left) and get_proper_type( r ) == get_proper_type(right): @@ -128,11 +139,14 @@ def get_assumptions(self, is_proper: bool) -> list[tuple[Type, Type]]: def reset_all_subtype_caches(self) -> None: """Completely reset all known subtype caches.""" self._subtype_caches.clear() + self._negative_subtype_caches.clear() def reset_subtype_caches_for(self, info: TypeInfo) -> None: """Reset subtype caches (if any) for a given supertype TypeInfo.""" if info in self._subtype_caches: self._subtype_caches[info].clear() + if info in self._negative_subtype_caches: + self._negative_subtype_caches[info].clear() def reset_all_subtype_caches_for(self, info: TypeInfo) -> None: """Reset subtype caches (if any) for a given supertype TypeInfo and its MRO.""" @@ -154,6 +168,23 @@ def is_cached_subtype_check(self, kind: SubtypeKind, left: Instance, right: Inst return False return (left, right) in subcache + def is_cached_negative_subtype_check( + self, kind: SubtypeKind, left: Instance, right: Instance + ) -> bool: + if left.last_known_value is not None or right.last_known_value is not None: + # If there is a literal last known value, give up. There + # will be an unbounded number of potential types to cache, + # making caching less effective. + return False + info = right.type + cache = self._negative_subtype_caches.get(info) + if cache is None: + return False + subcache = cache.get(kind) + if subcache is None: + return False + return (left, right) in subcache + def record_subtype_cache_entry( self, kind: SubtypeKind, left: Instance, right: Instance ) -> None: @@ -161,7 +192,28 @@ def record_subtype_cache_entry( # These are unlikely to match, due to the large space of # possible values. Avoid uselessly increasing cache sizes. return - cache = self._subtype_caches.setdefault(right.type, dict()) + if any( + (isinstance(tv, TypeVarType) and tv.variance == VARIANCE_NOT_READY) + for tv in right.type.defn.type_vars + ): + # Variance indeterminate -- don't know the result + return + cache = self._subtype_caches.setdefault(right.type, {}) + cache.setdefault(kind, set()).add((left, right)) + + def record_negative_subtype_cache_entry( + self, kind: SubtypeKind, left: Instance, right: Instance + ) -> None: + if left.last_known_value is not None or right.last_known_value is not None: + # These are unlikely to match, due to the large space of + # possible values. Avoid uselessly increasing cache sizes. + return + if len(self._negative_subtype_caches) > MAX_NEGATIVE_CACHE_TYPES: + self._negative_subtype_caches.clear() + cache = self._negative_subtype_caches.setdefault(right.type, {}) + subcache = cache.setdefault(kind, set()) + if len(subcache) > MAX_NEGATIVE_CACHE_ENTRIES: + subcache.clear() cache.setdefault(kind, set()).add((left, right)) def reset_protocol_deps(self) -> None: @@ -269,7 +321,7 @@ def add_all_protocol_deps(self, deps: dict[str, set[str]]) -> None: def reset_global_state() -> None: """Reset most existing global state. - Currently most of it is in this module. Few exceptions are strict optional status and + Currently most of it is in this module. Few exceptions are strict optional status and functools.lru_cache. """ type_state.reset_all_subtype_caches() diff --git a/mypy/typetraverser.py b/mypy/typetraverser.py index d9ab54871f4a..4d740a802b55 100644 --- a/mypy/typetraverser.py +++ b/mypy/typetraverser.py @@ -61,16 +61,16 @@ def visit_type_var(self, t: TypeVarType) -> None: # Note that type variable values and upper bound aren't treated as # components, since they are components of the type variable # definition. We want to traverse everything just once. - pass + t.default.accept(self) def visit_param_spec(self, t: ParamSpecType) -> None: - pass + t.default.accept(self) def visit_parameters(self, t: Parameters) -> None: self.traverse_types(t.arg_types) def visit_type_var_tuple(self, t: TypeVarTupleType) -> None: - pass + t.default.accept(self) def visit_literal_type(self, t: LiteralType) -> None: t.fallback.accept(self) @@ -86,6 +86,12 @@ def visit_callable_type(self, t: CallableType) -> None: t.ret_type.accept(self) t.fallback.accept(self) + if t.type_guard is not None: + t.type_guard.accept(self) + + if t.type_is is not None: + t.type_is.accept(self) + def visit_tuple_type(self, t: TupleType) -> None: self.traverse_types(t.items) t.partial_fallback.accept(self) @@ -124,7 +130,8 @@ def visit_partial_type(self, t: PartialType) -> None: pass def visit_raw_expression_type(self, t: RawExpressionType) -> None: - pass + if t.node is not None: + t.node.accept(self) def visit_type_alias_type(self, t: TypeAliasType) -> None: # TODO: sometimes we want to traverse target as well diff --git a/mypy/typevars.py b/mypy/typevars.py index 69c2eed37fa4..3d74a40c303f 100644 --- a/mypy/typevars.py +++ b/mypy/typevars.py @@ -6,6 +6,7 @@ AnyType, Instance, ParamSpecType, + ProperType, TupleType, Type, TypeOfAny, @@ -27,16 +28,7 @@ def fill_typevars(typ: TypeInfo) -> Instance | TupleType: tv: TypeVarLikeType | UnpackType = typ.defn.type_vars[i] # Change the line number if isinstance(tv, TypeVarType): - tv = TypeVarType( - tv.name, - tv.fullname, - tv.id, - tv.values, - tv.upper_bound, - tv.variance, - line=-1, - column=-1, - ) + tv = tv.copy_modified(line=-1, column=-1) elif isinstance(tv, TypeVarTupleType): tv = UnpackType( TypeVarTupleType( @@ -45,6 +37,7 @@ def fill_typevars(typ: TypeInfo) -> Instance | TupleType: tv.id, tv.upper_bound, tv.tuple_fallback, + tv.default, line=-1, column=-1, ) @@ -52,10 +45,18 @@ def fill_typevars(typ: TypeInfo) -> Instance | TupleType: else: assert isinstance(tv, ParamSpecType) tv = ParamSpecType( - tv.name, tv.fullname, tv.id, tv.flavor, tv.upper_bound, line=-1, column=-1 + tv.name, + tv.fullname, + tv.id, + tv.flavor, + tv.upper_bound, + tv.default, + line=-1, + column=-1, ) tvs.append(tv) inst = Instance(typ, tvs) + # TODO: do we need to also handle typeddict_type here and below? if typ.tuple_type is None: return inst return typ.tuple_type.copy_modified(fallback=inst) @@ -63,10 +64,23 @@ def fill_typevars(typ: TypeInfo) -> Instance | TupleType: def fill_typevars_with_any(typ: TypeInfo) -> Instance | TupleType: """Apply a correct number of Any's as type arguments to a type.""" - inst = Instance(typ, [AnyType(TypeOfAny.special_form)] * len(typ.defn.type_vars)) + args: list[Type] = [] + for tv in typ.defn.type_vars: + # Valid erasure for *Ts is *tuple[Any, ...], not just Any. + if isinstance(tv, TypeVarTupleType): + args.append( + UnpackType(tv.tuple_fallback.copy_modified(args=[AnyType(TypeOfAny.special_form)])) + ) + else: + args.append(AnyType(TypeOfAny.special_form)) + inst = Instance(typ, args) if typ.tuple_type is None: return inst - return typ.tuple_type.copy_modified(fallback=inst) + erased_tuple_type = erase_typevars(typ.tuple_type, {tv.id for tv in typ.defn.type_vars}) + assert isinstance(erased_tuple_type, ProperType) + if isinstance(erased_tuple_type, TupleType): + return typ.tuple_type.copy_modified(fallback=inst) + return inst def has_no_typevars(typ: Type) -> bool: diff --git a/mypy/typevartuples.py b/mypy/typevartuples.py index 29b85dae72eb..af2effbd4035 100644 --- a/mypy/typevartuples.py +++ b/mypy/typevartuples.py @@ -2,37 +2,16 @@ from __future__ import annotations -from typing import Sequence, TypeVar +from typing import Sequence -from mypy.nodes import ARG_POS, ARG_STAR -from mypy.types import CallableType, Instance, ProperType, Type, UnpackType, get_proper_type - - -def find_unpack_in_list(items: Sequence[Type]) -> int | None: - unpack_index: int | None = None - for i, item in enumerate(items): - proper_item = get_proper_type(item) - if isinstance(proper_item, UnpackType): - # We cannot fail here, so we must check this in an earlier - # semanal phase. - # Funky code here avoids mypyc narrowing the type of unpack_index. - old_index = unpack_index - assert old_index is None - # Don't return so that we can also sanity check there is only one. - unpack_index = i - return unpack_index - - -T = TypeVar("T") - - -def split_with_prefix_and_suffix( - types: tuple[T, ...], prefix: int, suffix: int -) -> tuple[tuple[T, ...], tuple[T, ...], tuple[T, ...]]: - if suffix: - return (types[:prefix], types[prefix:-suffix], types[-suffix:]) - else: - return (types[:prefix], types[prefix:], ()) +from mypy.types import ( + Instance, + ProperType, + Type, + UnpackType, + get_proper_type, + split_with_prefix_and_suffix, +) def split_with_instance( @@ -45,154 +24,9 @@ def split_with_instance( ) -def split_with_mapped_and_template( - mapped: tuple[Type, ...], - mapped_prefix_len: int | None, - mapped_suffix_len: int | None, - template: tuple[Type, ...], - template_prefix_len: int, - template_suffix_len: int, -) -> tuple[ - tuple[Type, ...], - tuple[Type, ...], - tuple[Type, ...], - tuple[Type, ...], - tuple[Type, ...], - tuple[Type, ...], -] | None: - split_result = fully_split_with_mapped_and_template( - mapped, - mapped_prefix_len, - mapped_suffix_len, - template, - template_prefix_len, - template_suffix_len, - ) - if split_result is None: - return None - - ( - mapped_prefix, - mapped_middle_prefix, - mapped_middle_middle, - mapped_middle_suffix, - mapped_suffix, - template_prefix, - template_middle_prefix, - template_middle_middle, - template_middle_suffix, - template_suffix, - ) = split_result - - return ( - mapped_prefix + mapped_middle_prefix, - mapped_middle_middle, - mapped_middle_suffix + mapped_suffix, - template_prefix + template_middle_prefix, - template_middle_middle, - template_middle_suffix + template_suffix, - ) - - -def fully_split_with_mapped_and_template( - mapped: tuple[Type, ...], - mapped_prefix_len: int | None, - mapped_suffix_len: int | None, - template: tuple[Type, ...], - template_prefix_len: int, - template_suffix_len: int, -) -> tuple[ - tuple[Type, ...], - tuple[Type, ...], - tuple[Type, ...], - tuple[Type, ...], - tuple[Type, ...], - tuple[Type, ...], - tuple[Type, ...], - tuple[Type, ...], - tuple[Type, ...], - tuple[Type, ...], -] | None: - if mapped_prefix_len is not None: - assert mapped_suffix_len is not None - mapped_prefix, mapped_middle, mapped_suffix = split_with_prefix_and_suffix( - tuple(mapped), mapped_prefix_len, mapped_suffix_len - ) - else: - mapped_prefix = tuple() - mapped_suffix = tuple() - mapped_middle = mapped - - template_prefix, template_middle, template_suffix = split_with_prefix_and_suffix( - tuple(template), template_prefix_len, template_suffix_len - ) - - unpack_prefix = find_unpack_in_list(template_middle) - if unpack_prefix is None: - return ( - mapped_prefix, - (), - mapped_middle, - (), - mapped_suffix, - template_prefix, - (), - template_middle, - (), - template_suffix, - ) - - unpack_suffix = len(template_middle) - unpack_prefix - 1 - # mapped_middle is too short to do the unpack - if unpack_prefix + unpack_suffix > len(mapped_middle): - return None - - ( - mapped_middle_prefix, - mapped_middle_middle, - mapped_middle_suffix, - ) = split_with_prefix_and_suffix(mapped_middle, unpack_prefix, unpack_suffix) - ( - template_middle_prefix, - template_middle_middle, - template_middle_suffix, - ) = split_with_prefix_and_suffix(template_middle, unpack_prefix, unpack_suffix) - - return ( - mapped_prefix, - mapped_middle_prefix, - mapped_middle_middle, - mapped_middle_suffix, - mapped_suffix, - template_prefix, - template_middle_prefix, - template_middle_middle, - template_middle_suffix, - template_suffix, - ) - - def extract_unpack(types: Sequence[Type]) -> ProperType | None: """Given a list of types, extracts either a single type from an unpack, or returns None.""" if len(types) == 1: - proper_type = get_proper_type(types[0]) - if isinstance(proper_type, UnpackType): - return get_proper_type(proper_type.type) + if isinstance(types[0], UnpackType): + return get_proper_type(types[0].type) return None - - -def replace_starargs(callable: CallableType, types: list[Type]) -> CallableType: - star_index = callable.arg_kinds.index(ARG_STAR) - arg_kinds = ( - callable.arg_kinds[:star_index] - + [ARG_POS] * len(types) - + callable.arg_kinds[star_index + 1 :] - ) - arg_names = ( - callable.arg_names[:star_index] - + [None] * len(types) - + callable.arg_names[star_index + 1 :] - ) - arg_types = callable.arg_types[:star_index] + types + callable.arg_types[star_index + 1 :] - - return callable.copy_modified(arg_types=arg_types, arg_names=arg_names, arg_kinds=arg_kinds) diff --git a/mypy/util.py b/mypy/util.py index 2c225c7fe651..4b1b918b92e6 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -11,8 +11,8 @@ import sys import time from importlib import resources as importlib_resources -from typing import IO, Callable, Container, Iterable, Sequence, Sized, TypeVar -from typing_extensions import Final, Literal +from typing import IO, Callable, Container, Final, Iterable, Sequence, Sized, TypeVar +from typing_extensions import Literal try: import curses @@ -234,50 +234,91 @@ def get_mypy_comments(source: str) -> list[tuple[int, str]]: return results -PASS_TEMPLATE: Final = """ - - - - +JUNIT_HEADER_TEMPLATE: Final = """ + """ -FAIL_TEMPLATE: Final = """ - - +JUNIT_TESTCASE_FAIL_TEMPLATE: Final = """ {text} - """ -ERROR_TEMPLATE: Final = """ - - +JUNIT_ERROR_TEMPLATE: Final = """ {text} - """ +JUNIT_TESTCASE_PASS_TEMPLATE: Final = """ + +""" -def write_junit_xml( - dt: float, serious: bool, messages: list[str], path: str, version: str, platform: str -) -> None: +JUNIT_FOOTER: Final = """ +""" + + +def _generate_junit_contents( + dt: float, + serious: bool, + messages_by_file: dict[str | None, list[str]], + version: str, + platform: str, +) -> str: from xml.sax.saxutils import escape - if not messages and not serious: - xml = PASS_TEMPLATE.format(time=dt, ver=version, platform=platform) - elif not serious: - xml = FAIL_TEMPLATE.format( - text=escape("\n".join(messages)), time=dt, ver=version, platform=platform - ) + if serious: + failures = 0 + errors = len(messages_by_file) else: - xml = ERROR_TEMPLATE.format( - text=escape("\n".join(messages)), time=dt, ver=version, platform=platform - ) + failures = len(messages_by_file) + errors = 0 + + xml = JUNIT_HEADER_TEMPLATE.format( + errors=errors, + failures=failures, + time=dt, + # If there are no messages, we still write one "test" indicating success. + tests=len(messages_by_file) or 1, + ) + + if not messages_by_file: + xml += JUNIT_TESTCASE_PASS_TEMPLATE.format(time=dt, ver=version, platform=platform) + else: + for filename, messages in messages_by_file.items(): + if filename is not None: + xml += JUNIT_TESTCASE_FAIL_TEMPLATE.format( + text=escape("\n".join(messages)), + filename=filename, + time=dt, + name="mypy-py{ver}-{platform} {filename}".format( + ver=version, platform=platform, filename=filename + ), + ) + else: + xml += JUNIT_TESTCASE_FAIL_TEMPLATE.format( + text=escape("\n".join(messages)), + filename="mypy", + time=dt, + name=f"mypy-py{version}-{platform}", + ) + + xml += JUNIT_FOOTER - # checks for a directory structure in path and creates folders if needed + return xml + + +def write_junit_xml( + dt: float, + serious: bool, + messages_by_file: dict[str | None, list[str]], + path: str, + version: str, + platform: str, +) -> None: + xml = _generate_junit_contents(dt, serious, messages_by_file, version, platform) + + # creates folders if needed xml_dirs = os.path.dirname(os.path.abspath(path)) - if not os.path.isdir(xml_dirs): - os.makedirs(xml_dirs) + os.makedirs(xml_dirs, exist_ok=True) with open(path, "wb") as f: f.write(xml.encode("utf-8")) @@ -308,17 +349,6 @@ def get_prefix(fullname: str) -> str: return fullname.rsplit(".", 1)[0] -def get_top_two_prefixes(fullname: str) -> tuple[str, str]: - """Return one and two component prefixes of a fully qualified name. - - Given 'a.b.c.d', return ('a', 'a.b'). - - If fullname has only one component, return (fullname, fullname). - """ - components = fullname.split(".", 3) - return components[0], ".".join(components[:2]) - - def correct_relative_import( cur_mod_id: str, relative: int, target: str, is_cur_package_init_file: bool ) -> tuple[str, bool]: @@ -421,10 +451,10 @@ def get_unique_redefinition_name(name: str, existing: Container[str]) -> str: def check_python_version(program: str) -> None: """Report issues with the Python used to run mypy, dmypy, or stubgen""" # Check for known bad Python versions. - if sys.version_info[:2] < (3, 7): + if sys.version_info[:2] < (3, 8): # noqa: UP036 sys.exit( - "Running {name} with Python 3.6 or lower is not supported; " - "please upgrade to 3.7 or newer".format(name=program) + "Running {name} with Python 3.7 or lower is not supported; " + "please upgrade to 3.8 or newer".format(name=program) ) @@ -533,8 +563,12 @@ class FancyFormatter: This currently only works on Linux and Mac. """ - def __init__(self, f_out: IO[str], f_err: IO[str], hide_error_codes: bool) -> None: + def __init__( + self, f_out: IO[str], f_err: IO[str], hide_error_codes: bool, hide_success: bool = False + ) -> None: self.hide_error_codes = hide_error_codes + self.hide_success = hide_success + # Check if we are in a human-facing terminal on a supported platform. if sys.platform not in ("linux", "darwin", "win32", "emscripten"): self.dummy_term = True @@ -763,6 +797,9 @@ def format_success(self, n_sources: int, use_color: bool = True) -> str: n_sources is total number of files passed directly on command line, i.e. excluding stubs and followed imports. """ + if self.hide_success: + return "" + msg = f"Success: no issues found in {n_sources} source file{plural_s(n_sources)}" if not use_color: return msg @@ -820,3 +857,20 @@ def plural_s(s: int | Sized) -> str: return "s" else: return "" + + +def quote_docstring(docstr: str) -> str: + """Returns docstring correctly encapsulated in a single or double quoted form.""" + # Uses repr to get hint on the correct quotes and escape everything properly. + # Creating multiline string for prettier output. + docstr_repr = "\n".join(re.split(r"(?<=[^\\])\\n", repr(docstr))) + + if docstr_repr.startswith("'"): + # Enforce double quotes when it's safe to do so. + # That is when double quotes are not in the string + # or when it doesn't end with a single quote. + if '"' not in docstr_repr[1:-1] and docstr_repr[-2] != "'": + return f'"""{docstr_repr[1:-1]}"""' + return f"''{docstr_repr}''" + else: + return f'""{docstr_repr}""' diff --git a/mypy/version.py b/mypy/version.py index 258a0e4f8bcb..f2615b77109d 100644 --- a/mypy/version.py +++ b/mypy/version.py @@ -8,7 +8,7 @@ # - Release versions have the form "1.2.3". # - Dev versions have the form "1.2.3+dev" (PLUS sign to conform to PEP 440). # - Before 1.0 we had the form "0.NNN". -__version__ = "1.1.0+dev" +__version__ = "1.11.0+dev" base_version = __version__ mypy_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) diff --git a/mypy/visitor.py b/mypy/visitor.py index c5aa3caa8295..340e1af64e00 100644 --- a/mypy/visitor.py +++ b/mypy/visitor.py @@ -309,6 +309,10 @@ def visit_try_stmt(self, o: mypy.nodes.TryStmt) -> T: def visit_match_stmt(self, o: mypy.nodes.MatchStmt) -> T: pass + @abstractmethod + def visit_type_alias_stmt(self, o: mypy.nodes.TypeAliasStmt) -> T: + pass + @trait @mypyc_attr(allow_interpreted_subclasses=True) @@ -460,6 +464,9 @@ def visit_with_stmt(self, o: mypy.nodes.WithStmt) -> T: def visit_match_stmt(self, o: mypy.nodes.MatchStmt) -> T: pass + def visit_type_alias_stmt(self, o: mypy.nodes.TypeAliasStmt) -> T: + pass + # Expressions (default no-op implementation) def visit_int_expr(self, o: mypy.nodes.IntExpr) -> T: diff --git a/mypy_self_check.ini b/mypy_self_check.ini index d20fcd60a9cb..7f1f9689a757 100644 --- a/mypy_self_check.ini +++ b/mypy_self_check.ini @@ -5,10 +5,12 @@ disallow_any_unimported = True show_traceback = True pretty = True always_false = MYPYC -plugins = misc/proper_plugin.py -python_version = 3.7 +plugins = mypy.plugins.proper_plugin +python_version = 3.8 exclude = mypy/typeshed/|mypyc/test-data/|mypyc/lib-rt/ enable_error_code = ignore-without-code,redundant-expr +enable_incomplete_feature = PreciseTupleTypes +show_error_code_links = True [mypy-mypy.visitor] # See docstring for NodeVisitor for motivation. diff --git a/mypyc/.readthedocs.yaml b/mypyc/.readthedocs.yaml new file mode 100644 index 000000000000..90831dfd7069 --- /dev/null +++ b/mypyc/.readthedocs.yaml @@ -0,0 +1,18 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +sphinx: + configuration: mypyc/doc/conf.py + +formats: [pdf, htmlzip, epub] + +python: + install: + - requirements: docs/requirements-docs.txt diff --git a/mypyc/analysis/attrdefined.py b/mypyc/analysis/attrdefined.py index 02e02a82a4f9..350158246cdb 100644 --- a/mypyc/analysis/attrdefined.py +++ b/mypyc/analysis/attrdefined.py @@ -63,8 +63,7 @@ def foo(self) -> int: from __future__ import annotations -from typing import Set, Tuple -from typing_extensions import Final +from typing import Final, Set, Tuple from mypyc.analysis.dataflow import ( CFG, @@ -414,7 +413,7 @@ def update_always_defined_attrs_using_subclasses(cl: ClassIR, seen: set[ClassIR] seen.add(cl) -def detect_undefined_bitmap(cl: ClassIR, seen: Set[ClassIR]) -> None: +def detect_undefined_bitmap(cl: ClassIR, seen: set[ClassIR]) -> None: if cl.is_trait: return diff --git a/mypyc/analysis/dataflow.py b/mypyc/analysis/dataflow.py index 21c4da8981d1..9babf860fb31 100644 --- a/mypyc/analysis/dataflow.py +++ b/mypyc/analysis/dataflow.py @@ -18,6 +18,10 @@ ComparisonOp, ControlOp, Extend, + Float, + FloatComparisonOp, + FloatNeg, + FloatOp, GetAttr, GetElementPtr, Goto, @@ -34,6 +38,7 @@ MethodCall, Op, OpVisitor, + PrimitiveOp, RaiseStandardError, RegisterOp, Return, @@ -42,6 +47,7 @@ Truncate, TupleGet, TupleSet, + Unborrow, Unbox, Unreachable, Value, @@ -67,11 +73,8 @@ def __init__( self.exits = exits def __str__(self) -> str: - lines = [] - lines.append("exits: %s" % sorted(self.exits, key=lambda e: int(e.label))) - lines.append("succ: %s" % self.succ) - lines.append("pred: %s" % self.pred) - return "\n".join(lines) + exits = sorted(self.exits, key=lambda e: int(e.label)) + return f"exits: {exits}\nsucc: {self.succ}\npred: {self.pred}" def get_cfg(blocks: list[BasicBlock]) -> CFG: @@ -85,7 +88,6 @@ def get_cfg(blocks: list[BasicBlock]) -> CFG: pred_map: dict[BasicBlock, list[BasicBlock]] = {} exits = set() for block in blocks: - assert not any( isinstance(op, ControlOp) for op in block.ops[:-1] ), "Control-flow ops must be at the end of blocks" @@ -142,7 +144,7 @@ def cleanup_cfg(blocks: list[BasicBlock]) -> None: # Then delete any blocks that have no predecessors changed = False cfg = get_cfg(blocks) - orig_blocks = blocks[:] + orig_blocks = blocks.copy() blocks.clear() for i, block in enumerate(orig_blocks): if i == 0 or cfg.pred[block]: @@ -233,6 +235,9 @@ def visit_raise_standard_error(self, op: RaiseStandardError) -> GenAndKill[T]: def visit_call_c(self, op: CallC) -> GenAndKill[T]: return self.visit_register_op(op) + def visit_primitive_op(self, op: PrimitiveOp) -> GenAndKill[T]: + return self.visit_register_op(op) + def visit_truncate(self, op: Truncate) -> GenAndKill[T]: return self.visit_register_op(op) @@ -245,9 +250,18 @@ def visit_load_global(self, op: LoadGlobal) -> GenAndKill[T]: def visit_int_op(self, op: IntOp) -> GenAndKill[T]: return self.visit_register_op(op) + def visit_float_op(self, op: FloatOp) -> GenAndKill[T]: + return self.visit_register_op(op) + + def visit_float_neg(self, op: FloatNeg) -> GenAndKill[T]: + return self.visit_register_op(op) + def visit_comparison_op(self, op: ComparisonOp) -> GenAndKill[T]: return self.visit_register_op(op) + def visit_float_comparison_op(self, op: FloatComparisonOp) -> GenAndKill[T]: + return self.visit_register_op(op) + def visit_load_mem(self, op: LoadMem) -> GenAndKill[T]: return self.visit_register_op(op) @@ -260,6 +274,9 @@ def visit_load_address(self, op: LoadAddress) -> GenAndKill[T]: def visit_keep_alive(self, op: KeepAlive) -> GenAndKill[T]: return self.visit_register_op(op) + def visit_unborrow(self, op: Unborrow) -> GenAndKill[T]: + return self.visit_register_op(op) + class DefinedVisitor(BaseAnalysisVisitor[Value]): """Visitor for finding defined registers. @@ -444,7 +461,7 @@ def analyze_undefined_regs( def non_trivial_sources(op: Op) -> set[Value]: result = set() for source in op.sources(): - if not isinstance(source, Integer): + if not isinstance(source, (Integer, Float)): result.add(source) return result @@ -454,7 +471,7 @@ def visit_branch(self, op: Branch) -> GenAndKill[Value]: return non_trivial_sources(op), set() def visit_return(self, op: Return) -> GenAndKill[Value]: - if not isinstance(op.value, Integer): + if not isinstance(op.value, (Integer, Float)): return {op.value}, set() else: return set(), set() diff --git a/mypyc/analysis/ircheck.py b/mypyc/analysis/ircheck.py index 719faebfcee8..88737ac208de 100644 --- a/mypyc/analysis/ircheck.py +++ b/mypyc/analysis/ircheck.py @@ -1,4 +1,5 @@ """Utilities for checking that internal ir is valid and consistent.""" + from __future__ import annotations from mypyc.ir.func_ir import FUNC_STATICMETHOD, FuncIR @@ -16,6 +17,9 @@ ControlOp, DecRef, Extend, + FloatComparisonOp, + FloatNeg, + FloatOp, GetAttr, GetElementPtr, Goto, @@ -33,6 +37,7 @@ MethodCall, Op, OpVisitor, + PrimitiveOp, RaiseStandardError, Register, Return, @@ -41,8 +46,10 @@ Truncate, TupleGet, TupleSet, + Unborrow, Unbox, Unreachable, + Value, ) from mypyc.ir.pprint import format_func from mypyc.ir.rtypes import ( @@ -54,6 +61,7 @@ bytes_rprimitive, dict_rprimitive, int_rprimitive, + is_float_rprimitive, is_object_rprimitive, list_rprimitive, range_rprimitive, @@ -221,6 +229,14 @@ def check_compatibility(self, op: Op, t: RType, s: RType) -> None: if not can_coerce_to(t, s) or not can_coerce_to(s, t): self.fail(source=op, desc=f"{t.name} and {s.name} are not compatible") + def expect_float(self, op: Op, v: Value) -> None: + if not is_float_rprimitive(v.type): + self.fail(op, f"Float expected (actual type is {v.type})") + + def expect_non_float(self, op: Op, v: Value) -> None: + if is_float_rprimitive(v.type): + self.fail(op, "Float not expected") + def visit_goto(self, op: Goto) -> None: self.check_control_op_targets(op) @@ -366,6 +382,9 @@ def visit_raise_standard_error(self, op: RaiseStandardError) -> None: def visit_call_c(self, op: CallC) -> None: pass + def visit_primitive_op(self, op: PrimitiveOp) -> None: + pass + def visit_truncate(self, op: Truncate) -> None: pass @@ -376,10 +395,24 @@ def visit_load_global(self, op: LoadGlobal) -> None: pass def visit_int_op(self, op: IntOp) -> None: - pass + self.expect_non_float(op, op.lhs) + self.expect_non_float(op, op.rhs) def visit_comparison_op(self, op: ComparisonOp) -> None: self.check_compatibility(op, op.lhs.type, op.rhs.type) + self.expect_non_float(op, op.lhs) + self.expect_non_float(op, op.rhs) + + def visit_float_op(self, op: FloatOp) -> None: + self.expect_float(op, op.lhs) + self.expect_float(op, op.rhs) + + def visit_float_neg(self, op: FloatNeg) -> None: + self.expect_float(op, op.src) + + def visit_float_comparison_op(self, op: FloatComparisonOp) -> None: + self.expect_float(op, op.lhs) + self.expect_float(op, op.rhs) def visit_load_mem(self, op: LoadMem) -> None: pass @@ -395,3 +428,6 @@ def visit_load_address(self, op: LoadAddress) -> None: def visit_keep_alive(self, op: KeepAlive) -> None: pass + + def visit_unborrow(self, op: Unborrow) -> None: + pass diff --git a/mypyc/analysis/selfleaks.py b/mypyc/analysis/selfleaks.py index 16c1050acf91..5d89a9bfc7c6 100644 --- a/mypyc/analysis/selfleaks.py +++ b/mypyc/analysis/selfleaks.py @@ -14,6 +14,9 @@ Cast, ComparisonOp, Extend, + FloatComparisonOp, + FloatNeg, + FloatOp, GetAttr, GetElementPtr, Goto, @@ -28,6 +31,7 @@ LoadStatic, MethodCall, OpVisitor, + PrimitiveOp, RaiseStandardError, Register, RegisterOp, @@ -37,6 +41,7 @@ Truncate, TupleGet, TupleSet, + Unborrow, Unbox, Unreachable, ) @@ -145,6 +150,9 @@ def visit_raise_standard_error(self, op: RaiseStandardError) -> GenAndKill: def visit_call_c(self, op: CallC) -> GenAndKill: return self.check_register_op(op) + def visit_primitive_op(self, op: PrimitiveOp) -> GenAndKill: + return self.check_register_op(op) + def visit_truncate(self, op: Truncate) -> GenAndKill: return CLEAN @@ -160,6 +168,15 @@ def visit_int_op(self, op: IntOp) -> GenAndKill: def visit_comparison_op(self, op: ComparisonOp) -> GenAndKill: return CLEAN + def visit_float_op(self, op: FloatOp) -> GenAndKill: + return CLEAN + + def visit_float_neg(self, op: FloatNeg) -> GenAndKill: + return CLEAN + + def visit_float_comparison_op(self, op: FloatComparisonOp) -> GenAndKill: + return CLEAN + def visit_load_mem(self, op: LoadMem) -> GenAndKill: return CLEAN @@ -172,6 +189,9 @@ def visit_load_address(self, op: LoadAddress) -> GenAndKill: def visit_keep_alive(self, op: KeepAlive) -> GenAndKill: return CLEAN + def visit_unborrow(self, op: Unborrow) -> GenAndKill: + return CLEAN + def check_register_op(self, op: RegisterOp) -> GenAndKill: if any(src is self.self_reg for src in op.sources()): return DIRTY diff --git a/mypyc/build.py b/mypyc/build.py index cc03eba95b4e..485803acba46 100644 --- a/mypyc/build.py +++ b/mypyc/build.py @@ -25,7 +25,7 @@ import re import sys import time -from typing import TYPE_CHECKING, Any, Dict, Iterable, NoReturn, cast +from typing import TYPE_CHECKING, Any, Dict, Iterable, NoReturn, Union, cast from mypy.build import BuildSource from mypy.errors import CompileError @@ -40,30 +40,46 @@ from mypyc.namegen import exported_name from mypyc.options import CompilerOptions -if TYPE_CHECKING: - from distutils.core import Extension - try: # Import setuptools so that it monkey-patch overrides distutils - import setuptools # noqa: F401 + import setuptools except ImportError: + pass + +if TYPE_CHECKING: if sys.version_info >= (3, 12): - # Raise on Python 3.12, since distutils will go away forever - raise -from distutils import ccompiler, sysconfig + from setuptools import Extension + else: + from distutils.core import Extension as _distutils_Extension + from typing_extensions import TypeAlias + + from setuptools import Extension as _setuptools_Extension + + Extension: TypeAlias = Union[_setuptools_Extension, _distutils_Extension] + +if sys.version_info >= (3, 12): + # From setuptools' monkeypatch + from distutils import ccompiler, sysconfig # type: ignore[import-not-found] +else: + from distutils import ccompiler, sysconfig def get_extension() -> type[Extension]: # We can work with either setuptools or distutils, and pick setuptools # if it has been imported. use_setuptools = "setuptools" in sys.modules + extension_class: type[Extension] - if not use_setuptools: - from distutils.core import Extension + if sys.version_info < (3, 12) and not use_setuptools: + import distutils.core + + extension_class = distutils.core.Extension else: - from setuptools import Extension + if not use_setuptools: + sys.exit("error: setuptools not installed") + extension_class = setuptools.Extension - return Extension + return extension_class def setup_mypycify_vars() -> None: @@ -89,7 +105,14 @@ def emit_messages(options: Options, messages: list[str], dt: float, serious: boo # ... you know, just in case. if options.junit_xml: py_version = f"{options.python_version[0]}_{options.python_version[1]}" - write_junit_xml(dt, serious, messages, options.junit_xml, py_version, options.platform) + write_junit_xml( + dt, + serious, + {None: messages} if messages else {}, + options.junit_xml, + py_version, + options.platform, + ) if messages: print("\n".join(messages)) @@ -216,7 +239,7 @@ def generate_c( if compiler_options.verbose: print(f"Parsed and typechecked in {t1 - t0:.3f}s") - errors = Errors() + errors = Errors(options) modules, ctext = emitmodule.compile_modules_to_c( result, compiler_options=compiler_options, errors=errors, groups=groups ) diff --git a/mypyc/codegen/cstring.py b/mypyc/codegen/cstring.py index e006f12e09ec..853787f8161d 100644 --- a/mypyc/codegen/cstring.py +++ b/mypyc/codegen/cstring.py @@ -21,7 +21,7 @@ from __future__ import annotations import string -from typing_extensions import Final +from typing import Final CHAR_MAP: Final = [f"\\{i:03o}" for i in range(256)] diff --git a/mypyc/codegen/emit.py b/mypyc/codegen/emit.py index 6e0c89dd0ecf..fce6896e8d11 100644 --- a/mypyc/codegen/emit.py +++ b/mypyc/codegen/emit.py @@ -2,9 +2,10 @@ from __future__ import annotations +import pprint import sys -from typing import Callable -from typing_extensions import Final +import textwrap +from typing import Callable, Final from mypyc.codegen.literals import Literals from mypyc.common import ( @@ -33,6 +34,7 @@ is_dict_rprimitive, is_fixed_width_rtype, is_float_rprimitive, + is_int16_rprimitive, is_int32_rprimitive, is_int64_rprimitive, is_int_rprimitive, @@ -45,6 +47,7 @@ is_short_int_rprimitive, is_str_rprimitive, is_tuple_rprimitive, + is_uint8_rprimitive, object_rprimitive, optional_value_type, ) @@ -191,10 +194,31 @@ def reg(self, reg: Value) -> str: def attr(self, name: str) -> str: return ATTR_PREFIX + name - def emit_line(self, line: str = "") -> None: + def object_annotation(self, obj: object, line: str) -> str: + """Build a C comment with an object's string represention. + + If the comment exceeds the line length limit, it's wrapped into a + multiline string (with the extra lines indented to be aligned with + the first line's comment). + + If it contains illegal characters, an empty string is returned.""" + line_width = self._indent + len(line) + formatted = pprint.pformat(obj, compact=True, width=max(90 - line_width, 20)) + if any(x in formatted for x in ("/*", "*/", "\0")): + return "" + + if "\n" in formatted: + first_line, rest = formatted.split("\n", maxsplit=1) + comment_continued = textwrap.indent(rest, (line_width + 3) * " ") + return f" /* {first_line}\n{comment_continued} */" + else: + return f" /* {formatted} */" + + def emit_line(self, line: str = "", *, ann: object = None) -> None: if line.startswith("}"): self.dedent() - self.fragments.append(self._indent * " " + line + "\n") + comment = self.object_annotation(ann, line) if ann is not None else "" + self.fragments.append(self._indent * " " + line + comment + "\n") if line.endswith("{"): self.indent() @@ -206,6 +230,9 @@ def emit_label(self, label: BasicBlock | str) -> None: if isinstance(label, str): text = label else: + if label.label == 0 or not label.referenced: + return + text = self.label(label) # Extra semicolon prevents an error when the next line declares a tempvar self.fragments.append(f"{text}: ;\n") @@ -319,12 +346,6 @@ def tuple_c_declaration(self, rtuple: RTuple) -> list[str]: result.append(f"{self.ctype_spaced(typ)}f{i};") i += 1 result.append(f"}} {rtuple.struct_name};") - values = self.tuple_undefined_value_helper(rtuple) - result.append( - "static {} {} = {{ {} }};".format( - self.ctype(rtuple), self.tuple_undefined_value(rtuple), "".join(values) - ) - ) result.append("#endif") result.append("") @@ -444,23 +465,20 @@ def tuple_undefined_check_cond( return check def tuple_undefined_value(self, rtuple: RTuple) -> str: - return "tuple_undefined_" + rtuple.unique_id + """Undefined tuple value suitable in an expression.""" + return f"({rtuple.struct_name}) {self.c_initializer_undefined_value(rtuple)}" - def tuple_undefined_value_helper(self, rtuple: RTuple) -> list[str]: - res = [] - # see tuple_c_declaration() - if len(rtuple.types) == 0: - return [self.c_undefined_value(int_rprimitive)] - for item in rtuple.types: - if not isinstance(item, RTuple): - res.append(self.c_undefined_value(item)) - else: - sub_list = self.tuple_undefined_value_helper(item) - res.append("{ ") - res.extend(sub_list) - res.append(" }") - res.append(", ") - return res[:-1] + def c_initializer_undefined_value(self, rtype: RType) -> str: + """Undefined value represented in a form suitable for variable initialization.""" + if isinstance(rtype, RTuple): + if not rtype.types: + # Empty tuples contain a flag so that they can still indicate + # error values. + return f"{{ {int_rprimitive.c_undefined} }}" + items = ", ".join([self.c_initializer_undefined_value(t) for t in rtype.types]) + return f"{{ {items} }}" + else: + return self.c_undefined_value(rtype) # Higher-level operations @@ -668,7 +686,7 @@ def emit_cast( if likely: check = f"(likely{check})" self.emit_arg_check(src, dest, typ, check, optional) - self.emit_lines(f" {dest} = {src};".format(dest, src), "else {") + self.emit_lines(f" {dest} = {src};", "else {") self.emit_cast_error_handler(error, src, dest, typ, raise_exception) self.emit_line("}") elif is_none_rprimitive(typ): @@ -883,18 +901,43 @@ def emit_unbox( self.emit_line(f" {dest} = 1;") elif is_int64_rprimitive(typ): # Whether we are borrowing or not makes no difference. + assert not optional # Not supported for overlapping error values if declare_dest: self.emit_line(f"int64_t {dest};") self.emit_line(f"{dest} = CPyLong_AsInt64({src});") - # TODO: Handle 'optional' - # TODO: Handle 'failure' + if not isinstance(error, AssignHandler): + self.emit_unbox_failure_with_overlapping_error_value(dest, typ, failure) elif is_int32_rprimitive(typ): # Whether we are borrowing or not makes no difference. + assert not optional # Not supported for overlapping error values if declare_dest: self.emit_line(f"int32_t {dest};") self.emit_line(f"{dest} = CPyLong_AsInt32({src});") - # TODO: Handle 'optional' - # TODO: Handle 'failure' + if not isinstance(error, AssignHandler): + self.emit_unbox_failure_with_overlapping_error_value(dest, typ, failure) + elif is_int16_rprimitive(typ): + # Whether we are borrowing or not makes no difference. + assert not optional # Not supported for overlapping error values + if declare_dest: + self.emit_line(f"int16_t {dest};") + self.emit_line(f"{dest} = CPyLong_AsInt16({src});") + if not isinstance(error, AssignHandler): + self.emit_unbox_failure_with_overlapping_error_value(dest, typ, failure) + elif is_uint8_rprimitive(typ): + # Whether we are borrowing or not makes no difference. + assert not optional # Not supported for overlapping error values + if declare_dest: + self.emit_line(f"uint8_t {dest};") + self.emit_line(f"{dest} = CPyLong_AsUInt8({src});") + if not isinstance(error, AssignHandler): + self.emit_unbox_failure_with_overlapping_error_value(dest, typ, failure) + elif is_float_rprimitive(typ): + assert not optional # Not supported for overlapping error values + if declare_dest: + self.emit_line(f"double {dest};") + # TODO: Don't use __float__ and __index__ + self.emit_line(f"{dest} = PyFloat_AsDouble({src});") + self.emit_lines(f"if ({dest} == -1.0 && PyErr_Occurred()) {{", failure, "}") elif isinstance(typ, RTuple): self.declare_tuple_struct(typ) if declare_dest: @@ -979,10 +1022,12 @@ def emit_box( self.emit_lines(f"{declaration}{dest} = Py_None;") if not can_borrow: self.emit_inc_ref(dest, object_rprimitive) - elif is_int32_rprimitive(typ): + elif is_int32_rprimitive(typ) or is_int16_rprimitive(typ) or is_uint8_rprimitive(typ): self.emit_line(f"{declaration}{dest} = PyLong_FromLong({src});") elif is_int64_rprimitive(typ): self.emit_line(f"{declaration}{dest} = PyLong_FromLongLong({src});") + elif is_float_rprimitive(typ): + self.emit_line(f"{declaration}{dest} = PyFloat_FromDouble({src});") elif isinstance(typ, RTuple): self.declare_tuple_struct(typ) self.emit_line(f"{declaration}{dest} = PyTuple_New({len(typ.types)});") @@ -1107,3 +1152,42 @@ def _emit_traceback( self.emit_line(line) if DEBUG_ERRORS: self.emit_line('assert(PyErr_Occurred() != NULL && "failure w/o err!");') + + def emit_unbox_failure_with_overlapping_error_value( + self, dest: str, typ: RType, failure: str + ) -> None: + self.emit_line(f"if ({dest} == {self.c_error_value(typ)} && PyErr_Occurred()) {{") + self.emit_line(failure) + self.emit_line("}") + + +def c_array_initializer(components: list[str], *, indented: bool = False) -> str: + """Construct an initializer for a C array variable. + + Components are C expressions valid in an initializer. + + For example, if components are ["1", "2"], the result + would be "{1, 2}", which can be used like this: + + int a[] = {1, 2}; + + If the result is long, split it into multiple lines. + """ + indent = " " * 4 if indented else "" + res = [] + current: list[str] = [] + cur_len = 0 + for c in components: + if not current or cur_len + 2 + len(indent) + len(c) < 70: + current.append(c) + cur_len += len(c) + 2 + else: + res.append(indent + ", ".join(current)) + current = [c] + cur_len = len(c) + if not res: + # Result fits on a single line + return "{%s}" % ", ".join(current) + # Multi-line result + res.append(indent + ", ".join(current)) + return "{\n " + ",\n ".join(res) + "\n" + indent + "}" diff --git a/mypyc/codegen/emitclass.py b/mypyc/codegen/emitclass.py index a9b51b8ff1a4..8dcf7212b694 100644 --- a/mypyc/codegen/emitclass.py +++ b/mypyc/codegen/emitclass.py @@ -18,7 +18,7 @@ generate_richcompare_wrapper, generate_set_del_item_wrapper, ) -from mypyc.common import BITMAP_BITS, BITMAP_TYPE, NATIVE_PREFIX, PREFIX, REG_PREFIX, use_fastcall +from mypyc.common import BITMAP_BITS, BITMAP_TYPE, NATIVE_PREFIX, PREFIX, REG_PREFIX from mypyc.ir.class_ir import ClassIR, VTableEntries from mypyc.ir.func_ir import FUNC_CLASSMETHOD, FUNC_STATICMETHOD, FuncDecl, FuncIR from mypyc.ir.rtypes import RTuple, RType, object_rprimitive @@ -217,7 +217,7 @@ def generate_class(cl: ClassIR, module: str, emitter: Emitter) -> None: fields["tp_name"] = f'"{name}"' generate_full = not cl.is_trait and not cl.builtin_base - needs_getseters = cl.needs_getseters or not cl.is_generated + needs_getseters = cl.needs_getseters or not cl.is_generated or cl.has_dict if not cl.builtin_base: fields["tp_new"] = new_name @@ -270,7 +270,7 @@ def emit_line() -> None: # that isn't what we want. # XXX: there is no reason for the __weakref__ stuff to be mixed up with __dict__ - if cl.has_dict: + if cl.has_dict and not has_managed_dict(cl, emitter): # __dict__ lives right after the struct and __weakref__ lives right after that # TODO: They should get members in the struct instead of doing this nonsense. weak_offset = f"{base_size} + sizeof(PyObject *)" @@ -284,8 +284,9 @@ def emit_line() -> None: fields["tp_members"] = members_name fields["tp_basicsize"] = f"{base_size} + 2*sizeof(PyObject *)" - fields["tp_dictoffset"] = base_size - fields["tp_weaklistoffset"] = weak_offset + if emitter.capi_version < (3, 12): + fields["tp_dictoffset"] = base_size + fields["tp_weaklistoffset"] = weak_offset else: fields["tp_basicsize"] = base_size @@ -341,6 +342,8 @@ def emit_line() -> None: # This is just a placeholder to please CPython. It will be # overridden during setup. fields["tp_call"] = "PyVectorcall_Call" + if has_managed_dict(cl, emitter): + flags.append("Py_TPFLAGS_MANAGED_DICT") fields["tp_flags"] = " | ".join(flags) emitter.emit_line(f"static PyTypeObject {emitter.type_struct_name(cl)}_template_ = {{") @@ -578,7 +581,12 @@ def generate_setup_for_class( for base in reversed(cl.base_mro): for attr, rtype in base.attributes.items(): - emitter.emit_line(rf"self->{emitter.attr(attr)} = {emitter.c_undefined_value(rtype)};") + value = emitter.c_undefined_value(rtype) + + # We don't need to set this field to NULL since tp_alloc() already + # zero-initializes `self`. + if value != "NULL": + emitter.emit_line(rf"self->{emitter.attr(attr)} = {value};") # Initialize attributes to default values, if necessary if defaults_fn is not None: @@ -725,7 +733,9 @@ def generate_traverse_for_class(cl: ClassIR, func_name: str, emitter: Emitter) - for base in reversed(cl.base_mro): for attr, rtype in base.attributes.items(): emitter.emit_gc_visit(f"self->{emitter.attr(attr)}", rtype) - if cl.has_dict: + if has_managed_dict(cl, emitter): + emitter.emit_line("_PyObject_VisitManagedDict((PyObject *)self, visit, arg);") + elif cl.has_dict: struct_name = cl.struct_name(emitter.names) # __dict__ lives right after the struct and __weakref__ lives right after that emitter.emit_gc_visit( @@ -746,7 +756,9 @@ def generate_clear_for_class(cl: ClassIR, func_name: str, emitter: Emitter) -> N for base in reversed(cl.base_mro): for attr, rtype in base.attributes.items(): emitter.emit_gc_clear(f"self->{emitter.attr(attr)}", rtype) - if cl.has_dict: + if has_managed_dict(cl, emitter): + emitter.emit_line("_PyObject_ClearManagedDict((PyObject *)self);") + elif cl.has_dict: struct_name = cl.struct_name(emitter.names) # __dict__ lives right after the struct and __weakref__ lives right after that emitter.emit_gc_clear( @@ -782,11 +794,7 @@ def generate_methods_table(cl: ClassIR, name: str, emitter: Emitter) -> None: continue emitter.emit_line(f'{{"{fn.name}",') emitter.emit_line(f" (PyCFunction){PREFIX}{fn.cname(emitter.names)},") - if use_fastcall(emitter.capi_version): - flags = ["METH_FASTCALL"] - else: - flags = ["METH_VARARGS"] - flags.append("METH_KEYWORDS") + flags = ["METH_FASTCALL", "METH_KEYWORDS"] if fn.decl.kind == FUNC_STATICMETHOD: flags.append("METH_STATIC") elif fn.decl.kind == FUNC_CLASSMETHOD: @@ -878,6 +886,9 @@ def generate_getseters_table(cl: ClassIR, name: str, emitter: Emitter) -> None: else: emitter.emit_line("NULL, NULL, NULL},") + if cl.has_dict: + emitter.emit_line('{"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict},') + emitter.emit_line("{NULL} /* Sentinel */") emitter.emit_line("};") @@ -1004,6 +1015,7 @@ def generate_readonly_getter( emitter.ctype_spaced(rtype), NATIVE_PREFIX, func_ir.cname(emitter.names) ) ) + emitter.emit_error_check("retval", rtype, "return NULL;") emitter.emit_box("retval", "retbox", rtype, declare_dest=True) emitter.emit_line("return retbox;") else: @@ -1016,7 +1028,6 @@ def generate_readonly_getter( def generate_property_setter( cl: ClassIR, attr: str, arg_type: RType, func_ir: FuncIR, emitter: Emitter ) -> None: - emitter.emit_line("static int") emitter.emit_line( "{}({} *self, PyObject *value, void *closure)".format( @@ -1035,3 +1046,15 @@ def generate_property_setter( ) emitter.emit_line("return 0;") emitter.emit_line("}") + + +def has_managed_dict(cl: ClassIR, emitter: Emitter) -> bool: + """Should the class get the Py_TPFLAGS_MANAGED_DICT flag?""" + # On 3.11 and earlier the flag doesn't exist and we use + # tp_dictoffset instead. If a class inherits from Exception, the + # flag conflicts with tp_dictoffset set in the base class. + return ( + emitter.capi_version >= (3, 12) + and cl.has_dict + and cl.builtin_base != "PyBaseExceptionObject" + ) diff --git a/mypyc/codegen/emitfunc.py b/mypyc/codegen/emitfunc.py index e7fb7db80413..12f57b9cee6f 100644 --- a/mypyc/codegen/emitfunc.py +++ b/mypyc/codegen/emitfunc.py @@ -2,10 +2,10 @@ from __future__ import annotations -from typing_extensions import Final +from typing import Final from mypyc.analysis.blockfreq import frequently_executed_blocks -from mypyc.codegen.emit import DEBUG_ERRORS, Emitter, TracebackAndGotoHandler +from mypyc.codegen.emit import DEBUG_ERRORS, Emitter, TracebackAndGotoHandler, c_array_initializer from mypyc.common import MODULE_PREFIX, NATIVE_PREFIX, REG_PREFIX, STATIC_PREFIX, TYPE_PREFIX from mypyc.ir.class_ir import ClassIR from mypyc.ir.func_ir import FUNC_CLASSMETHOD, FUNC_STATICMETHOD, FuncDecl, FuncIR, all_values @@ -23,8 +23,13 @@ CallC, Cast, ComparisonOp, + ControlOp, DecRef, Extend, + Float, + FloatComparisonOp, + FloatNeg, + FloatOp, GetAttr, GetElementPtr, Goto, @@ -42,6 +47,7 @@ MethodCall, Op, OpVisitor, + PrimitiveOp, RaiseStandardError, Register, Return, @@ -50,6 +56,7 @@ Truncate, TupleGet, TupleSet, + Unborrow, Unbox, Unreachable, Value, @@ -119,6 +126,28 @@ def generate_native_function( for i, block in enumerate(blocks): block.label = i + # Find blocks that are never jumped to or are only jumped to from the + # block directly above it. This allows for more labels and gotos to be + # eliminated during code generation. + for block in fn.blocks: + terminator = block.terminator + assert isinstance(terminator, ControlOp) + + for target in terminator.targets(): + is_next_block = target.label == block.label + 1 + + # Always emit labels for GetAttr error checks since the emit code that + # generates them will add instructions between the branch and the + # next label, causing the label to be wrongly removed. A better + # solution would be to change the IR so that it adds a basic block + # inbetween the calls. + is_problematic_op = isinstance(terminator, Branch) and any( + isinstance(s, GetAttr) for s in terminator.sources() + ) + + if not is_next_block or is_problematic_op: + fn.blocks[target.label].referenced = True + common = frequently_executed_blocks(fn.blocks[0]) for i in range(len(blocks)): @@ -170,13 +199,6 @@ def visit_goto(self, op: Goto) -> None: def visit_branch(self, op: Branch) -> None: true, false = op.true, op.false - if op.op == Branch.IS_ERROR and isinstance(op.value, GetAttr) and not op.negated: - op2 = op.value - if op2.class_type.class_ir.is_always_defined(op2.attr): - # Getting an always defined attribute never fails, so the branch can be omitted. - if false is not self.next_block: - self.emit_line(f"goto {self.label(false)};") - return negated = op.negated negated_rare = False if true is self.next_block and op.traceback_entry is None: @@ -212,7 +234,8 @@ def visit_branch(self, op: Branch) -> None: if false is self.next_block: if op.traceback_entry is None: - self.emit_line(f"if ({cond}) goto {self.label(true)};") + if true is not self.next_block: + self.emit_line(f"if ({cond}) goto {self.label(true)};") else: self.emit_line(f"if ({cond}) {{") self.emit_traceback(op) @@ -220,9 +243,11 @@ def visit_branch(self, op: Branch) -> None: else: self.emit_line(f"if ({cond}) {{") self.emit_traceback(op) - self.emit_lines( - "goto %s;" % self.label(true), "} else", " goto %s;" % self.label(false) - ) + + if true is not self.next_block: + self.emit_line("goto %s;" % self.label(true)) + + self.emit_lines("} else", " goto %s;" % self.label(false)) def visit_return(self, op: Return) -> None: value_str = self.reg(op.value) @@ -237,7 +262,6 @@ def visit_tuple_set(self, op: TupleSet) -> None: else: for i, item in enumerate(op.items): self.emit_line(f"{dest}.f{i} = {self.reg(item)};") - self.emit_inc_ref(dest, tuple_type) def visit_assign(self, op: Assign) -> None: dest = self.reg(op.dest) @@ -258,12 +282,12 @@ def visit_assign_multi(self, op: AssignMulti) -> None: # RArray values can only be assigned to once, so we can always # declare them on initialization. self.emit_line( - "%s%s[%d] = {%s};" + "%s%s[%d] = %s;" % ( self.emitter.ctype_spaced(typ.item_type), dest, len(op.src), - ", ".join(self.reg(s) for s in op.src), + c_array_initializer([self.reg(s) for s in op.src], indented=True), ) ) @@ -278,15 +302,12 @@ def visit_load_error_value(self, op: LoadErrorValue) -> None: def visit_load_literal(self, op: LoadLiteral) -> None: index = self.literals.literal_index(op.value) - s = repr(op.value) - if not any(x in s for x in ("/*", "*/", "\0")): - ann = " /* %s */" % s - else: - ann = "" if not is_int_rprimitive(op.type): - self.emit_line("%s = CPyStatics[%d];%s" % (self.reg(op), index, ann)) + self.emit_line("%s = CPyStatics[%d];" % (self.reg(op), index), ann=op.value) else: - self.emit_line("%s = (CPyTagged)CPyStatics[%d] | 1;%s" % (self.reg(op), index, ann)) + self.emit_line( + "%s = (CPyTagged)CPyStatics[%d] | 1;" % (self.reg(op), index), ann=op.value + ) def get_attr_expr(self, obj: str, op: GetAttr | SetAttr, decl_cl: ClassIR) -> str: """Generate attribute accessor for normal (non-property) access. @@ -464,12 +485,7 @@ def visit_load_static(self, op: LoadStatic) -> None: name = self.emitter.static_name(op.identifier, op.module_name, prefix) if op.namespace == NAMESPACE_TYPE: name = "(PyObject *)%s" % name - ann = "" - if op.ann: - s = repr(op.ann) - if not any(x in s for x in ("/*", "*/", "\0")): - ann = " /* %s */" % s - self.emit_line(f"{dest} = {name};{ann}") + self.emit_line(f"{dest} = {name};", ann=op.ann) def visit_init_static(self, op: InitStatic) -> None: value = self.reg(op.value) @@ -484,7 +500,8 @@ def visit_tuple_get(self, op: TupleGet) -> None: dest = self.reg(op) src = self.reg(op.src) self.emit_line(f"{dest} = {src}.f{op.index};") - self.emit_inc_ref(dest, op.type) + if not op.is_borrowed: + self.emit_inc_ref(dest, op.type) def get_dest_assign(self, dest: Value) -> str: if not dest.is_void: @@ -519,9 +536,7 @@ def visit_method_call(self, op: MethodCall) -> None: obj_args = ( [] if method.decl.kind == FUNC_STATICMETHOD - else [f"(PyObject *)Py_TYPE({obj})"] - if method.decl.kind == FUNC_CLASSMETHOD - else [obj] + else [f"(PyObject *)Py_TYPE({obj})"] if method.decl.kind == FUNC_CLASSMETHOD else [obj] ) args = ", ".join(obj_args + [self.reg(arg) for arg in op.args]) mtype = native_function_type(method, self.emitter) @@ -615,6 +630,11 @@ def visit_call_c(self, op: CallC) -> None: args = ", ".join(self.reg(arg) for arg in op.args) self.emitter.emit_line(f"{dest}{op.function_name}({args});") + def visit_primitive_op(self, op: PrimitiveOp) -> None: + raise RuntimeError( + f"unexpected PrimitiveOp {op.desc.name}: they must be lowered before codegen" + ) + def visit_truncate(self, op: Truncate) -> None: dest = self.reg(op) value = self.reg(op.src) @@ -632,12 +652,7 @@ def visit_extend(self, op: Extend) -> None: def visit_load_global(self, op: LoadGlobal) -> None: dest = self.reg(op) - ann = "" - if op.ann: - s = repr(op.ann) - if not any(x in s for x in ("/*", "*/", "\0")): - ann = " /* %s */" % s - self.emit_line(f"{dest} = {op.identifier};{ann}") + self.emit_line(f"{dest} = {op.identifier};", ann=op.ann) def visit_int_op(self, op: IntOp) -> None: dest = self.reg(op) @@ -671,6 +686,27 @@ def visit_comparison_op(self, op: ComparisonOp) -> None: lhs_cast = self.emit_signed_int_cast(op.lhs.type) self.emit_line(f"{dest} = {lhs_cast}{lhs} {op.op_str[op.op]} {rhs_cast}{rhs};") + def visit_float_op(self, op: FloatOp) -> None: + dest = self.reg(op) + lhs = self.reg(op.lhs) + rhs = self.reg(op.rhs) + if op.op != FloatOp.MOD: + self.emit_line(f"{dest} = {lhs} {op.op_str[op.op]} {rhs};") + else: + # TODO: This may set errno as a side effect, that is a little sketchy. + self.emit_line(f"{dest} = fmod({lhs}, {rhs});") + + def visit_float_neg(self, op: FloatNeg) -> None: + dest = self.reg(op) + src = self.reg(op.src) + self.emit_line(f"{dest} = -{src};") + + def visit_float_comparison_op(self, op: FloatComparisonOp) -> None: + dest = self.reg(op) + lhs = self.reg(op.lhs) + rhs = self.reg(op.rhs) + self.emit_line(f"{dest} = {lhs} {op.op_str[op.op]} {rhs};") + def visit_load_mem(self, op: LoadMem) -> None: dest = self.reg(op) src = self.reg(op.src) @@ -702,13 +738,25 @@ def visit_get_element_ptr(self, op: GetElementPtr) -> None: def visit_load_address(self, op: LoadAddress) -> None: typ = op.type dest = self.reg(op) - src = self.reg(op.src) if isinstance(op.src, Register) else op.src + if isinstance(op.src, Register): + src = self.reg(op.src) + elif isinstance(op.src, LoadStatic): + prefix = self.PREFIX_MAP[op.src.namespace] + src = self.emitter.static_name(op.src.identifier, op.src.module_name, prefix) + else: + src = op.src self.emit_line(f"{dest} = ({typ._ctype})&{src};") def visit_keep_alive(self, op: KeepAlive) -> None: # This is a no-op. pass + def visit_unborrow(self, op: Unborrow) -> None: + # This is a no-op that propagates the source value. + dest = self.reg(op) + src = self.reg(op.src) + self.emit_line(f"{dest} = {src};") + # Helpers def label(self, label: BasicBlock) -> str: @@ -732,6 +780,15 @@ def reg(self, reg: Value) -> str: elif val <= -(1 << 31): s += "LL" return s + elif isinstance(reg, Float): + r = repr(reg.value) + if r == "inf": + return "INFINITY" + elif r == "-inf": + return "-INFINITY" + elif r == "nan": + return "NAN" + return r else: return self.emitter.reg(reg) @@ -744,8 +801,8 @@ def c_error_value(self, rtype: RType) -> str: def c_undefined_value(self, rtype: RType) -> str: return self.emitter.c_undefined_value(rtype) - def emit_line(self, line: str) -> None: - self.emitter.emit_line(line) + def emit_line(self, line: str, *, ann: object = None) -> None: + self.emitter.emit_line(line, ann=ann) def emit_lines(self, *lines: str) -> None: self.emitter.emit_lines(*lines) diff --git a/mypyc/codegen/emitmodule.py b/mypyc/codegen/emitmodule.py index 9f65aa77c47f..6c8f5ac91335 100644 --- a/mypyc/codegen/emitmodule.py +++ b/mypyc/codegen/emitmodule.py @@ -26,7 +26,7 @@ from mypy.plugin import Plugin, ReportConfigContext from mypy.util import hash_digest from mypyc.codegen.cstring import c_string_initializer -from mypyc.codegen.emit import Emitter, EmitterContext, HeaderDeclaration +from mypyc.codegen.emit import Emitter, EmitterContext, HeaderDeclaration, c_array_initializer from mypyc.codegen.emitclass import generate_class, generate_class_type_decl from mypyc.codegen.emitfunc import generate_native_function, native_function_header from mypyc.codegen.emitwrapper import ( @@ -43,7 +43,6 @@ TOP_LEVEL_NAME, shared_lib_name, short_id_from_name, - use_fastcall, use_vectorcall, ) from mypyc.errors import Errors @@ -51,13 +50,16 @@ from mypyc.ir.func_ir import FuncIR from mypyc.ir.module_ir import ModuleIR, ModuleIRs, deserialize_modules from mypyc.ir.ops import DeserMaps, LoadLiteral -from mypyc.ir.rtypes import RTuple, RType +from mypyc.ir.rtypes import RType from mypyc.irbuild.main import build_ir from mypyc.irbuild.mapper import Mapper from mypyc.irbuild.prepare import load_type_map from mypyc.namegen import NameGenerator, exported_name from mypyc.options import CompilerOptions +from mypyc.transform.copy_propagation import do_copy_propagation from mypyc.transform.exceptions import insert_exception_handling +from mypyc.transform.flag_elimination import do_flag_elimination +from mypyc.transform.lower import lower_ir from mypyc.transform.refcount import insert_ref_count_opcodes from mypyc.transform.uninit import insert_uninit_checks @@ -226,18 +228,19 @@ def compile_scc_to_ir( if errors.num_errors > 0: return modules - # Insert uninit checks. for module in modules.values(): for fn in module.functions: + # Insert uninit checks. insert_uninit_checks(fn) - # Insert exception handling. - for module in modules.values(): - for fn in module.functions: + # Insert exception handling. insert_exception_handling(fn) - # Insert refcount handling. - for module in modules.values(): - for fn in module.functions: + # Insert refcount handling. insert_ref_count_opcodes(fn) + # Switch to lower abstraction level IR. + lower_ir(fn, compiler_options) + # Perform optimizations. + do_copy_propagation(fn, compiler_options) + do_flag_elimination(fn, compiler_options) return modules @@ -296,11 +299,11 @@ def compile_ir_to_c( # compiled into a separate extension module. ctext: dict[str | None, list[tuple[str, str]]] = {} for group_sources, group_name in groups: - group_modules = [ - (source.module, modules[source.module]) + group_modules = { + source.module: modules[source.module] for source in group_sources if source.module in modules - ] + } if not group_modules: ctext[group_name] = [] continue @@ -369,7 +372,7 @@ def write_cache( "src_hashes": hashes[group_map[id]], } - result.manager.metastore.write(newpath, json.dumps(ir_data)) + result.manager.metastore.write(newpath, json.dumps(ir_data, separators=(",", ":"))) result.manager.metastore.commit() @@ -423,10 +426,11 @@ def compile_modules_to_c( ) modules = compile_modules_to_ir(result, mapper, compiler_options, errors) - ctext = compile_ir_to_c(groups, modules, result, mapper, compiler_options) + if errors.num_errors > 0: + return {}, [] - if errors.num_errors == 0: - write_cache(modules, result, group_map, ctext) + ctext = compile_ir_to_c(groups, modules, result, mapper, compiler_options) + write_cache(modules, result, group_map, ctext) return modules, [ctext[name] for _, name in groups] @@ -465,7 +469,7 @@ def group_dir(group_name: str) -> str: class GroupGenerator: def __init__( self, - modules: list[tuple[str, ModuleIR]], + modules: dict[str, ModuleIR], source_paths: dict[str, str], group_name: str | None, group_map: dict[str, str | None], @@ -512,7 +516,7 @@ def generate_c_for_modules(self) -> list[tuple[str, str]]: multi_file = self.use_shared_lib and self.multi_file # Collect all literal refs in IR. - for _, module in self.modules: + for module in self.modules.values(): for fn in module.functions: collect_literals(fn, self.context.literals) @@ -528,7 +532,7 @@ def generate_c_for_modules(self) -> list[tuple[str, str]]: self.generate_literal_tables() - for module_name, module in self.modules: + for module_name, module in self.modules.items(): if multi_file: emitter = Emitter(self.context) emitter.emit_line(f'#include "__native{self.short_group_suffix}.h"') @@ -582,7 +586,7 @@ def generate_c_for_modules(self) -> list[tuple[str, str]]: declarations.emit_line("int CPyGlobalsInit(void);") declarations.emit_line() - for module_name, module in self.modules: + for module_name, module in self.modules.items(): self.declare_finals(module_name, module.final_names, declarations) for cl in module.classes: generate_class_type_decl(cl, emitter, ext_declarations, declarations) @@ -790,7 +794,7 @@ def generate_shared_lib_init(self, emitter: Emitter) -> None: "", ) - for mod, _ in self.modules: + for mod in self.modules: name = exported_name(mod) emitter.emit_lines( f"extern PyObject *CPyInit_{name}(void);", @@ -994,7 +998,7 @@ def _toposort_visit(name: str) -> None: result.append(decl.declaration) decl.mark = True - for name, marked_declaration in marked_declarations.items(): + for name in marked_declarations: _toposort_visit(name) return result @@ -1023,12 +1027,13 @@ def module_internal_static_name(self, module_name: str, emitter: Emitter) -> str return emitter.static_name(module_name + "_internal", None, prefix=MODULE_PREFIX) def declare_module(self, module_name: str, emitter: Emitter) -> None: - # We declare two globals for each module: + # We declare two globals for each compiled module: # one used internally in the implementation of module init to cache results # and prevent infinite recursion in import cycles, and one used # by other modules to refer to it. - internal_static_name = self.module_internal_static_name(module_name, emitter) - self.declare_global("CPyModule *", internal_static_name, initializer="NULL") + if module_name in self.modules: + internal_static_name = self.module_internal_static_name(module_name, emitter) + self.declare_global("CPyModule *", internal_static_name, initializer="NULL") static_name = emitter.static_name(module_name, None, prefix=MODULE_PREFIX) self.declare_global("CPyModule *", static_name) self.simple_inits.append((static_name, "Py_None")) @@ -1051,11 +1056,7 @@ def declare_finals( def final_definition(self, module: str, name: str, typ: RType, emitter: Emitter) -> str: static_name = emitter.static_name(name, module) # Here we rely on the fact that undefined value and error value are always the same - if isinstance(typ, RTuple): - # We need to inline because initializer must be static - undefined = "{{ {} }}".format("".join(emitter.tuple_undefined_value_helper(typ))) - else: - undefined = emitter.c_undefined_value(typ) + undefined = emitter.c_initializer_undefined_value(typ) return f"{emitter.ctype_spaced(typ)}{static_name} = {undefined};" def declare_static_pyobject(self, identifier: str, emitter: Emitter) -> None: @@ -1110,8 +1111,8 @@ def is_fastcall_supported(fn: FuncIR, capi_version: tuple[int, int]) -> bool: # We can use vectorcalls (PEP 590) when supported return use_vectorcall(capi_version) # TODO: Support fastcall for __init__. - return use_fastcall(capi_version) and fn.name != "__init__" - return use_fastcall(capi_version) + return fn.name != "__init__" + return True def collect_literals(fn: FuncIR, literals: Literals) -> None: @@ -1126,37 +1127,6 @@ def collect_literals(fn: FuncIR, literals: Literals) -> None: literals.record_literal(op.value) -def c_array_initializer(components: list[str]) -> str: - """Construct an initializer for a C array variable. - - Components are C expressions valid in an initializer. - - For example, if components are ["1", "2"], the result - would be "{1, 2}", which can be used like this: - - int a[] = {1, 2}; - - If the result is long, split it into multiple lines. - """ - res = [] - current: list[str] = [] - cur_len = 0 - for c in components: - if not current or cur_len + 2 + len(c) < 70: - current.append(c) - cur_len += len(c) + 2 - else: - res.append(", ".join(current)) - current = [c] - cur_len = len(c) - if not res: - # Result fits on a single line - return "{%s}" % ", ".join(current) - # Multi-line result - res.append(", ".join(current)) - return "{\n " + ",\n ".join(res) + "\n}" - - def c_string_array_initializer(components: list[bytes]) -> str: result = [] result.append("{\n") diff --git a/mypyc/codegen/emitwrapper.py b/mypyc/codegen/emitwrapper.py index ed03bb7948cc..45c6c7a05867 100644 --- a/mypyc/codegen/emitwrapper.py +++ b/mypyc/codegen/emitwrapper.py @@ -145,7 +145,7 @@ def generate_wrapper_function( real_args = list(fn.args) if fn.sig.num_bitmap_args: real_args = real_args[: -fn.sig.num_bitmap_args] - if fn.class_name and not fn.decl.kind == FUNC_STATICMETHOD: + if fn.class_name and fn.decl.kind != FUNC_STATICMETHOD: arg = real_args.pop(0) emitter.emit_line(f"PyObject *obj_{arg.name} = self;") @@ -179,7 +179,7 @@ def generate_wrapper_function( nargs = "nargs" parse_fn = "CPyArg_ParseStackAndKeywords" # Special case some common signatures - if len(real_args) == 0: + if not real_args: # No args parse_fn = "CPyArg_ParseStackAndKeywordsNoArgs" elif len(real_args) == 1 and len(groups[ARG_POS]) == 1: @@ -238,7 +238,7 @@ def generate_legacy_wrapper_function( real_args = list(fn.args) if fn.sig.num_bitmap_args: real_args = real_args[: -fn.sig.num_bitmap_args] - if fn.class_name and not fn.decl.kind == FUNC_STATICMETHOD: + if fn.class_name and fn.decl.kind != FUNC_STATICMETHOD: arg = real_args.pop(0) emitter.emit_line(f"PyObject *obj_{arg.name} = self;") diff --git a/mypyc/codegen/literals.py b/mypyc/codegen/literals.py index 05884b754452..1f0c3bc6ec7b 100644 --- a/mypyc/codegen/literals.py +++ b/mypyc/codegen/literals.py @@ -1,7 +1,7 @@ from __future__ import annotations -from typing import Any, FrozenSet, List, Tuple, Union, cast -from typing_extensions import Final +from typing import Final, FrozenSet, Tuple, Union +from typing_extensions import TypeGuard # Supported Python literal types. All tuple / frozenset items must have supported # literal types as well, but we can't represent the type precisely. @@ -9,6 +9,11 @@ str, bytes, int, bool, float, complex, Tuple[object, ...], FrozenSet[object], None ] + +def _is_literal_value(obj: object) -> TypeGuard[LiteralValue]: + return isinstance(obj, (str, bytes, int, float, complex, tuple, frozenset, type(None))) + + # Some literals are singletons and handled specially (None, False and True) NUM_SINGLETONS: Final = 3 @@ -55,13 +60,15 @@ def record_literal(self, value: LiteralValue) -> None: tuple_literals = self.tuple_literals if value not in tuple_literals: for item in value: - self.record_literal(cast(Any, item)) + assert _is_literal_value(item) + self.record_literal(item) tuple_literals[value] = len(tuple_literals) elif isinstance(value, frozenset): frozenset_literals = self.frozenset_literals if value not in frozenset_literals: for item in value: - self.record_literal(cast(Any, item)) + assert _is_literal_value(item) + self.record_literal(item) frozenset_literals[value] = len(frozenset_literals) else: assert False, "invalid literal: %r" % value @@ -133,7 +140,7 @@ def encoded_complex_values(self) -> list[str]: def encoded_tuple_values(self) -> list[str]: return self._encode_collection_values(self.tuple_literals) - def encoded_frozenset_values(self) -> List[str]: + def encoded_frozenset_values(self) -> list[str]: return self._encode_collection_values(self.frozenset_literals) def _encode_collection_values( @@ -159,7 +166,8 @@ def _encode_collection_values( value = value_by_index[i] result.append(str(len(value))) for item in value: - index = self.literal_index(cast(Any, item)) + assert _is_literal_value(item) + index = self.literal_index(item) result.append(str(index)) return result @@ -257,6 +265,8 @@ def float_to_c(x: float) -> str: return "INFINITY" elif s == "-inf": return "-INFINITY" + elif s == "nan": + return "NAN" return s diff --git a/mypyc/common.py b/mypyc/common.py index c8da5ff63bab..3d07f6c3d0d3 100644 --- a/mypyc/common.py +++ b/mypyc/common.py @@ -2,8 +2,7 @@ import sys import sysconfig -from typing import Any, Dict -from typing_extensions import Final +from typing import Any, Dict, Final from mypy.util import unnamed_function @@ -69,6 +68,7 @@ "getargs.c", "getargsfast.c", "int_ops.c", + "float_ops.c", "str_ops.c", "bytes_ops.c", "list_ops.c", @@ -98,11 +98,6 @@ def short_name(name: str) -> str: return name -def use_fastcall(capi_version: tuple[int, int]) -> bool: - # We can use METH_FASTCALL for faster wrapper functions on Python 3.7+. - return capi_version >= (3, 7) - - def use_vectorcall(capi_version: tuple[int, int]) -> bool: # We can use vectorcalls to make calls on Python 3.8+ (PEP 590). return capi_version >= (3, 8) diff --git a/mypyc/doc/differences_from_python.rst b/mypyc/doc/differences_from_python.rst index 16faae60303f..f1d4d05a3a87 100644 --- a/mypyc/doc/differences_from_python.rst +++ b/mypyc/doc/differences_from_python.rst @@ -268,19 +268,27 @@ used in compiled code, or there are some limitations. You can partially work around some of these limitations by running your code in interpreted mode. -Operator overloading -******************** +Nested classes +************** -Native classes can only use these dunder methods to override operators: +Nested classes are not supported. -* ``__eq__`` -* ``__ne__`` -* ``__getitem__`` -* ``__setitem__`` +Conditional functions or classes +******************************** -.. note:: +Function and class definitions guarded by an if-statement are not supported. + +Dunder methods +************** - This limitation will be lifted in the future. +Native classes **cannot** use these dunders. If defined, they will not +work as expected. + +* ``__del__`` +* ``__index__`` +* ``__getattr__``, ``__getattribute__`` +* ``__setattr__`` +* ``__delattr__`` Generator expressions ********************* @@ -299,10 +307,16 @@ Descriptors Native classes can't contain arbitrary descriptors. Properties, static methods and class methods are supported. -Stack introspection -******************* +Introspection +************* + +Various methods of introspection may break by using mypyc. Here's an +non-exhaustive list of what won't work: -Frames of compiled functions can't be inspected using ``inspect``. +- Instance ``__annotations__`` is usually not kept +- Frames of compiled functions can't be inspected using ``inspect`` +- Compiled methods aren't considered methods by ``inspect.ismethod`` +- ``inspect.signature`` chokes on compiled functions Profiling hooks and tracing *************************** diff --git a/mypyc/doc/float_operations.rst b/mypyc/doc/float_operations.rst index c1e4d284c4ba..feae5a806c70 100644 --- a/mypyc/doc/float_operations.rst +++ b/mypyc/doc/float_operations.rst @@ -7,18 +7,44 @@ These ``float`` operations have fast, optimized implementations. Other floating point operations use generic implementations that are often slower. -.. note:: - - At the moment, only a few float operations are optimized. This will - improve in future mypyc releases. - Construction ------------ * Float literal -* ``float(string)`` +* ``float(x: int)`` +* ``float(x: i64)`` +* ``float(x: i32)`` +* ``float(x: i16)`` +* ``float(x: u8)`` +* ``float(x: str)`` +* ``float(x: float)`` (no-op) + +Operators +--------- + +* Arithmetic (``+``, ``-``, ``*``, ``/``, ``//``, ``%``) +* Comparisons (``==``, ``!=``, ``<``, etc.) +* Augmented assignment (``x += y``, etc.) Functions --------- +* ``int(f)`` +* ``i64(f)`` (convert to 64-bit signed integer) +* ``i32(f)`` (convert to 32-bit signed integer) +* ``i16(f)`` (convert to 16-bit signed integer) +* ``u8(f)`` (convert to 8-bit unsigned integer) * ``abs(f)`` +* ``math.sin(f)`` +* ``math.cos(f)`` +* ``math.tan(f)`` +* ``math.sqrt(f)`` +* ``math.exp(f)`` +* ``math.log(f)`` +* ``math.floor(f)`` +* ``math.ceil(f)`` +* ``math.fabs(f)`` +* ``math.pow(x, y)`` +* ``math.copysign(x, y)`` +* ``math.isinf(f)`` +* ``math.isnan(f)`` diff --git a/mypyc/doc/getting_started.rst b/mypyc/doc/getting_started.rst index 8d3bf5bba662..adc617419ffa 100644 --- a/mypyc/doc/getting_started.rst +++ b/mypyc/doc/getting_started.rst @@ -32,23 +32,23 @@ Ubuntu 18.04, for example: Windows ******* -Install `Visual C++ `_. +From `Build Tools for Visual Studio 2022 `_, install MSVC C++ build tools for your architecture and a Windows SDK. (latest versions recommended) Installation ------------ Mypyc is shipped as part of the mypy distribution. Install mypy like -this (you need Python 3.5 or later): +this (you need Python 3.8 or later): .. code-block:: - $ python3 -m pip install -U mypy + $ python3 -m pip install -U 'mypy[mypyc]' On some systems you need to use this instead: .. code-block:: - $ python -m pip install -U mypy + $ python -m pip install -U 'mypy[mypyc]' Example program --------------- diff --git a/mypyc/doc/int_operations.rst b/mypyc/doc/int_operations.rst index 038b6e5dbc63..eb875f5c9452 100644 --- a/mypyc/doc/int_operations.rst +++ b/mypyc/doc/int_operations.rst @@ -3,32 +3,160 @@ Native integer operations ========================= -Operations on ``int`` values that are listed here have fast, optimized +Mypyc supports these integer types: + +* ``int`` (arbitrary-precision integer) +* ``i64`` (64-bit signed integer) +* ``i32`` (32-bit signed integer) +* ``i16`` (16-bit signed integer) +* ``u8`` (8-bit unsigned integer) + +``i64``, ``i32``, ``i16`` and ``u8`` are *native integer types* and +are available in the ``mypy_extensions`` module. ``int`` corresponds +to the Python ``int`` type, but uses a more efficient runtime +representation (tagged pointer). Native integer types are value types. + +All integer types have optimized primitive operations, but the native +integer types are more efficient than ``int``, since they don't +require range or bounds checks. + +Operations on integers that are listed here have fast, optimized implementations. Other integer operations use generic implementations -that are often slower. Some operations involving integers and other -types are documented elsewhere, such as list indexing. +that are generally slower. Some operations involving integers and other +types, such as list indexing, are documented elsewhere. Construction ------------ +``int`` type: + * Integer literal * ``int(x: float)`` +* ``int(x: i64)`` +* ``int(x: i32)`` +* ``int(x: i16)`` +* ``int(x: u8)`` * ``int(x: str)`` * ``int(x: str, base: int)`` +* ``int(x: int)`` (no-op) + +``i64`` type: + +* ``i64(x: int)`` +* ``i64(x: float)`` +* ``i64(x: i64)`` (no-op) +* ``i64(x: i32)`` +* ``i64(x: i16)`` +* ``i64(x: u8)`` +* ``i64(x: str)`` +* ``i64(x: str, base: int)`` + +``i32`` type: + +* ``i32(x: int)`` +* ``i32(x: float)`` +* ``i32(x: i64)`` (truncate) +* ``i32(x: i32)`` (no-op) +* ``i32(x: i16)`` +* ``i32(x: u8)`` +* ``i32(x: str)`` +* ``i32(x: str, base: int)`` + +``i16`` type: + +* ``i16(x: int)`` +* ``i16(x: float)`` +* ``i16(x: i64)`` (truncate) +* ``i16(x: i32)`` (truncate) +* ``i16(x: i16)`` (no-op) +* ``i16(x: u8)`` +* ``i16(x: str)`` +* ``i16(x: str, base: int)`` + +Conversions from ``int`` to a native integer type raise +``OverflowError`` if the value is too large or small. Conversions from +a wider native integer type to a narrower one truncate the value and never +fail. More generally, operations between native integer types don't +check for overflow. + +Implicit conversions +-------------------- + +``int`` values can be implicitly converted to a native integer type, +for convenience. This means that these are equivalent:: + + from mypy_extensions import i64 + + def implicit() -> None: + # Implicit conversion of 0 (int) to i64 + x: i64 = 0 + + def explicit() -> None: + # Explicit conversion of 0 (int) to i64 + x = i64(0) + +Similarly, a native integer value can be implicitly converted to an +arbitrary-precision integer. These two functions are equivalent:: + + def implicit(x: i64) -> int: + # Implicit conversion from i64 to int + return x + + def explicit(x: i64) -> int: + # Explicit conversion from i64 to int + return int(x) Operators --------- -* Arithmetic (``+``, ``-``, ``*``, ``//``, ``%``) +* Arithmetic (``+``, ``-``, ``*``, ``//``, ``/``, ``%``) * Bitwise operations (``&``, ``|``, ``^``, ``<<``, ``>>``, ``~``) * Comparisons (``==``, ``!=``, ``<``, etc.) * Augmented assignment (``x += y``, etc.) +If one of the above native integer operations overflows or underflows +with signed operands, the behavior is undefined. Signed native integer +types should only be used if all possible values are small enough for +the type. For this reason, the arbitrary-precision ``int`` type is +recommended for signed values unless the performance of integer +operations is critical. + +Operations on unsigned integers (``u8``) wrap around on overflow. + +It's a compile-time error to mix different native integer types in a +binary operation such as addition. An explicit conversion is required:: + + from mypy_extensions import i64, i32 + + def add(x: i64, y: i32) -> None: + a = x + y # Error (i64 + i32) + b = x + i64(y) # OK + +You can freely mix a native integer value and an arbitrary-precision +``int`` value in an operation. The native integer type is "sticky" +and the ``int`` operand is coerced to the native integer type:: + + def example(x: i64, y: int) -> None: + a = x * y + # Type of "a" is "i64" + ... + b = 1 - x + # Similarly, type of "b" is "i64" + Statements ---------- -For loop over range: +For loop over a range is compiled efficiently, if the ``range(...)`` object +is constructed in the for statement (after ``in``): * ``for x in range(end)`` * ``for x in range(start, end)`` * ``for x in range(start, end, step)`` + +If one of the arguments to ``range`` in a for loop is a native integer +type, the type of the loop variable is inferred to have this native +integer type, instead of ``int``:: + + for x in range(i64(n)): + # Type of "x" is "i64" + ... diff --git a/mypyc/doc/introduction.rst b/mypyc/doc/introduction.rst index 874071bac23f..53c86ecdab1b 100644 --- a/mypyc/doc/introduction.rst +++ b/mypyc/doc/introduction.rst @@ -10,7 +10,7 @@ The compiled language is a strict, *gradually typed* Python variant. It restricts the use of some dynamic Python features to gain performance, but it's mostly compatible with standard Python. -Mypyc uses `mypy `_ to perform type +Mypyc uses `mypy `_ to perform type checking and type inference. Most type system features in the stdlib `typing `_ module are supported. diff --git a/mypyc/doc/make.bat b/mypyc/doc/make.bat index 2119f51099bf..153be5e2f6f9 100644 --- a/mypyc/doc/make.bat +++ b/mypyc/doc/make.bat @@ -21,7 +21,7 @@ if errorlevel 9009 ( echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ + echo.https://www.sphinx-doc.org/ exit /b 1 ) diff --git a/mypyc/doc/native_classes.rst b/mypyc/doc/native_classes.rst index 2b4a0892b790..b2935a6f7185 100644 --- a/mypyc/doc/native_classes.rst +++ b/mypyc/doc/native_classes.rst @@ -63,6 +63,8 @@ classes: * ``IndexError`` * ``LookupError`` * ``UserWarning`` +* ``typing.NamedTuple`` +* ``enum.Enum`` By default, a non-native class can't inherit a native class, and you can't inherit from a native class outside the compilation unit that @@ -104,6 +106,11 @@ through an instance. Example:: print(o.cv) # OK (2) o.cv = 3 # Error! +.. tip:: + + Constant class variables can be declared using ``typing.Final`` or + ``typing.Final[]``. + Generic native classes ---------------------- @@ -150,9 +157,10 @@ decorators can be used with native classes, however: * ``mypy_extensions.trait`` (for defining :ref:`trait types `) * ``mypy_extensions.mypyc_attr`` (see :ref:`above `) * ``dataclasses.dataclass`` +* ``@attr.s(auto_attribs=True)`` -Dataclasses have partial native support, and they aren't as efficient -as pure native classes. +Dataclasses and attrs classes have partial native support, and they aren't as +efficient as pure native classes. .. note:: diff --git a/mypyc/doc/performance_tips_and_tricks.rst b/mypyc/doc/performance_tips_and_tricks.rst index 668d32827402..ae0b2950814c 100644 --- a/mypyc/doc/performance_tips_and_tricks.rst +++ b/mypyc/doc/performance_tips_and_tricks.rst @@ -103,8 +103,6 @@ These things also tend to be relatively slow: * Using generator functions -* Using floating point numbers (they are relatively unoptimized) - * Using callable values (i.e. not leveraging early binding to call functions or methods) @@ -160,6 +158,8 @@ Here are examples of features that are fast, in no particular order * Many integer operations +* Many ``float`` operations + * Booleans * :ref:`Native list operations `, such as indexing, diff --git a/mypyc/doc/using_type_annotations.rst b/mypyc/doc/using_type_annotations.rst index a01246ab0914..04c923819d54 100644 --- a/mypyc/doc/using_type_annotations.rst +++ b/mypyc/doc/using_type_annotations.rst @@ -30,6 +30,10 @@ mypyc, and many operations on these types have efficient implementations: * ``int`` (:ref:`native operations `) +* ``i64`` (:ref:`documentation `, :ref:`native operations `) +* ``i32`` (:ref:`documentation `, :ref:`native operations `) +* ``i16`` (:ref:`documentation `, :ref:`native operations `) +* ``u8`` (:ref:`documentation `, :ref:`native operations `) * ``float`` (:ref:`native operations `) * ``bool`` (:ref:`native operations `) * ``str`` (:ref:`native operations `) @@ -191,8 +195,8 @@ Traits have some special properties: * You shouldn't create instances of traits (though mypyc does not prevent it yet). -* Traits can subclass other traits, but they can't subclass non-trait - classes (other than ``object``). +* Traits can subclass other traits or native classes, but the MRO must be + linear (just like with native classes). * Accessing methods or attributes through a trait type is somewhat less efficient than through a native class type, but this is much @@ -271,7 +275,8 @@ Value and heap types In CPython, memory for all objects is dynamically allocated on the heap. All Python types are thus *heap types*. In compiled code, some types are *value types* -- no object is (necessarily) allocated on the -heap. ``bool``, ``None`` and fixed-length tuples are value types. +heap. ``bool``, ``float``, ``None``, :ref:`native integer types ` +and fixed-length tuples are value types. ``int`` is a hybrid. For typical integer values, it is a value type. Large enough integer values, those that require more than 63 @@ -287,9 +292,9 @@ Value types have a few differences from heap types: * Similarly, mypyc transparently changes from a heap-based representation to a value representation (unboxing). -* Object identity of integers and tuples is not preserved. You should - use ``==`` instead of ``is`` if you are comparing two integers or - fixed-length tuples. +* Object identity of integers, floating point values and tuples is not + preserved. You should use ``==`` instead of ``is`` if you are comparing + two integers, floats or fixed-length tuples. * When an instance of a subclass of a value type is converted to the base type, it is implicitly converted to an instance of the target @@ -312,3 +317,82 @@ Example:: x = a[0] # True is converted to 1 on assignment x = True + +Since integers and floating point values have a different runtime +representations and neither can represent all the values of the other +type, type narrowing of floating point values through assignment is +disallowed in compiled code. For consistency, mypyc rejects assigning +an integer value to a float variable even in variable initialization. +An explicit conversion is required. + +Examples:: + + def narrowing(n: int) -> None: + # Error: Incompatible value representations in assignment + # (expression has type "int", variable has type "float") + x: float = 0 + + y: float = 0.0 # Ok + + if f(): + y = n # Error + if f(): + y = float(n) # Ok + +.. _native-ints: + +Native integer types +-------------------- + +You can use the native integer types ``i64`` (64-bit signed integer), +``i32`` (32-bit signed integer), ``i16`` (16-bit signed integer), and +``u8`` (8-bit unsigned integer) if you know that integer values will +always fit within fixed bounds. These types are faster than the +arbitrary-precision ``int`` type, since they don't require overflow +checks on operations. They may also use less memory than ``int`` +values. The types are imported from the ``mypy_extensions`` module +(installed via ``pip install mypy_extensions``). + +Example:: + + from mypy_extensions import i64 + + def sum_list(l: list[i64]) -> i64: + s: i64 = 0 + for n in l: + s += n + return s + + # Implicit conversions from int to i64 + print(sum_list([1, 3, 5])) + +.. note:: + + Since there are no overflow checks when performing native integer + arithmetic, the above function could result in an overflow or other + undefined behavior if the sum might not fit within 64 bits. + + The behavior when running as interpreted Python program will be + different if there are overflows. Declaring native integer types + have no effect unless code is compiled. Native integer types are + effectively equivalent to ``int`` when interpreted. + +Native integer types have these additional properties: + +* Values can be implicitly converted between ``int`` and a native + integer type (both ways). + +* Conversions between different native integer types must be explicit. + A conversion to a narrower native integer type truncates the value + without a runtime overflow check. + +* If a binary operation (such as ``+``) or an augmented assignment + (such as ``+=``) mixes native integer and ``int`` values, the + ``int`` operand is implicitly coerced to the native integer type + (native integer types are "sticky"). + +* You can't mix different native integer types in binary + operations. Instead, convert between types explicitly. + +For more information about native integer types, refer to +:ref:`native integer operations `. diff --git a/mypyc/errors.py b/mypyc/errors.py index 1dd269fe25f3..8bc9b2714f75 100644 --- a/mypyc/errors.py +++ b/mypyc/errors.py @@ -1,13 +1,14 @@ from __future__ import annotations import mypy.errors +from mypy.options import Options class Errors: - def __init__(self) -> None: + def __init__(self, options: Options) -> None: self.num_errors = 0 self.num_warnings = 0 - self._errors = mypy.errors.Errors(hide_error_codes=True) + self._errors = mypy.errors.Errors(options, hide_error_codes=True) def error(self, msg: str, path: str, line: int) -> None: self._errors.report(line, None, msg, severity="error", file=path) diff --git a/mypyc/ir/class_ir.py b/mypyc/ir/class_ir.py index a1534780b79b..18f3cbcff987 100644 --- a/mypyc/ir/class_ir.py +++ b/mypyc/ir/class_ir.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import List, NamedTuple, Optional +from typing import List, NamedTuple from mypyc.common import PROPSET_PREFIX, JsonDict from mypyc.ir.func_ir import FuncDecl, FuncIR, FuncSignature @@ -70,10 +70,10 @@ class VTableMethod(NamedTuple): - cls: "ClassIR" + cls: "ClassIR" # noqa: UP037 name: str method: FuncIR - shadow_method: Optional[FuncIR] + shadow_method: FuncIR | None VTableEntries = List[VTableMethod] @@ -169,7 +169,9 @@ def __init__( self.base_mro: list[ClassIR] = [self] # Direct subclasses of this class (use subclasses() to also include non-direct ones) - # None if separate compilation prevents this from working + # None if separate compilation prevents this from working. + # + # Often it's better to use has_no_subclasses() or subclasses() instead. self.children: list[ClassIR] | None = [] # Instance attributes that are initialized in the class body. @@ -190,7 +192,7 @@ def __init__( # bitmap for types such as native ints that can't have a dedicated error # value that doesn't overlap a valid value. The bitmap is used if the # value of an attribute is the same as the error value. - self.bitmap_attrs: List[str] = [] + self.bitmap_attrs: list[str] = [] def __repr__(self) -> str: return ( @@ -301,6 +303,9 @@ def get_method(self, name: str, *, prefer_method: bool = False) -> FuncIR | None def has_method_decl(self, name: str) -> bool: return any(name in ir.method_decls for ir in self.mro) + def has_no_subclasses(self) -> bool: + return self.children == [] and not self.allow_interpreted_subclasses + def subclasses(self) -> set[ClassIR] | None: """Return all subclasses of this class, both direct and indirect. @@ -378,9 +383,9 @@ def serialize(self) -> JsonDict: "traits": [cir.fullname for cir in self.traits], "mro": [cir.fullname for cir in self.mro], "base_mro": [cir.fullname for cir in self.base_mro], - "children": [cir.fullname for cir in self.children] - if self.children is not None - else None, + "children": ( + [cir.fullname for cir in self.children] if self.children is not None else None + ), "deletable": self.deletable, "attrs_with_defaults": sorted(self.attrs_with_defaults), "_always_initialized_attrs": sorted(self._always_initialized_attrs), diff --git a/mypyc/ir/func_ir.py b/mypyc/ir/func_ir.py index dbb45fc7ec29..44847c7bb0b3 100644 --- a/mypyc/ir/func_ir.py +++ b/mypyc/ir/func_ir.py @@ -2,8 +2,7 @@ from __future__ import annotations -from typing import Sequence -from typing_extensions import Final +from typing import Final, Sequence from mypy.nodes import ARG_POS, ArgKind, Block, FuncDef from mypyc.common import BITMAP_BITS, JsonDict, bitmap_name, get_id_from_name, short_id_from_name @@ -86,7 +85,7 @@ def real_args(self) -> tuple[RuntimeArg, ...]: return self.args[: -self.num_bitmap_args] return self.args - def bound_sig(self) -> "FuncSignature": + def bound_sig(self) -> FuncSignature: if self.num_bitmap_args: return FuncSignature(self.args[1 : -self.num_bitmap_args], self.ret_type) else: diff --git a/mypyc/ir/module_ir.py b/mypyc/ir/module_ir.py index 4b6a177af149..dcf6f8768547 100644 --- a/mypyc/ir/module_ir.py +++ b/mypyc/ir/module_ir.py @@ -23,7 +23,7 @@ def __init__( final_names: list[tuple[str, RType]], ) -> None: self.fullname = fullname - self.imports = imports[:] + self.imports = imports.copy() self.functions = functions self.classes = classes self.final_names = final_names diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index 51a0bffcf3f1..377266e797d9 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -12,8 +12,7 @@ from __future__ import annotations from abc import abstractmethod -from typing import TYPE_CHECKING, Dict, Generic, List, NamedTuple, Sequence, TypeVar, Union -from typing_extensions import Final +from typing import TYPE_CHECKING, Final, Generic, List, NamedTuple, Sequence, TypeVar, Union from mypy_extensions import trait @@ -25,6 +24,7 @@ RVoid, bit_rprimitive, bool_rprimitive, + float_rprimitive, int_rprimitive, is_bit_rprimitive, is_bool_rprimitive, @@ -80,6 +80,7 @@ def __init__(self, label: int = -1) -> None: self.label = label self.ops: list[Op] = [] self.error_handler: BasicBlock | None = None + self.referenced = False @property def terminated(self) -> bool: @@ -190,6 +191,25 @@ def __init__(self, value: int, rtype: RType = short_int_rprimitive, line: int = self.type = rtype self.line = line + def numeric_value(self) -> int: + if is_short_int_rprimitive(self.type) or is_int_rprimitive(self.type): + return self.value // 2 + return self.value + + +class Float(Value): + """Float literal. + + Floating point literals are treated as constant values and are generally + not included in data flow analyses and such, unlike Register and + Op subclasses. + """ + + def __init__(self, value: float, line: int = -1) -> None: + self.value = value + self.type = float_rprimitive + self.line = line + class Op(Value): """Abstract base class for all IR operations. @@ -279,7 +299,7 @@ def __init__(self, dest: Register, src: list[Value], line: int = -1) -> None: self.src = src def sources(self) -> list[Value]: - return self.src[:] + return self.src.copy() def stolen(self) -> list[Value]: return [] @@ -522,7 +542,7 @@ def __init__(self, fn: FuncDecl, args: Sequence[Value], line: int) -> None: super().__init__(line) def sources(self) -> list[Value]: - return list(self.args[:]) + return list(self.args.copy()) def accept(self, visitor: OpVisitor[T]) -> T: return visitor.visit_call(self) @@ -550,12 +570,98 @@ def __init__(self, obj: Value, method: str, args: list[Value], line: int = -1) - super().__init__(line) def sources(self) -> list[Value]: - return self.args[:] + [self.obj] + return self.args.copy() + [self.obj] def accept(self, visitor: OpVisitor[T]) -> T: return visitor.visit_method_call(self) +class PrimitiveDescription: + """Description of a primitive op. + + Primitives get lowered into lower-level ops before code generation. + + If c_function_name is provided, a primitive will be lowered into a CallC op. + Otherwise custom logic will need to be implemented to transform the + primitive into lower-level ops. + """ + + def __init__( + self, + name: str, + arg_types: list[RType], + return_type: RType, # TODO: What about generic? + var_arg_type: RType | None, + truncated_type: RType | None, + c_function_name: str | None, + error_kind: int, + steals: StealsDescription, + is_borrowed: bool, + ordering: list[int] | None, + extra_int_constants: list[tuple[int, RType]], + priority: int, + is_pure: bool, + ) -> None: + # Each primitive much have a distinct name, but otherwise they are arbitrary. + self.name: Final = name + self.arg_types: Final = arg_types + self.return_type: Final = return_type + self.var_arg_type: Final = var_arg_type + self.truncated_type: Final = truncated_type + # If non-None, this will map to a call of a C helper function; if None, + # there must be a custom handler function that gets invoked during the lowering + # pass to generate low-level IR for the primitive (in the mypyc.lower package) + self.c_function_name: Final = c_function_name + self.error_kind: Final = error_kind + self.steals: Final = steals + self.is_borrowed: Final = is_borrowed + self.ordering: Final = ordering + self.extra_int_constants: Final = extra_int_constants + self.priority: Final = priority + # Pure primitives have no side effects, take immutable arguments, and + # never fail. They support additional optimizations. + self.is_pure: Final = is_pure + if is_pure: + assert error_kind == ERR_NEVER + + def __repr__(self) -> str: + return f"" + + +class PrimitiveOp(RegisterOp): + """A higher-level primitive operation. + + Some of these have special compiler support. These will be lowered + (transformed) into lower-level IR ops before code generation, and after + reference counting op insertion. Others will be transformed into CallC + ops. + + Tagged integer equality is a typical primitive op with non-trivial + lowering. It gets transformed into a tag check, followed by different + code paths for short and long representations. + """ + + def __init__(self, args: list[Value], desc: PrimitiveDescription, line: int = -1) -> None: + self.args = args + self.type = desc.return_type + self.error_kind = desc.error_kind + self.desc = desc + + def sources(self) -> list[Value]: + return self.args + + def stolen(self) -> list[Value]: + steals = self.desc.steals + if isinstance(steals, list): + assert len(steals) == len(self.args) + return [arg for arg, steal in zip(self.args, steals) if steal] + else: + return [] if not steals else self.sources() + + def accept(self, visitor: OpVisitor[T]) -> T: + return visitor.visit_primitive_op(self) + + class LoadErrorValue(RegisterOp): """Load an error value. @@ -770,7 +876,10 @@ def __init__(self, items: list[Value], line: int) -> None: self.type = self.tuple_type def sources(self) -> list[Value]: - return self.items[:] + return self.items.copy() + + def stolen(self) -> list[Value]: + return self.items.copy() def accept(self, visitor: OpVisitor[T]) -> T: return visitor.visit_tuple_set(self) @@ -781,13 +890,14 @@ class TupleGet(RegisterOp): error_kind = ERR_NEVER - def __init__(self, src: Value, index: int, line: int = -1) -> None: + def __init__(self, src: Value, index: int, line: int = -1, *, borrow: bool = False) -> None: super().__init__(line) self.src = src self.index = index assert isinstance(src.type, RTuple), "TupleGet only operates on tuples" assert index >= 0 self.type = src.type.types[index] + self.is_borrowed = borrow def sources(self) -> list[Value]: return [self.src] @@ -895,6 +1005,7 @@ class RaiseStandardError(RegisterOp): UNBOUND_LOCAL_ERROR: Final = "UnboundLocalError" RUNTIME_ERROR: Final = "RuntimeError" NAME_ERROR: Final = "NameError" + ZERO_DIVISION_ERROR: Final = "ZeroDivisionError" def __init__(self, class_name: str, value: str | Value | None, line: int) -> None: super().__init__(line) @@ -931,6 +1042,8 @@ def __init__( error_kind: int, line: int, var_arg_idx: int = -1, + *, + is_pure: bool = False, ) -> None: self.error_kind = error_kind super().__init__(line) @@ -941,6 +1054,12 @@ def __init__( self.is_borrowed = is_borrowed # The position of the first variable argument in args (if >= 0) self.var_arg_idx = var_arg_idx + # Is the function pure? Pure functions have no side effects + # and all the arguments are immutable. Pure functions support + # additional optimizations. Pure functions never fail. + self.is_pure = is_pure + if is_pure: + assert error_kind == ERR_NEVER def sources(self) -> list[Value]: return self.args @@ -1042,7 +1161,7 @@ class IntOp(RegisterOp): """Binary arithmetic or bitwise op on integer operands (e.g., r1 = r2 + r3). These ops are low-level and are similar to the corresponding C - operations (and unlike Python operations). + operations. The left and right values must have low-level integer types with compatible representations. Fixed-width integers, short_int_rprimitive, @@ -1141,6 +1260,7 @@ class ComparisonOp(RegisterOp): } signed_ops: Final = {"==": EQ, "!=": NEQ, "<": SLT, ">": SGT, "<=": SLE, ">=": SGE} + unsigned_ops: Final = {"==": EQ, "!=": NEQ, "<": ULT, ">": UGT, "<=": ULE, ">=": UGE} def __init__(self, lhs: Value, rhs: Value, op: int, line: int = -1) -> None: super().__init__(line) @@ -1156,6 +1276,94 @@ def accept(self, visitor: OpVisitor[T]) -> T: return visitor.visit_comparison_op(self) +class FloatOp(RegisterOp): + """Binary float arithmetic op (e.g., r1 = r2 + r3). + + These ops are low-level and are similar to the corresponding C + operations (and somewhat different from Python operations). + + The left and right values must be floats. + """ + + error_kind = ERR_NEVER + + ADD: Final = 0 + SUB: Final = 1 + MUL: Final = 2 + DIV: Final = 3 + MOD: Final = 4 + + op_str: Final = {ADD: "+", SUB: "-", MUL: "*", DIV: "/", MOD: "%"} + + def __init__(self, lhs: Value, rhs: Value, op: int, line: int = -1) -> None: + super().__init__(line) + self.type = float_rprimitive + self.lhs = lhs + self.rhs = rhs + self.op = op + + def sources(self) -> list[Value]: + return [self.lhs, self.rhs] + + def accept(self, visitor: OpVisitor[T]) -> T: + return visitor.visit_float_op(self) + + +# We can't have this in the FloatOp class body, because of +# https://github.com/mypyc/mypyc/issues/932. +float_op_to_id: Final = {op: op_id for op_id, op in FloatOp.op_str.items()} + + +class FloatNeg(RegisterOp): + """Float negation op (r1 = -r2).""" + + error_kind = ERR_NEVER + + def __init__(self, src: Value, line: int = -1) -> None: + super().__init__(line) + self.type = float_rprimitive + self.src = src + + def sources(self) -> list[Value]: + return [self.src] + + def accept(self, visitor: OpVisitor[T]) -> T: + return visitor.visit_float_neg(self) + + +class FloatComparisonOp(RegisterOp): + """Low-level comparison op for floats.""" + + error_kind = ERR_NEVER + + EQ: Final = 200 + NEQ: Final = 201 + LT: Final = 202 + GT: Final = 203 + LE: Final = 204 + GE: Final = 205 + + op_str: Final = {EQ: "==", NEQ: "!=", LT: "<", GT: ">", LE: "<=", GE: ">="} + + def __init__(self, lhs: Value, rhs: Value, op: int, line: int = -1) -> None: + super().__init__(line) + self.type = bit_rprimitive + self.lhs = lhs + self.rhs = rhs + self.op = op + + def sources(self) -> list[Value]: + return [self.lhs, self.rhs] + + def accept(self, visitor: OpVisitor[T]) -> T: + return visitor.visit_float_comparison_op(self) + + +# We can't have this in the FloatOp class body, because of +# https://github.com/mypyc/mypyc/issues/932. +float_comparison_op_to_id: Final = {op: op_id for op_id, op in FloatComparisonOp.op_str.items()} + + class LoadMem(RegisterOp): """Read a memory location: result = *(type *)src. @@ -1239,13 +1447,14 @@ class LoadAddress(RegisterOp): Attributes: type: Type of the loaded address(e.g. ptr/object_ptr) src: Source value (str for globals like 'PyList_Type', - Register for temporary values or locals) + Register for temporary values or locals, LoadStatic + for statics.) """ error_kind = ERR_NEVER is_borrowed = True - def __init__(self, type: RType, src: str | Register, line: int = -1) -> None: + def __init__(self, type: RType, src: str | Register | LoadStatic, line: int = -1) -> None: super().__init__(line) self.type = type self.src = src @@ -1276,21 +1485,77 @@ class KeepAlive(RegisterOp): If we didn't have "keep_alive x", x could be freed immediately after taking the address of 'item', resulting in a read after free on the second line. + + If 'steal' is true, the value is considered to be stolen at + this op, i.e. it won't be decref'd. You need to ensure that + the value is freed otherwise, perhaps by using borrowing + followed by Unborrow. + + Be careful with steal=True -- this can cause memory leaks. """ error_kind = ERR_NEVER - def __init__(self, src: list[Value]) -> None: + def __init__(self, src: list[Value], *, steal: bool = False) -> None: assert src self.src = src + self.steal = steal def sources(self) -> list[Value]: - return self.src[:] + return self.src.copy() + + def stolen(self) -> list[Value]: + if self.steal: + return self.src.copy() + return [] def accept(self, visitor: OpVisitor[T]) -> T: return visitor.visit_keep_alive(self) +class Unborrow(RegisterOp): + """A no-op op to create a regular reference from a borrowed one. + + Borrowed references can only be used temporarily and the reference + counts won't be managed. This value will be refcounted normally. + + This is mainly useful if you split an aggregate value, such as + a tuple, into components using borrowed values (to avoid increfs), + and want to treat the components as sharing the original managed + reference. You'll also need to use KeepAlive with steal=True to + "consume" the original tuple reference: + + # t is a 2-tuple + r0 = borrow t[0] + r1 = borrow t[1] + r2 = unborrow r0 + r3 = unborrow r1 + # now (r2, r3) represent the tuple as separate items, and the + # original tuple can be considered dead and available to be + # stolen + keep_alive steal t + + Be careful with this -- this can easily cause double freeing. + """ + + error_kind = ERR_NEVER + + def __init__(self, src: Value, line: int = -1) -> None: + super().__init__(line) + assert src.is_borrowed + self.src = src + self.type = src.type + + def sources(self) -> list[Value]: + return [self.src] + + def stolen(self) -> list[Value]: + return [] + + def accept(self, visitor: OpVisitor[T]) -> T: + return visitor.visit_unborrow(self) + + @trait class OpVisitor(Generic[T]): """Generic visitor over ops (uses the visitor design pattern).""" @@ -1385,6 +1650,10 @@ def visit_raise_standard_error(self, op: RaiseStandardError) -> T: def visit_call_c(self, op: CallC) -> T: raise NotImplementedError + @abstractmethod + def visit_primitive_op(self, op: PrimitiveOp) -> T: + raise NotImplementedError + @abstractmethod def visit_truncate(self, op: Truncate) -> T: raise NotImplementedError @@ -1405,6 +1674,18 @@ def visit_int_op(self, op: IntOp) -> T: def visit_comparison_op(self, op: ComparisonOp) -> T: raise NotImplementedError + @abstractmethod + def visit_float_op(self, op: FloatOp) -> T: + raise NotImplementedError + + @abstractmethod + def visit_float_neg(self, op: FloatNeg) -> T: + raise NotImplementedError + + @abstractmethod + def visit_float_comparison_op(self, op: FloatComparisonOp) -> T: + raise NotImplementedError + @abstractmethod def visit_load_mem(self, op: LoadMem) -> T: raise NotImplementedError @@ -1425,9 +1706,14 @@ def visit_load_address(self, op: LoadAddress) -> T: def visit_keep_alive(self, op: KeepAlive) -> T: raise NotImplementedError + @abstractmethod + def visit_unborrow(self, op: Unborrow) -> T: + raise NotImplementedError + # TODO: Should the following definition live somewhere else? + # We do a three-pass deserialization scheme in order to resolve name # references. # 1. Create an empty ClassIR for each class in an SCC. @@ -1451,5 +1737,5 @@ def visit_keep_alive(self, op: KeepAlive) -> T: # (Serialization and deserialization *will* be used for incremental # compilation but so far it is not hooked up to anything.) class DeserMaps(NamedTuple): - classes: Dict[str, "ClassIR"] - functions: Dict[str, "FuncIR"] + classes: dict[str, ClassIR] + functions: dict[str, FuncIR] diff --git a/mypyc/ir/pprint.py b/mypyc/ir/pprint.py index cb9e4a2d2541..59ee994f012d 100644 --- a/mypyc/ir/pprint.py +++ b/mypyc/ir/pprint.py @@ -3,8 +3,7 @@ from __future__ import annotations from collections import defaultdict -from typing import Any, Sequence, Union -from typing_extensions import Final +from typing import Any, Final, Sequence, Union from mypyc.common import short_name from mypyc.ir.func_ir import FuncIR, all_values_full @@ -23,6 +22,10 @@ ControlOp, DecRef, Extend, + Float, + FloatComparisonOp, + FloatNeg, + FloatOp, GetAttr, GetElementPtr, Goto, @@ -40,6 +43,7 @@ MethodCall, Op, OpVisitor, + PrimitiveOp, RaiseStandardError, Register, Return, @@ -48,6 +52,7 @@ Truncate, TupleGet, TupleSet, + Unborrow, Unbox, Unreachable, Value, @@ -150,7 +155,7 @@ def visit_init_static(self, op: InitStatic) -> str: return self.format("%s = %r :: %s", name, op.value, op.namespace) def visit_tuple_get(self, op: TupleGet) -> str: - return self.format("%r = %r[%d]", op, op.src, op.index) + return self.format("%r = %s%r[%d]", op, self.borrow_prefix(op), op.src, op.index) def visit_tuple_set(self, op: TupleSet) -> str: item_str = ", ".join(self.format("%r", item) for item in op.items) @@ -213,6 +218,25 @@ def visit_call_c(self, op: CallC) -> str: else: return self.format("%r = %s(%s)", op, op.function_name, args_str) + def visit_primitive_op(self, op: PrimitiveOp) -> str: + args = [] + arg_index = 0 + type_arg_index = 0 + for arg_type in zip(op.desc.arg_types): + if arg_type: + args.append(self.format("%r", op.args[arg_index])) + arg_index += 1 + else: + assert op.type_args + args.append(self.format("%r", op.type_args[type_arg_index])) + type_arg_index += 1 + + args_str = ", ".join(args) + if op.is_void: + return self.format("%s %s", op.desc.name, args_str) + else: + return self.format("%r = %s %s", op, op.desc.name, args_str) + def visit_truncate(self, op: Truncate) -> str: return self.format("%r = truncate %r: %t to %t", op, op.src, op.src_type, op.type) @@ -241,6 +265,15 @@ def visit_comparison_op(self, op: ComparisonOp) -> str: "%r = %r %s %r%s", op, op.lhs, ComparisonOp.op_str[op.op], op.rhs, sign_format ) + def visit_float_op(self, op: FloatOp) -> str: + return self.format("%r = %r %s %r", op, op.lhs, FloatOp.op_str[op.op], op.rhs) + + def visit_float_neg(self, op: FloatNeg) -> str: + return self.format("%r = -%r", op, op.src) + + def visit_float_comparison_op(self, op: FloatComparisonOp) -> str: + return self.format("%r = %r %s %r", op, op.lhs, op.op_str[op.op], op.rhs) + def visit_load_mem(self, op: LoadMem) -> str: return self.format("%r = load_mem %r :: %t*", op, op.src, op.type) @@ -253,11 +286,25 @@ def visit_get_element_ptr(self, op: GetElementPtr) -> str: def visit_load_address(self, op: LoadAddress) -> str: if isinstance(op.src, Register): return self.format("%r = load_address %r", op, op.src) + elif isinstance(op.src, LoadStatic): + name = op.src.identifier + if op.src.module_name is not None: + name = f"{op.src.module_name}.{name}" + return self.format("%r = load_address %s :: %s", op, name, op.src.namespace) else: return self.format("%r = load_address %s", op, op.src) def visit_keep_alive(self, op: KeepAlive) -> str: - return self.format("keep_alive %s" % ", ".join(self.format("%r", v) for v in op.src)) + if op.steal: + steal = "steal " + else: + steal = "" + return self.format( + "keep_alive {}{}".format(steal, ", ".join(self.format("%r", v) for v in op.src)) + ) + + def visit_unborrow(self, op: Unborrow) -> str: + return self.format("%r = unborrow %r", op, op.src) # Helpers @@ -289,6 +336,8 @@ def format(self, fmt: str, *args: Any) -> str: assert isinstance(arg, Value) if isinstance(arg, Integer): result.append(str(arg.value)) + elif isinstance(arg, Float): + result.append(repr(arg.value)) else: result.append(self.names[arg]) elif typespec == "d": @@ -445,7 +494,7 @@ def generate_names_for_ir(args: list[Register], blocks: list[BasicBlock]) -> dic continue if isinstance(value, Register) and value.name: name = value.name - elif isinstance(value, Integer): + elif isinstance(value, (Integer, Float)): continue else: name = "r%d" % temp_index diff --git a/mypyc/ir/rtypes.py b/mypyc/ir/rtypes.py index babfe0770f35..fecfaee5ef77 100644 --- a/mypyc/ir/rtypes.py +++ b/mypyc/ir/rtypes.py @@ -23,8 +23,8 @@ from __future__ import annotations from abc import abstractmethod -from typing import TYPE_CHECKING, ClassVar, Generic, TypeVar -from typing_extensions import Final +from typing import TYPE_CHECKING, ClassVar, Final, Generic, TypeVar +from typing_extensions import TypeGuard from mypyc.common import IS_32_BIT_PLATFORM, PLATFORM_SIZE, JsonDict, short_name from mypyc.namegen import NameGenerator @@ -206,13 +206,12 @@ def __init__( self.error_overlap = error_overlap if ctype == "CPyTagged": self.c_undefined = "CPY_INT_TAG" - elif ctype in ("int32_t", "int64_t"): + elif ctype in ("int16_t", "int32_t", "int64_t"): # This is basically an arbitrary value that is pretty # unlikely to overlap with a real value. self.c_undefined = "-113" - elif ctype in ("CPyPtr", "uint32_t", "uint64_t"): - # TODO: For low-level integers, we need to invent an overlapping - # error value, similar to int64_t above. + elif ctype == "CPyPtr": + # TODO: Invent an overlapping error value? self.c_undefined = "0" elif ctype == "PyObject *": # Boxed types use the null pointer as the error value. @@ -221,6 +220,10 @@ def __init__( self.c_undefined = "2" elif ctype in ("PyObject **", "void *"): self.c_undefined = "NULL" + elif ctype == "double": + self.c_undefined = "-113.0" + elif ctype in ("uint8_t", "uint16_t", "uint32_t", "uint64_t"): + self.c_undefined = "239" # An arbitrary number else: assert False, "Unrecognized ctype: %r" % ctype @@ -288,8 +291,18 @@ def __hash__(self) -> int: # Low level integer types (correspond to C integer types) +int16_rprimitive: Final = RPrimitive( + "i16", + is_unboxed=True, + is_refcounted=False, + is_native_int=True, + is_signed=True, + ctype="int16_t", + size=2, + error_overlap=True, +) int32_rprimitive: Final = RPrimitive( - "int32", + "i32", is_unboxed=True, is_refcounted=False, is_native_int=True, @@ -299,7 +312,7 @@ def __hash__(self) -> int: error_overlap=True, ) int64_rprimitive: Final = RPrimitive( - "int64", + "i64", is_unboxed=True, is_refcounted=False, is_native_int=True, @@ -308,23 +321,49 @@ def __hash__(self) -> int: size=8, error_overlap=True, ) +uint8_rprimitive: Final = RPrimitive( + "u8", + is_unboxed=True, + is_refcounted=False, + is_native_int=True, + is_signed=False, + ctype="uint8_t", + size=1, + error_overlap=True, +) + +# The following unsigned native int types (u16, u32, u64) are not +# exposed to the user. They are for internal use within mypyc only. + +u16_rprimitive: Final = RPrimitive( + "u16", + is_unboxed=True, + is_refcounted=False, + is_native_int=True, + is_signed=False, + ctype="uint16_t", + size=2, + error_overlap=True, +) uint32_rprimitive: Final = RPrimitive( - "uint32", + "u32", is_unboxed=True, is_refcounted=False, is_native_int=True, is_signed=False, ctype="uint32_t", size=4, + error_overlap=True, ) uint64_rprimitive: Final = RPrimitive( - "uint64", + "u64", is_unboxed=True, is_refcounted=False, is_native_int=True, is_signed=False, ctype="uint64_t", size=8, + error_overlap=True, ) # The C 'int' type @@ -366,7 +405,14 @@ def __hash__(self) -> int: # Floats are represent as 'float' PyObject * values. (In the future # we'll likely switch to a more efficient, unboxed representation.) -float_rprimitive: Final = RPrimitive("builtins.float", is_unboxed=False, is_refcounted=True) +float_rprimitive: Final = RPrimitive( + "builtins.float", + is_unboxed=True, + is_refcounted=False, + ctype="double", + size=8, + error_overlap=True, +) # An unboxed Python bool value. This actually has three possible values # (0 -> False, 1 -> True, 2 -> error). If you only need True/False, use @@ -423,7 +469,11 @@ def is_short_int_rprimitive(rtype: RType) -> bool: return rtype is short_int_rprimitive -def is_int32_rprimitive(rtype: RType) -> bool: +def is_int16_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: + return rtype is int16_rprimitive + + +def is_int32_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return rtype is int32_rprimitive or ( rtype is c_pyssize_t_rprimitive and rtype._ctype == "int32_t" ) @@ -435,8 +485,17 @@ def is_int64_rprimitive(rtype: RType) -> bool: ) -def is_fixed_width_rtype(rtype: RType) -> bool: - return is_int32_rprimitive(rtype) or is_int64_rprimitive(rtype) +def is_fixed_width_rtype(rtype: RType) -> TypeGuard[RPrimitive]: + return ( + is_int64_rprimitive(rtype) + or is_int32_rprimitive(rtype) + or is_int16_rprimitive(rtype) + or is_uint8_rprimitive(rtype) + ) + + +def is_uint8_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: + return rtype is uint8_rprimitive def is_uint32_rprimitive(rtype: RType) -> bool: @@ -527,6 +586,12 @@ def visit_rprimitive(self, t: RPrimitive) -> str: return "8" # "8 byte integer" elif t._ctype == "int32_t": return "4" # "4 byte integer" + elif t._ctype == "int16_t": + return "2" # "2 byte integer" + elif t._ctype == "uint8_t": + return "U1" # "1 byte unsigned integer" + elif t._ctype == "double": + return "F" assert not t.is_unboxed, f"{t} unexpected unboxed type" return "O" @@ -809,17 +874,11 @@ def make_simplified_union(items: list[RType]) -> RType: items = flatten_nested_unions(items) assert items - # Remove duplicate items using set + list to preserve item order - seen = set() - new_items = [] - for item in items: - if item not in seen: - new_items.append(item) - seen.add(item) - if len(new_items) > 1: - return RUnion(new_items) + unique_items = dict.fromkeys(items) + if len(unique_items) > 1: + return RUnion(list(unique_items)) else: - return new_items[0] + return next(iter(unique_items)) def accept(self, visitor: RTypeVisitor[T]) -> T: return visitor.visit_runion(self) @@ -965,3 +1024,15 @@ def deserialize(cls, data: JsonDict, ctx: DeserMaps) -> RArray: names=["ob_base", "ob_item", "allocated"], types=[PyVarObject, pointer_rprimitive, c_pyssize_t_rprimitive], ) + + +def check_native_int_range(rtype: RPrimitive, n: int) -> bool: + """Is n within the range of a native, fixed-width int type? + + Assume the type is a fixed-width int type. + """ + if not rtype.is_signed: + return 0 <= n < (1 << (8 * rtype.size)) + else: + limit = 1 << (rtype.size * 8 - 1) + return -limit <= n < limit diff --git a/mypyc/irbuild/ast_helpers.py b/mypyc/irbuild/ast_helpers.py index 1af1ad611a89..3b0f50514594 100644 --- a/mypyc/irbuild/ast_helpers.py +++ b/mypyc/irbuild/ast_helpers.py @@ -62,6 +62,7 @@ def maybe_process_conditional_comparison( do nothing and return False. Args: + self: IR form Builder e: Arbitrary expression true: Branch target if comparison is true false: Branch target if comparison is false @@ -93,7 +94,9 @@ def maybe_process_conditional_comparison( self.add_bool_branch(reg, true, false) else: # "left op right" for two tagged integers - self.builder.compare_tagged_condition(left, right, op, true, false, e.line) + reg = self.builder.binary_op(left, right, op, e.line) + self.flush_keep_alives() + self.add_bool_branch(reg, true, false) return True diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index f37fae608083..cca771e82c83 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -10,11 +10,12 @@ example, expressions are transformed in mypyc.irbuild.expression and functions are transformed in mypyc.irbuild.function. """ + from __future__ import annotations from contextlib import contextmanager -from typing import Any, Callable, Iterator, Sequence, Union -from typing_extensions import Final, overload +from typing import Any, Callable, Final, Iterator, Sequence, Union +from typing_extensions import overload from mypy.build import Graph from mypy.maptype import map_instance_to_supertype @@ -41,22 +42,25 @@ Statement, SymbolNode, TupleExpr, + TypeAlias, TypeInfo, UnaryExpr, Var, ) from mypy.types import ( AnyType, + DeletedType, Instance, ProperType, TupleType, Type, + TypedDictType, TypeOfAny, UninhabitedType, UnionType, get_proper_type, ) -from mypy.util import split_target +from mypy.util import module_prefix, split_target from mypy.visitor import ExpressionVisitor, StatementVisitor from mypyc.common import BITMAP_BITS, SELF_NAME, TEMP_ATTR_NAME from mypyc.crash import catch_errors @@ -88,13 +92,14 @@ RType, RUnion, bitmap_rprimitive, - c_int_rprimitive, c_pyssize_t_rprimitive, dict_rprimitive, int_rprimitive, + is_float_rprimitive, is_list_rprimitive, is_none_rprimitive, is_object_rprimitive, + is_tagged, is_tuple_rprimitive, none_rprimitive, object_rprimitive, @@ -123,12 +128,7 @@ from mypyc.primitives.dict_ops import dict_get_item_op, dict_set_item_op from mypyc.primitives.generic_ops import iter_op, next_op, py_setattr_op from mypyc.primitives.list_ops import list_get_item_unsafe_op, list_pop_last, to_list -from mypyc.primitives.misc_ops import ( - check_unpack_count_op, - get_module_dict_op, - import_extra_args_op, - import_op, -) +from mypyc.primitives.misc_ops import check_unpack_count_op, get_module_dict_op, import_op from mypyc.primitives.registry import CFunctionDescription, function_ops # These int binary operations can borrow their operands safely, since the @@ -160,12 +160,15 @@ def __init__( options: CompilerOptions, singledispatch_impls: dict[FuncDef, list[RegisterImplInfo]], ) -> None: - self.builder = LowLevelIRBuilder(current_module, mapper, options) + self.builder = LowLevelIRBuilder(errors, options) self.builders = [self.builder] self.symtables: list[dict[SymbolNode, SymbolTarget]] = [{}] self.runtime_args: list[list[RuntimeArg]] = [[]] self.function_name_stack: list[str] = [] self.class_ir_stack: list[ClassIR] = [] + # Keep track of whether the next statement in a block is reachable + # or not, separately for each block nesting level + self.block_reachable_stack: list[bool] = [True] self.current_module = current_module self.mapper = mapper @@ -173,6 +176,7 @@ def __init__( self.graph = graph self.ret_types: list[RType] = [] self.functions: list[FuncIR] = [] + self.function_names: set[tuple[str | None, str]] = set() self.classes: list[ClassIR] = [] self.final_names: list[tuple[str, RType]] = [] self.callable_class_names: set[str] = set() @@ -190,6 +194,8 @@ def __init__( self.encapsulating_funcs = pbv.encapsulating_funcs self.nested_fitems = pbv.nested_funcs.keys() self.fdefs_to_decorators = pbv.funcs_to_decorators + self.module_import_groups = pbv.module_import_groups + self.singledispatch_impls = singledispatch_impls self.visitor = visitor @@ -223,14 +229,13 @@ def set_module(self, module_name: str, module_path: str) -> None: """ self.module_name = module_name self.module_path = module_path + self.builder.set_module(module_name, module_path) @overload - def accept(self, node: Expression, *, can_borrow: bool = False) -> Value: - ... + def accept(self, node: Expression, *, can_borrow: bool = False) -> Value: ... @overload - def accept(self, node: Statement) -> None: - ... + def accept(self, node: Statement) -> None: ... def accept(self, node: Statement | Expression, *, can_borrow: bool = False) -> Value | None: """Transform an expression or a statement. @@ -301,6 +306,9 @@ def load_bytes_from_str_literal(self, value: str) -> Value: def load_int(self, value: int) -> Value: return self.builder.load_int(value) + def load_float(self, value: float) -> Value: + return self.builder.load_float(value) + def unary_op(self, lreg: Value, expr_op: str, line: int) -> Value: return self.builder.unary_op(lreg, expr_op, line) @@ -370,9 +378,6 @@ def call_c(self, desc: CFunctionDescription, args: list[Value], line: int) -> Va def int_op(self, type: RType, lhs: Value, rhs: Value, op: int, line: int) -> Value: return self.builder.int_op(type, lhs, rhs, op, line) - def compare_tagged(self, lhs: Value, rhs: Value, op: str, line: int) -> Value: - return self.builder.compare_tagged(lhs, rhs, op, line) - def compare_tuples(self, lhs: Value, rhs: Value, op: str, line: int) -> Value: return self.builder.compare_tuples(lhs, rhs, op, line) @@ -391,22 +396,6 @@ def add_to_non_ext_dict( key_unicode = self.load_str(key) self.call_c(dict_set_item_op, [non_ext.dict, key_unicode, val], line) - def gen_import_from( - self, id: str, globals_dict: Value, imported: list[str], line: int - ) -> Value: - self.imports[id] = None - - null_dict = Integer(0, dict_rprimitive, line) - names_to_import = self.new_list_op([self.load_str(name) for name in imported], line) - zero_int = Integer(0, c_int_rprimitive, line) - value = self.call_c( - import_extra_args_op, - [self.load_str(id), globals_dict, null_dict, names_to_import, zero_int], - line, - ) - self.add(InitStatic(value, id, namespace=NAMESPACE_MODULE)) - return value - def gen_import(self, id: str, line: int) -> None: self.imports[id] = None @@ -513,6 +502,11 @@ def non_function_scope(self) -> bool: # Currently the stack always has at least two items: dummy and top-level. return len(self.fn_infos) <= 2 + def top_level_fn_info(self) -> FuncInfo | None: + if self.non_function_scope(): + return None + return self.fn_infos[2] + def init_final_static( self, lvalue: Lvalue, @@ -547,16 +541,14 @@ def load_final_static( error_msg=f'value for final name "{error_name}" was not set', ) - def load_final_literal_value(self, val: int | str | bytes | float | bool, line: int) -> Value: - """Load value of a final name or class-level attribute.""" + def load_literal_value(self, val: int | str | bytes | float | complex | bool) -> Value: + """Load value of a final name, class-level attribute, or constant folded expression.""" if isinstance(val, bool): if val: return self.true() else: return self.false() elif isinstance(val, int): - # TODO: take care of negative integer initializers - # (probably easier to fix this in mypy itself). return self.builder.load_int(val) elif isinstance(val, float): return self.builder.load_float(val) @@ -564,10 +556,16 @@ def load_final_literal_value(self, val: int | str | bytes | float | bool, line: return self.builder.load_str(val) elif isinstance(val, bytes): return self.builder.load_bytes(val) + elif isinstance(val, complex): + return self.builder.load_complex(val) else: - assert False, "Unsupported final literal value" + assert False, "Unsupported literal value" - def get_assignment_target(self, lvalue: Lvalue, line: int = -1) -> AssignmentTarget: + def get_assignment_target( + self, lvalue: Lvalue, line: int = -1, *, for_read: bool = False + ) -> AssignmentTarget: + if line == -1: + line = lvalue.line if isinstance(lvalue, NameExpr): # If we are visiting a decorator, then the SymbolNode we really want to be looking at # is the function that is decorated, not the entire Decorator node itself. @@ -578,8 +576,14 @@ def get_assignment_target(self, lvalue: Lvalue, line: int = -1) -> AssignmentTar # New semantic analyzer doesn't create ad-hoc Vars for special forms. assert lvalue.is_special_form symbol = Var(lvalue.name) + if not for_read and isinstance(symbol, Var) and symbol.is_cls: + self.error("Cannot assign to the first argument of classmethod", line) if lvalue.kind == LDEF: if symbol not in self.symtables[-1]: + if isinstance(symbol, Var) and not isinstance(symbol.type, DeletedType): + reg_type = self.type_to_rtype(symbol.type) + else: + reg_type = self.node_type(lvalue) # If the function is a generator function, then first define a new variable # in the current function's environment class. Next, define a target that # refers to the newly defined variable in that environment class. Add the @@ -587,14 +591,11 @@ def get_assignment_target(self, lvalue: Lvalue, line: int = -1) -> AssignmentTar # current environment. if self.fn_info.is_generator: return self.add_var_to_env_class( - symbol, - self.node_type(lvalue), - self.fn_info.generator_class, - reassign=False, + symbol, reg_type, self.fn_info.generator_class, reassign=False ) # Otherwise define a new local variable. - return self.add_local_reg(symbol, self.node_type(lvalue)) + return self.add_local_reg(symbol, reg_type) else: # Assign to a previously defined variable. return self.lookup(symbol) @@ -658,13 +659,13 @@ def read( def assign(self, target: Register | AssignmentTarget, rvalue_reg: Value, line: int) -> None: if isinstance(target, Register): - self.add(Assign(target, self.coerce(rvalue_reg, target.type, line))) + self.add(Assign(target, self.coerce_rvalue(rvalue_reg, target.type, line))) elif isinstance(target, AssignmentTargetRegister): - rvalue_reg = self.coerce(rvalue_reg, target.type, line) + rvalue_reg = self.coerce_rvalue(rvalue_reg, target.type, line) self.add(Assign(target.register, rvalue_reg)) elif isinstance(target, AssignmentTargetAttr): if isinstance(target.obj_type, RInstance): - rvalue_reg = self.coerce(rvalue_reg, target.type, line) + rvalue_reg = self.coerce_rvalue(rvalue_reg, target.type, line) self.add(SetAttr(target.obj, target.attr, rvalue_reg, line)) else: key = self.load_str(target.attr) @@ -691,6 +692,18 @@ def assign(self, target: Register | AssignmentTarget, rvalue_reg: Value, line: i else: assert False, "Unsupported assignment target" + def coerce_rvalue(self, rvalue: Value, rtype: RType, line: int) -> Value: + if is_float_rprimitive(rtype) and is_tagged(rvalue.type): + typename = rvalue.type.short_name() + if typename == "short_int": + typename = "int" + self.error( + "Incompatible value representations in assignment " + + f'(expression has type "{typename}", variable has type "float")', + line, + ) + return self.coerce(rvalue, rtype, line) + def process_sequence_assignment( self, target: AssignmentTargetTuple, rvalue: Value, line: int ) -> None: @@ -734,7 +747,6 @@ def process_iterator_tuple_assignment_helper( def process_iterator_tuple_assignment( self, target: AssignmentTargetTuple, rvalue_reg: Value, line: int ) -> None: - iterator = self.call_c(iter_op, [rvalue_reg], line) # This may be the whole lvalue list if there is no starred value @@ -892,8 +904,12 @@ def get_dict_base_type(self, expr: Expression) -> list[Instance]: dict_types = [] for t in types: - assert isinstance(t, Instance), t - dict_base = next(base for base in t.type.mro if base.fullname == "builtins.dict") + if isinstance(t, TypedDictType): + t = t.fallback + dict_base = next(base for base in t.type.mro if base.fullname == "typing.Mapping") + else: + assert isinstance(t, Instance), t + dict_base = next(base for base in t.type.mro if base.fullname == "builtins.dict") dict_types.append(map_instance_to_supertype(t, dict_base)) return dict_types @@ -1003,8 +1019,8 @@ def emit_load_final( line: line number where loading occurs """ if final_var.final_value is not None: # this is safe even for non-native names - return self.load_final_literal_value(final_var.final_value, line) - elif native: + return self.load_literal_value(final_var.final_value) + elif native and module_prefix(self.graph, fullname): return self.load_final_static(fullname, self.mapper.type_to_rtype(typ), line, name) else: return None @@ -1015,10 +1031,10 @@ def is_module_member_expr(self, expr: MemberExpr) -> bool: def call_refexpr_with_args( self, expr: CallExpr, callee: RefExpr, arg_values: list[Value] ) -> Value: - # Handle data-driven special-cased primitive call ops. if callee.fullname and expr.arg_kinds == [ARG_POS] * len(arg_values): - call_c_ops_candidates = function_ops.get(callee.fullname, []) + fullname = get_call_target_fullname(callee) + call_c_ops_candidates = function_ops.get(fullname, []) target = self.builder.matching_call_c( call_c_ops_candidates, arg_values, expr.line, self.node_type(expr) ) @@ -1092,7 +1108,8 @@ def flatten_classes(self, arg: RefExpr | TupleExpr) -> list[ClassIR] | None: def enter(self, fn_info: FuncInfo | str = "") -> None: if isinstance(fn_info, str): fn_info = FuncInfo(name=fn_info) - self.builder = LowLevelIRBuilder(self.current_module, self.mapper, self.options) + self.builder = LowLevelIRBuilder(self.errors, self.options) + self.builder.set_module(self.module_name, self.module_path) self.builders.append(self.builder) self.symtables.append({}) self.runtime_args.append([]) @@ -1224,14 +1241,15 @@ def add_var_to_env_class( ) -> AssignmentTarget: # First, define the variable name as an attribute of the environment class, and then # construct a target for that attribute. - self.fn_info.env_class.attributes[var.name] = rtype - attr_target = AssignmentTargetAttr(base.curr_env_reg, var.name) + name = remangle_redefinition_name(var.name) + self.fn_info.env_class.attributes[name] = rtype + attr_target = AssignmentTargetAttr(base.curr_env_reg, name) if reassign: # Read the local definition of the variable, and set the corresponding attribute of # the environment class' variable to be that value. reg = self.read(self.lookup(var), self.fn_info.fitem.line) - self.add(SetAttr(base.curr_env_reg, var.name, reg, self.fn_info.fitem.line)) + self.add(SetAttr(base.curr_env_reg, name, reg, self.fn_info.fitem.line)) # Override the local definition of the variable to instead point at the variable in # the environment class. @@ -1283,6 +1301,14 @@ def is_native_attr_ref(self, expr: MemberExpr) -> bool: and not obj_rtype.class_ir.get_method(expr.name) ) + def mark_block_unreachable(self) -> None: + """Mark statements in the innermost block being processed as unreachable. + + This should be called after a statement that unconditionally leaves the + block, such as 'break' or 'return'. + """ + self.block_reachable_stack[-1] = False + # Lacks a good type because there wasn't a reasonable type in 3.5 :( def catch_errors(self, line: int) -> Any: return catch_errors(self.module_path, line) @@ -1296,6 +1322,14 @@ def error(self, msg: str, line: int) -> None: def note(self, msg: str, line: int) -> None: self.errors.note(msg, self.module_path, line) + def add_function(self, func_ir: FuncIR, line: int) -> None: + name = (func_ir.class_name, func_ir.name) + if name in self.function_names: + self.error(f'Duplicate definition of "{name[1]}" not supported by mypyc', line) + return + self.function_names.add(name) + self.functions.append(func_ir) + def gen_arg_defaults(builder: IRBuilder) -> None: """Generate blocks for arguments that have default values. @@ -1349,3 +1383,12 @@ def remangle_redefinition_name(name: str) -> str: lookups. """ return name.replace("'", "__redef__") + + +def get_call_target_fullname(ref: RefExpr) -> str: + if isinstance(ref.node, TypeAlias): + # Resolve simple type aliases. In calls they evaluate to the type they point to. + target = get_proper_type(ref.node.target) + if isinstance(target, Instance): + return target.type.fullname + return ref.fullname diff --git a/mypyc/irbuild/callable_class.py b/mypyc/irbuild/callable_class.py index d3ee54a208cd..599dbb81f767 100644 --- a/mypyc/irbuild/callable_class.py +++ b/mypyc/irbuild/callable_class.py @@ -17,7 +17,7 @@ def setup_callable_class(builder: IRBuilder) -> None: - """Generate an (incomplete) callable class representing function. + """Generate an (incomplete) callable class representing a function. This can be a nested function or a function within a non-extension class. Also set up the 'self' variable for that class. diff --git a/mypyc/irbuild/classdef.py b/mypyc/irbuild/classdef.py index 59b1c05a0ddb..3f6ec0f33822 100644 --- a/mypyc/irbuild/classdef.py +++ b/mypyc/irbuild/classdef.py @@ -2,9 +2,9 @@ from __future__ import annotations +import typing_extensions from abc import abstractmethod -from typing import Callable -from typing_extensions import Final +from typing import Callable, Final from mypy.nodes import ( AssignmentStmt, @@ -24,7 +24,7 @@ TypeInfo, is_class_var, ) -from mypy.types import ENUM_REMOVED_PROPS, Instance, UnboundType, get_proper_type +from mypy.types import ENUM_REMOVED_PROPS, Instance, RawExpressionType, get_proper_type from mypyc.common import PROPSET_PREFIX from mypyc.ir.class_ir import ClassIR, NonExtClassInfo from mypyc.ir.func_ir import FuncDecl, FuncSignature @@ -498,6 +498,14 @@ def populate_non_ext_bases(builder: IRBuilder, cdef: ClassDef) -> Value: if builder.options.capi_version < (3, 8): # TypedDict was added to typing in Python 3.8. module = "typing_extensions" + # TypedDict is not a real type on typing_extensions 4.7.0+ + name = "_TypedDict" + if isinstance(typing_extensions.TypedDict, type): + raise RuntimeError( + "It looks like you may have an old version " + "of typing_extensions installed. " + "typing_extensions>=4.7.0 is required on Python 3.7." + ) else: # In Python 3.9 TypedDict is not a real type. name = "_TypedDict" @@ -593,16 +601,15 @@ def add_non_ext_class_attr_ann( if typ is None: # FIXME: if get_type_info is not provided, don't fall back to stmt.type? ann_type = get_proper_type(stmt.type) - if ( - isinstance(stmt.unanalyzed_type, UnboundType) - and stmt.unanalyzed_type.original_str_expr is not None + if isinstance(stmt.unanalyzed_type, RawExpressionType) and isinstance( + stmt.unanalyzed_type.literal_value, str ): # Annotation is a forward reference, so don't attempt to load the actual # type and load the string instead. # # TODO: is it possible to determine whether a non-string annotation is # actually a forward reference due to the __annotations__ future? - typ = builder.load_str(stmt.unanalyzed_type.original_str_expr) + typ = builder.load_str(stmt.unanalyzed_type.literal_value) elif isinstance(ann_type, Instance): typ = load_type(builder, ann_type.type, stmt.line) else: diff --git a/mypyc/irbuild/constant_fold.py b/mypyc/irbuild/constant_fold.py index 4e9eb53b9222..12a4b15dd40c 100644 --- a/mypyc/irbuild/constant_fold.py +++ b/mypyc/irbuild/constant_fold.py @@ -10,20 +10,28 @@ from __future__ import annotations -from typing import Union -from typing_extensions import Final +from typing import Final, Union -from mypy.constant_fold import ( - constant_fold_binary_int_op, - constant_fold_binary_str_op, - constant_fold_unary_int_op, +from mypy.constant_fold import constant_fold_binary_op, constant_fold_unary_op +from mypy.nodes import ( + BytesExpr, + ComplexExpr, + Expression, + FloatExpr, + IntExpr, + MemberExpr, + NameExpr, + OpExpr, + StrExpr, + UnaryExpr, + Var, ) -from mypy.nodes import Expression, IntExpr, MemberExpr, NameExpr, OpExpr, StrExpr, UnaryExpr, Var from mypyc.irbuild.builder import IRBuilder +from mypyc.irbuild.util import bytes_from_str # All possible result types of constant folding -ConstantValue = Union[int, str] -CONST_TYPES: Final = (int, str) +ConstantValue = Union[int, float, complex, str, bytes] +CONST_TYPES: Final = (int, float, complex, str, bytes) def constant_fold_expr(builder: IRBuilder, expr: Expression) -> ConstantValue | None: @@ -33,31 +41,55 @@ def constant_fold_expr(builder: IRBuilder, expr: Expression) -> ConstantValue | """ if isinstance(expr, IntExpr): return expr.value + if isinstance(expr, FloatExpr): + return expr.value if isinstance(expr, StrExpr): return expr.value + if isinstance(expr, BytesExpr): + return bytes_from_str(expr.value) + if isinstance(expr, ComplexExpr): + return expr.value elif isinstance(expr, NameExpr): node = expr.node if isinstance(node, Var) and node.is_final: - value = node.final_value - if isinstance(value, (CONST_TYPES)): - return value + final_value = node.final_value + if isinstance(final_value, (CONST_TYPES)): + return final_value elif isinstance(expr, MemberExpr): final = builder.get_final_ref(expr) if final is not None: fn, final_var, native = final if final_var.is_final: - value = final_var.final_value - if isinstance(value, (CONST_TYPES)): - return value + final_value = final_var.final_value + if isinstance(final_value, (CONST_TYPES)): + return final_value elif isinstance(expr, OpExpr): left = constant_fold_expr(builder, expr.left) right = constant_fold_expr(builder, expr.right) - if isinstance(left, int) and isinstance(right, int): - return constant_fold_binary_int_op(expr.op, left, right) - elif isinstance(left, str) and isinstance(right, str): - return constant_fold_binary_str_op(expr.op, left, right) + if left is not None and right is not None: + return constant_fold_binary_op_extended(expr.op, left, right) elif isinstance(expr, UnaryExpr): value = constant_fold_expr(builder, expr.expr) - if isinstance(value, int): - return constant_fold_unary_int_op(expr.op, value) + if value is not None and not isinstance(value, bytes): + return constant_fold_unary_op(expr.op, value) + return None + + +def constant_fold_binary_op_extended( + op: str, left: ConstantValue, right: ConstantValue +) -> ConstantValue | None: + """Like mypy's constant_fold_binary_op(), but includes bytes support. + + mypy cannot use constant folded bytes easily so it's simpler to only support them in mypyc. + """ + if not isinstance(left, bytes) and not isinstance(right, bytes): + return constant_fold_binary_op(op, left, right) + + if op == "+" and isinstance(left, bytes) and isinstance(right, bytes): + return left + right + elif op == "*" and isinstance(left, bytes) and isinstance(right, int): + return left * right + elif op == "*" and isinstance(left, int) and isinstance(right, bytes): + return left * right + return None diff --git a/mypyc/irbuild/context.py b/mypyc/irbuild/context.py index 676afb507504..a740f0b821d9 100644 --- a/mypyc/irbuild/context.py +++ b/mypyc/irbuild/context.py @@ -22,6 +22,7 @@ def __init__( contains_nested: bool = False, is_decorated: bool = False, in_non_ext: bool = False, + add_nested_funcs_to_env: bool = False, ) -> None: self.fitem = fitem self.name = name @@ -47,6 +48,7 @@ def __init__( self.contains_nested = contains_nested self.is_decorated = is_decorated self.in_non_ext = in_non_ext + self.add_nested_funcs_to_env = add_nested_funcs_to_env # TODO: add field for ret_type: RType = none_rprimitive diff --git a/mypyc/irbuild/env_class.py b/mypyc/irbuild/env_class.py index ded8072deb63..aa223fe20176 100644 --- a/mypyc/irbuild/env_class.py +++ b/mypyc/irbuild/env_class.py @@ -107,7 +107,7 @@ def load_env_registers(builder: IRBuilder) -> None: load_outer_envs(builder, fn_info.callable_class) # If this is a FuncDef, then make sure to load the FuncDef into its own environment # class so that the function can be called recursively. - if isinstance(fitem, FuncDef): + if isinstance(fitem, FuncDef) and fn_info.add_nested_funcs_to_env: setup_func_for_recursive_call(builder, fitem, fn_info.callable_class) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index 3f5b795a1436..a16faf6cd7d7 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -6,7 +6,8 @@ from __future__ import annotations -from typing import Callable, Sequence, cast +import math +from typing import Callable, Sequence from mypy.nodes import ( ARG_POS, @@ -48,6 +49,7 @@ ) from mypy.types import Instance, ProperType, TupleType, TypeType, get_proper_type from mypyc.common import MAX_SHORT_INT +from mypyc.ir.class_ir import ClassIR from mypyc.ir.func_ir import FUNC_CLASSMETHOD, FUNC_STATICMETHOD from mypyc.ir.ops import ( Assign, @@ -78,6 +80,7 @@ from mypyc.irbuild.constant_fold import constant_fold_expr from mypyc.irbuild.for_helpers import ( comprehension_helper, + raise_error_if_contains_unreachable_names, translate_list_comprehension, translate_set_comprehension, ) @@ -89,11 +92,9 @@ tokenizer_printf_style, ) from mypyc.irbuild.specialize import apply_function_specialization, apply_method_specialization -from mypyc.irbuild.util import bytes_from_str from mypyc.primitives.bytes_ops import bytes_slice_op from mypyc.primitives.dict_ops import dict_get_item_op, dict_new_op, dict_set_item_op from mypyc.primitives.generic_ops import iter_op -from mypyc.primitives.int_ops import int_comparison_op_mapping from mypyc.primitives.list_ops import list_append_op, list_extend_op, list_slice_op from mypyc.primitives.misc_ops import ellipsis_op, get_module_dict_op, new_slice_op, type_op from mypyc.primitives.registry import CFunctionDescription, builtin_names @@ -125,6 +126,12 @@ def transform_name_expr(builder: IRBuilder, expr: NameExpr) -> Value: return builder.true() if fullname == "builtins.False": return builder.false() + if fullname in ("typing.TYPE_CHECKING", "typing_extensions.TYPE_CHECKING"): + return builder.false() + + math_literal = transform_math_literal(builder, fullname) + if math_literal is not None: + return math_literal if isinstance(expr.node, Var) and expr.node.is_final: value = builder.emit_load_final( @@ -174,12 +181,16 @@ def transform_name_expr(builder: IRBuilder, expr: NameExpr) -> Value: ) return obj else: - return builder.read(builder.get_assignment_target(expr), expr.line) + return builder.read(builder.get_assignment_target(expr, for_read=True), expr.line) return builder.load_global(expr) def transform_member_expr(builder: IRBuilder, expr: MemberExpr) -> Value: + # Special Cases + if expr.fullname in ("typing.TYPE_CHECKING", "typing_extensions.TYPE_CHECKING"): + return builder.false() + # First check if this is maybe a final attribute. final = builder.get_final_ref(expr) if final is not None: @@ -190,6 +201,10 @@ def transform_member_expr(builder: IRBuilder, expr: MemberExpr) -> Value: if value is not None: return value + math_literal = transform_math_literal(builder, expr.fullname) + if math_literal is not None: + return math_literal + if isinstance(expr.node, MypyFile) and expr.node.fullname in builder.imports: return builder.load_module(expr.node.fullname) @@ -288,6 +303,9 @@ def transform_call_expr(builder: IRBuilder, expr: CallExpr) -> Value: callee = callee.analyzed.expr # Unwrap type application if isinstance(callee, MemberExpr): + if isinstance(callee.expr, RefExpr) and isinstance(callee.expr.node, MypyFile): + # Call a module-level function, not a method. + return translate_call(builder, expr, callee) return apply_method_specialization(builder, expr, callee) or translate_method_call( builder, expr, callee ) @@ -336,30 +354,7 @@ def translate_method_call(builder: IRBuilder, expr: CallExpr, callee: MemberExpr # Call a method via the *class* assert isinstance(callee.expr.node, TypeInfo) ir = builder.mapper.type_to_ir[callee.expr.node] - decl = ir.method_decl(callee.name) - args = [] - arg_kinds, arg_names = expr.arg_kinds[:], expr.arg_names[:] - # Add the class argument for class methods in extension classes - if decl.kind == FUNC_CLASSMETHOD and ir.is_ext_class: - args.append(builder.load_native_type_object(callee.expr.node.fullname)) - arg_kinds.insert(0, ARG_POS) - arg_names.insert(0, None) - args += [builder.accept(arg) for arg in expr.args] - - if ir.is_ext_class: - return builder.builder.call(decl, args, arg_kinds, arg_names, expr.line) - else: - obj = builder.accept(callee.expr) - return builder.gen_method_call( - obj, - callee.name, - args, - builder.node_type(expr), - expr.line, - expr.arg_kinds, - expr.arg_names, - ) - + return call_classmethod(builder, ir, expr, callee) elif builder.is_module_member_expr(callee): # Fall back to a PyCall for non-native module calls function = builder.accept(callee) @@ -368,6 +363,17 @@ def translate_method_call(builder: IRBuilder, expr: CallExpr, callee: MemberExpr function, args, expr.line, arg_kinds=expr.arg_kinds, arg_names=expr.arg_names ) else: + if isinstance(callee.expr, RefExpr): + node = callee.expr.node + if isinstance(node, Var) and node.is_cls: + typ = get_proper_type(node.type) + if isinstance(typ, TypeType) and isinstance(typ.item, Instance): + class_ir = builder.mapper.type_to_ir.get(typ.item.type) + if class_ir and class_ir.is_ext_class and class_ir.has_no_subclasses(): + # Call a native classmethod via cls that can be statically bound, + # since the class has no subclasses. + return call_classmethod(builder, class_ir, expr, callee) + receiver_typ = builder.node_type(callee.expr) # If there is a specializer for this method name/type, try calling it. @@ -389,6 +395,32 @@ def translate_method_call(builder: IRBuilder, expr: CallExpr, callee: MemberExpr ) +def call_classmethod(builder: IRBuilder, ir: ClassIR, expr: CallExpr, callee: MemberExpr) -> Value: + decl = ir.method_decl(callee.name) + args = [] + arg_kinds, arg_names = expr.arg_kinds.copy(), expr.arg_names.copy() + # Add the class argument for class methods in extension classes + if decl.kind == FUNC_CLASSMETHOD and ir.is_ext_class: + args.append(builder.load_native_type_object(ir.fullname)) + arg_kinds.insert(0, ARG_POS) + arg_names.insert(0, None) + args += [builder.accept(arg) for arg in expr.args] + + if ir.is_ext_class: + return builder.builder.call(decl, args, arg_kinds, arg_names, expr.line) + else: + obj = builder.accept(callee.expr) + return builder.gen_method_call( + obj, + callee.name, + args, + builder.node_type(expr), + expr.line, + expr.arg_kinds, + expr.arg_names, + ) + + def translate_super_method_call(builder: IRBuilder, expr: CallExpr, callee: SuperExpr) -> Value: if callee.info is None or (len(callee.call.args) != 0 and len(callee.call.args) != 2): return translate_call(builder, expr, callee) @@ -433,7 +465,7 @@ def translate_super_method_call(builder: IRBuilder, expr: CallExpr, callee: Supe decl = base.method_decl(callee.name) arg_values = [builder.accept(arg) for arg in expr.args] - arg_kinds, arg_names = expr.arg_kinds[:], expr.arg_names[:] + arg_kinds, arg_names = expr.arg_kinds.copy(), expr.arg_names.copy() if decl.kind != FUNC_STATICMETHOD: # Grab first argument @@ -477,7 +509,7 @@ def transform_op_expr(builder: IRBuilder, expr: OpExpr) -> Value: return builder.shortcircuit_expr(expr) # Special case for string formatting - if expr.op == "%" and (isinstance(expr.left, StrExpr) or isinstance(expr.left, BytesExpr)): + if expr.op == "%" and isinstance(expr.left, (StrExpr, BytesExpr)): ret = translate_printf_style_formatting(builder, expr.left, expr.right) if ret is not None: return ret @@ -547,10 +579,8 @@ def try_constant_fold(builder: IRBuilder, expr: Expression) -> Value | None: Return None otherwise. """ value = constant_fold_expr(builder, expr) - if isinstance(value, int): - return builder.load_int(value) - elif isinstance(value, str): - return builder.load_str(value) + if value is not None: + return builder.load_literal_value(value) return None @@ -632,10 +662,6 @@ def set_literal_values(builder: IRBuilder, items: Sequence[Expression]) -> list[ values.append(True) elif item.fullname == "builtins.False": values.append(False) - elif isinstance(item, (BytesExpr, FloatExpr, ComplexExpr)): - # constant_fold_expr() doesn't handle these (yet?) - v = bytes_from_str(item.value) if isinstance(item, BytesExpr) else item.value - values.append(v) elif isinstance(item, TupleExpr): tuple_values = set_literal_values(builder, item.items) if tuple_values is not None: @@ -655,7 +681,6 @@ def precompute_set_literal(builder: IRBuilder, s: SetExpr) -> Value | None: Supported items: - Anything supported by irbuild.constant_fold.constant_fold_expr() - None, True, and False - - Float, byte, and complex literals - Tuple literals with only items listed above """ values = set_literal_values(builder, s.items) @@ -689,7 +714,9 @@ def transform_comparison_expr(builder: IRBuilder, e: ComparisonExpr) -> Value: lhs = e.operands[0] mypy_file = builder.graph["builtins"].tree assert mypy_file is not None - bool_type = Instance(cast(TypeInfo, mypy_file.names["bool"].node), []) + info = mypy_file.names["bool"].node + assert isinstance(info, TypeInfo) + bool_type = Instance(info, []) exprs = [] for item in items: expr = ComparisonExpr([cmp_op], [lhs, item]) @@ -728,7 +755,7 @@ def transform_comparison_expr(builder: IRBuilder, e: ComparisonExpr) -> Value: set_literal = precompute_set_literal(builder, e.operands[1]) if set_literal is not None: lhs = e.operands[0] - result = builder.builder.call_c( + result = builder.builder.primitive_op( set_in_op, [builder.accept(lhs), set_literal], e.line, bool_rprimitive ) if first_op == "not in": @@ -750,7 +777,7 @@ def transform_comparison_expr(builder: IRBuilder, e: ComparisonExpr) -> Value: borrow_left = is_borrow_friendly_expr(builder, right_expr) left = builder.accept(left_expr, can_borrow=borrow_left) right = builder.accept(right_expr, can_borrow=True) - return builder.compare_tagged(left, right, first_op, e.line) + return builder.binary_op(left, right, first_op, e.line) # TODO: Don't produce an expression when used in conditional context # All of the trickiness here is due to support for chained conditionals @@ -786,29 +813,32 @@ def translate_is_none(builder: IRBuilder, expr: Expression, negated: bool) -> Va def transform_basic_comparison( builder: IRBuilder, op: str, left: Value, right: Value, line: int ) -> Value: - if ( - is_int_rprimitive(left.type) - and is_int_rprimitive(right.type) - and op in int_comparison_op_mapping.keys() - ): - return builder.compare_tagged(left, right, op, line) - if is_fixed_width_rtype(left.type) and op in int_comparison_op_mapping.keys(): + if is_fixed_width_rtype(left.type) and op in ComparisonOp.signed_ops: if right.type == left.type: - op_id = ComparisonOp.signed_ops[op] + if left.type.is_signed: + op_id = ComparisonOp.signed_ops[op] + else: + op_id = ComparisonOp.unsigned_ops[op] return builder.builder.comparison_op(left, right, op_id, line) elif isinstance(right, Integer): - op_id = ComparisonOp.signed_ops[op] + if left.type.is_signed: + op_id = ComparisonOp.signed_ops[op] + else: + op_id = ComparisonOp.unsigned_ops[op] return builder.builder.comparison_op( - left, Integer(right.value >> 1, left.type), op_id, line + left, builder.coerce(right, left.type, line), op_id, line ) elif ( is_fixed_width_rtype(right.type) - and op in int_comparison_op_mapping.keys() + and op in ComparisonOp.signed_ops and isinstance(left, Integer) ): - op_id = ComparisonOp.signed_ops[op] + if right.type.is_signed: + op_id = ComparisonOp.signed_ops[op] + else: + op_id = ComparisonOp.unsigned_ops[op] return builder.builder.comparison_op( - Integer(left.value >> 1, right.type), right, op_id, line + builder.coerce(left, right.type, line), right, op_id, line ) negate = False @@ -984,6 +1014,9 @@ def transform_set_comprehension(builder: IRBuilder, o: SetComprehension) -> Valu def transform_dictionary_comprehension(builder: IRBuilder, o: DictionaryComprehension) -> Value: + if raise_error_if_contains_unreachable_names(builder, o): + return builder.none() + d = builder.maybe_spill(builder.call_c(dict_new_op, [], o.line)) loop_params = list(zip(o.indices, o.sequences, o.condlists, o.is_async)) @@ -1020,3 +1053,18 @@ def transform_assignment_expr(builder: IRBuilder, o: AssignmentExpr) -> Value: target = builder.get_assignment_target(o.target) builder.assign(target, value, o.line) return value + + +def transform_math_literal(builder: IRBuilder, fullname: str) -> Value | None: + if fullname == "math.e": + return builder.load_float(math.e) + if fullname == "math.pi": + return builder.load_float(math.pi) + if fullname == "math.inf": + return builder.load_float(math.inf) + if fullname == "math.nan": + return builder.load_float(math.nan) + if fullname == "math.tau": + return builder.load_float(math.tau) + + return None diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 61dbbe960eb2..5d8315e88f72 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -12,10 +12,12 @@ from mypy.nodes import ( ARG_POS, CallExpr, + DictionaryComprehension, Expression, GeneratorExpr, Lvalue, MemberExpr, + NameExpr, RefExpr, SetExpr, TupleExpr, @@ -28,6 +30,7 @@ IntOp, LoadAddress, LoadMem, + RaiseStandardError, Register, TupleGet, TupleSet, @@ -229,6 +232,9 @@ def set_item(item_index: Value) -> None: def translate_list_comprehension(builder: IRBuilder, gen: GeneratorExpr) -> Value: + if raise_error_if_contains_unreachable_names(builder, gen): + return builder.none() + # Try simplest list comprehension, otherwise fall back to general one val = sequence_from_generator_preallocate_helper( builder, @@ -251,7 +257,30 @@ def gen_inner_stmts() -> None: return builder.read(list_ops) +def raise_error_if_contains_unreachable_names( + builder: IRBuilder, gen: GeneratorExpr | DictionaryComprehension +) -> bool: + """Raise a runtime error and return True if generator contains unreachable names. + + False is returned if the generator can be safely transformed without crashing. + (It may still be unreachable!) + """ + if any(isinstance(s, NameExpr) and s.node is None for s in gen.indices): + error = RaiseStandardError( + RaiseStandardError.RUNTIME_ERROR, + "mypyc internal error: should be unreachable", + gen.line, + ) + builder.add(error) + return True + + return False + + def translate_set_comprehension(builder: IRBuilder, gen: GeneratorExpr) -> Value: + if raise_error_if_contains_unreachable_names(builder, gen): + return builder.none() + set_ops = builder.maybe_spill(builder.new_set_op([], gen.line)) loop_params = list(zip(gen.indices, gen.sequences, gen.condlists, gen.is_async)) diff --git a/mypyc/irbuild/format_str_tokenizer.py b/mypyc/irbuild/format_str_tokenizer.py index 5ab38d0f2264..0b46887811fb 100644 --- a/mypyc/irbuild/format_str_tokenizer.py +++ b/mypyc/irbuild/format_str_tokenizer.py @@ -3,7 +3,7 @@ from __future__ import annotations from enum import Enum, unique -from typing_extensions import Final +from typing import Final from mypy.checkstrformat import ( ConversionSpecifier, @@ -13,6 +13,7 @@ from mypy.errors import Errors from mypy.messages import MessageBuilder from mypy.nodes import Context, Expression +from mypy.options import Options from mypyc.ir.ops import Integer, Value from mypyc.ir.rtypes import ( c_pyssize_t_rprimitive, @@ -108,7 +109,9 @@ def tokenizer_format_call(format_str: str) -> tuple[list[str], list[FormatOp]] | """ # Creates an empty MessageBuilder here. # It wouldn't be used since the code has passed the type-checking. - specifiers = parse_format_value(format_str, EMPTY_CONTEXT, MessageBuilder(Errors(), {})) + specifiers = parse_format_value( + format_str, EMPTY_CONTEXT, MessageBuilder(Errors(Options()), {}) + ) if specifiers is None: return None format_ops = generate_format_ops(specifiers) diff --git a/mypyc/irbuild/function.py b/mypyc/irbuild/function.py index 02155d70e928..c985e88b0e0c 100644 --- a/mypyc/irbuild/function.py +++ b/mypyc/irbuild/function.py @@ -19,6 +19,7 @@ ArgKind, ClassDef, Decorator, + FuncBase, FuncDef, FuncItem, LambdaExpr, @@ -102,7 +103,7 @@ def transform_func_def(builder: IRBuilder, fdef: FuncDef) -> None: if func_reg: builder.assign(get_func_target(builder, fdef), func_reg, fdef.line) maybe_insert_into_registry_dict(builder, fdef) - builder.functions.append(func_ir) + builder.add_function(func_ir, fdef.line) def transform_overloaded_func_def(builder: IRBuilder, o: OverloadedFuncDef) -> None: @@ -222,6 +223,7 @@ def c() -> None: is_decorated = fitem in builder.fdefs_to_decorators is_singledispatch = fitem in builder.singledispatch_impls in_non_ext = False + add_nested_funcs_to_env = has_nested_func_self_reference(builder, fitem) class_name = None if cdef: ir = builder.mapper.type_to_ir[cdef.info] @@ -234,14 +236,15 @@ def c() -> None: func_name = name builder.enter( FuncInfo( - fitem, - func_name, - class_name, - gen_func_ns(builder), - is_nested, - contains_nested, - is_decorated, - in_non_ext, + fitem=fitem, + name=func_name, + class_name=class_name, + namespace=gen_func_ns(builder), + is_nested=is_nested, + contains_nested=contains_nested, + is_decorated=is_decorated, + in_non_ext=in_non_ext, + add_nested_funcs_to_env=add_nested_funcs_to_env, ) ) @@ -267,7 +270,13 @@ def c() -> None: builder.enter(fn_info) setup_env_for_generator_class(builder) load_outer_envs(builder, builder.fn_info.generator_class) - if builder.fn_info.is_nested and isinstance(fitem, FuncDef): + top_level = builder.top_level_fn_info() + if ( + builder.fn_info.is_nested + and isinstance(fitem, FuncDef) + and top_level + and top_level.add_nested_funcs_to_env + ): setup_func_for_recursive_call(builder, fitem, builder.fn_info.generator_class) create_switch_for_generator_class(builder) add_raise_exception_blocks_to_generator_class(builder, fitem.line) @@ -344,6 +353,20 @@ def c() -> None: return func_ir, func_reg +def has_nested_func_self_reference(builder: IRBuilder, fitem: FuncItem) -> bool: + """Does a nested function contain a self-reference in its body? + + If a nested function only has references in the surrounding function, + we don't need to add it to the environment. + """ + if any(isinstance(sym, FuncBase) for sym in builder.free_variables.get(fitem, set())): + return True + return any( + has_nested_func_self_reference(builder, nested) + for nested in builder.encapsulating_funcs.get(fitem, []) + ) + + def gen_func_ir( builder: IRBuilder, args: list[Register], @@ -435,7 +458,6 @@ def handle_ext_method(builder: IRBuilder, cdef: ClassDef, fdef: FuncDef) -> None class_ir.method_decls[name].sig, base.method_decls[name].sig ) ): - # TODO: Support contravariant subtyping in the input argument for # property setters. Need to make a special glue method for handling this, # similar to gen_glue_property. @@ -516,7 +538,7 @@ def gen_func_ns(builder: IRBuilder) -> str: return "_".join( info.name + ("" if not info.class_name else "_" + info.class_name) for info in builder.fn_infos - if info.name and info.name != "" + if info.name and info.name != "" ) @@ -643,7 +665,7 @@ def f(builder: IRBuilder, x: object) -> int: ... args = args[: -base_sig.num_bitmap_args] arg_kinds = arg_kinds[: -base_sig.num_bitmap_args] arg_names = arg_names[: -base_sig.num_bitmap_args] - bitmap_args = builder.builder.args[-base_sig.num_bitmap_args :] + bitmap_args = list(builder.builder.args[-base_sig.num_bitmap_args :]) # We can do a passthrough *args/**kwargs with a native call, but if the # args need to get distributed out to arguments, we just let python handle it @@ -769,7 +791,7 @@ def get_func_target(builder: IRBuilder, fdef: FuncDef) -> AssignmentTarget: # Get the target associated with the previously defined FuncDef. return builder.lookup(fdef.original_def) - if builder.fn_info.is_generator or builder.fn_info.contains_nested: + if builder.fn_info.is_generator or builder.fn_info.add_nested_funcs_to_env: return builder.lookup(fdef) return builder.add_local_reg(fdef, object_rprimitive) @@ -867,9 +889,8 @@ def gen_native_func_call_and_return(fdef: FuncDef) -> None: call_impl, next_impl = BasicBlock(), BasicBlock() current_id = builder.load_int(i) - builder.builder.compare_tagged_condition( - passed_id, current_id, "==", call_impl, next_impl, line - ) + cond = builder.binary_op(passed_id, current_id, "==", line) + builder.add_bool_branch(cond, call_impl, next_impl) # Call the registered implementation builder.activate_block(call_impl) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 2391ccc4d0ed..0c9310e6a5ca 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -10,8 +10,7 @@ from __future__ import annotations -from typing import Callable, Optional, Sequence, Tuple -from typing_extensions import Final +from typing import Callable, Final, Optional, Sequence, Tuple from mypy.argmap import map_actuals_to_formals from mypy.nodes import ARG_POS, ARG_STAR, ARG_STAR2, ArgKind @@ -28,6 +27,7 @@ use_method_vectorcall, use_vectorcall, ) +from mypyc.errors import Errors from mypyc.ir.class_ir import ClassIR, all_concrete_classes from mypyc.ir.func_ir import FuncDecl, FuncSignature from mypyc.ir.ops import ( @@ -46,6 +46,10 @@ Cast, ComparisonOp, Extend, + Float, + FloatComparisonOp, + FloatNeg, + FloatOp, GetAttr, GetElementPtr, Goto, @@ -59,21 +63,23 @@ LoadStatic, MethodCall, Op, + PrimitiveDescription, + PrimitiveOp, RaiseStandardError, Register, - SetMem, Truncate, TupleGet, + TupleSet, Unbox, Unreachable, Value, + float_comparison_op_to_id, + float_op_to_id, int_op_to_id, ) from mypyc.ir.rtypes import ( - PyListObject, PyObject, PySetObject, - PyVarObject, RArray, RInstance, RPrimitive, @@ -88,6 +94,7 @@ c_pointer_rprimitive, c_pyssize_t_rprimitive, c_size_t_rprimitive, + check_native_int_range, dict_rprimitive, float_rprimitive, int_rprimitive, @@ -96,6 +103,8 @@ is_bytes_rprimitive, is_dict_rprimitive, is_fixed_width_rtype, + is_float_rprimitive, + is_int16_rprimitive, is_int32_rprimitive, is_int64_rprimitive, is_int_rprimitive, @@ -106,6 +115,7 @@ is_str_rprimitive, is_tagged, is_tuple_rprimitive, + is_uint8_rprimitive, list_rprimitive, none_rprimitive, object_pointer_rprimitive, @@ -115,7 +125,6 @@ short_int_rprimitive, str_rprimitive, ) -from mypyc.irbuild.mapper import Mapper from mypyc.irbuild.util import concrete_arg_kind from mypyc.options import CompilerOptions from mypyc.primitives.bytes_ops import bytes_compare @@ -126,6 +135,7 @@ dict_update_in_display_op, ) from mypyc.primitives.exc_ops import err_occurred_op, keep_propagating_op +from mypyc.primitives.float_ops import copysign_op, int_to_float_op from mypyc.primitives.generic_ops import ( generic_len_op, generic_ssize_t_len_op, @@ -137,19 +147,28 @@ py_vectorcall_op, ) from mypyc.primitives.int_ops import ( + int16_divide_op, + int16_mod_op, + int16_overflow, int32_divide_op, int32_mod_op, int32_overflow, int64_divide_op, int64_mod_op, int64_to_int_op, - int_comparison_op_mapping, int_to_int32_op, int_to_int64_op, ssize_t_to_int_op, + uint8_overflow, +) +from mypyc.primitives.list_ops import list_build_op, list_extend_op, list_items, new_list_op +from mypyc.primitives.misc_ops import ( + bool_op, + buf_init_item, + fast_isinstance_op, + none_object_op, + var_object_size, ) -from mypyc.primitives.list_ops import list_build_op, list_extend_op, new_list_op -from mypyc.primitives.misc_ops import bool_op, fast_isinstance_op, none_object_op from mypyc.primitives.registry import ( ERR_NEG_INT, CFunctionDescription, @@ -204,9 +223,8 @@ class LowLevelIRBuilder: - def __init__(self, current_module: str, mapper: Mapper, options: CompilerOptions) -> None: - self.current_module = current_module - self.mapper = mapper + def __init__(self, errors: Errors | None, options: CompilerOptions) -> None: + self.errors = errors self.options = options self.args: list[Register] = [] self.blocks: list[BasicBlock] = [] @@ -216,6 +234,11 @@ def __init__(self, current_module: str, mapper: Mapper, options: CompilerOptions # temporaries. Use flush_keep_alives() to mark the end of the live range. self.keep_alives: list[Value] = [] + def set_module(self, module_name: str, module_path: str) -> None: + """Set the name and path of the current module.""" + self.module_name = module_name + self.module_path = module_path + # Basic operations def add(self, op: Op) -> Value: @@ -242,6 +265,9 @@ def goto_and_activate(self, block: BasicBlock) -> None: self.goto(block) self.activate_block(block) + def keep_alive(self, values: list[Value], *, steal: bool = False) -> None: + self.add(KeepAlive(values, steal=steal)) + def push_error_handler(self, handler: BasicBlock | None) -> None: self.error_handlers.append(handler) @@ -257,7 +283,7 @@ def self(self) -> Register: def flush_keep_alives(self) -> None: if self.keep_alives: - self.add(KeepAlive(self.keep_alives[:])) + self.add(KeepAlive(self.keep_alives.copy())) self.keep_alives = [] # Type conversions @@ -311,7 +337,9 @@ def coerce( and is_short_int_rprimitive(src_type) and is_fixed_width_rtype(target_type) ): - # TODO: range check + value = src.numeric_value() + if not check_native_int_range(target_type, value): + self.error(f'Value {value} is out of range for "{target_type}"', line) return Integer(src.value >> 1, target_type) elif is_int_rprimitive(src_type) and is_fixed_width_rtype(target_type): return self.coerce_int_to_fixed_width(src, target_type, line) @@ -340,11 +368,39 @@ def coerce( is_bool_rprimitive(src_type) or is_bit_rprimitive(src_type) ) and is_fixed_width_rtype(target_type): return self.add(Extend(src, target_type, signed=False)) - else: - # To go from one unboxed type to another, we go through a boxed - # in-between value, for simplicity. - tmp = self.box(src) - return self.unbox_or_cast(tmp, target_type, line) + elif isinstance(src, Integer) and is_float_rprimitive(target_type): + if is_tagged(src_type): + return Float(float(src.value // 2)) + return Float(float(src.value)) + elif is_tagged(src_type) and is_float_rprimitive(target_type): + return self.int_to_float(src, line) + elif ( + isinstance(src_type, RTuple) + and isinstance(target_type, RTuple) + and len(src_type.types) == len(target_type.types) + ): + # Coerce between two tuple types by coercing each item separately + values = [] + for i in range(len(src_type.types)): + v = None + if isinstance(src, TupleSet): + item = src.items[i] + # We can't reuse register values, since they can be modified. + if not isinstance(item, Register): + v = item + if v is None: + v = TupleGet(src, i) + self.add(v) + values.append(v) + return self.add( + TupleSet( + [self.coerce(v, t, line) for v, t in zip(values, target_type.types)], line + ) + ) + # To go between any other unboxed types, we go through a boxed + # in-between value, for simplicity. + tmp = self.box(src) + return self.unbox_or_cast(tmp, target_type, line) if (not src_type.is_unboxed and target_type.is_unboxed) or not is_subtype( src_type, target_type ): @@ -373,10 +429,16 @@ def coerce_int_to_fixed_width(self, src: Value, target_type: RType, line: int) - # Add a range check when the target type is smaller than the source tyoe fast2, fast3 = BasicBlock(), BasicBlock() upper_bound = 1 << (size * 8 - 1) + if not target_type.is_signed: + upper_bound *= 2 check2 = self.add(ComparisonOp(src, Integer(upper_bound, src.type), ComparisonOp.SLT)) self.add(Branch(check2, fast2, slow, Branch.BOOL)) self.activate_block(fast2) - check3 = self.add(ComparisonOp(src, Integer(-upper_bound, src.type), ComparisonOp.SGE)) + if target_type.is_signed: + lower_bound = -upper_bound + else: + lower_bound = 0 + check3 = self.add(ComparisonOp(src, Integer(lower_bound, src.type), ComparisonOp.SGE)) self.add(Branch(check3, fast3, slow, Branch.BOOL)) self.activate_block(fast3) tmp = self.int_op( @@ -419,6 +481,14 @@ def coerce_int_to_fixed_width(self, src: Value, target_type: RType, line: int) - # Slow path just always generates an OverflowError self.call_c(int32_overflow, [], line) self.add(Unreachable()) + elif is_int16_rprimitive(target_type): + # Slow path just always generates an OverflowError + self.call_c(int16_overflow, [], line) + self.add(Unreachable()) + elif is_uint8_rprimitive(target_type): + # Slow path just always generates an OverflowError + self.call_c(uint8_overflow, [], line) + self.add(Unreachable()) else: assert False, target_type @@ -432,9 +502,13 @@ def coerce_short_int_to_fixed_width(self, src: Value, target_type: RType, line: assert False, (src.type, target_type) def coerce_fixed_width_to_int(self, src: Value, line: int) -> Value: - if is_int32_rprimitive(src.type) and PLATFORM_SIZE == 8: + if ( + (is_int32_rprimitive(src.type) and PLATFORM_SIZE == 8) + or is_int16_rprimitive(src.type) + or is_uint8_rprimitive(src.type) + ): # Simple case -- just sign extend and shift. - extended = self.add(Extend(src, c_pyssize_t_rprimitive, signed=True)) + extended = self.add(Extend(src, c_pyssize_t_rprimitive, signed=src.type.is_signed)) return self.int_op( int_rprimitive, extended, @@ -839,10 +913,8 @@ def _py_vector_call( ): if arg_values: # Create a C array containing all arguments as boxed values. - array = Register(RArray(object_rprimitive, len(arg_values))) coerced_args = [self.coerce(arg, object_rprimitive, line) for arg in arg_values] - self.add(AssignMulti(array, coerced_args)) - arg_ptr = self.add(LoadAddress(object_pointer_rprimitive, array)) + arg_ptr = self.setup_rarray(object_rprimitive, coerced_args, object_ptr=True) else: arg_ptr = Integer(0, object_pointer_rprimitive) num_pos = num_positional_args(arg_values, arg_kinds) @@ -916,13 +988,10 @@ def _py_vector_method_call( not kind.is_star() and not kind.is_optional() for kind in arg_kinds ): method_name_reg = self.load_str(method_name) - array = Register(RArray(object_rprimitive, len(arg_values) + 1)) - self_arg = self.coerce(obj, object_rprimitive, line) - coerced_args = [self_arg] + [ - self.coerce(arg, object_rprimitive, line) for arg in arg_values + coerced_args = [ + self.coerce(arg, object_rprimitive, line) for arg in [obj] + arg_values ] - self.add(AssignMulti(array, coerced_args)) - arg_ptr = self.add(LoadAddress(object_pointer_rprimitive, array)) + arg_ptr = self.setup_rarray(object_rprimitive, coerced_args, object_ptr=True) num_pos = num_positional_args(arg_values, arg_kinds) keywords = self._vectorcall_keywords(arg_names) value = self.call_c( @@ -1027,6 +1096,8 @@ def native_args_to_positional( elif not lst: if is_fixed_width_rtype(arg.type): output_arg = Integer(0, arg.type) + elif is_float_rprimitive(arg.type): + output_arg = Float(0.0) else: output_arg = self.add(LoadErrorValue(arg.type, is_borrowed=True)) else: @@ -1166,7 +1237,7 @@ def load_int(self, value: int) -> Value: def load_float(self, value: float) -> Value: """Load a float literal value.""" - return self.add(LoadLiteral(value, float_rprimitive)) + return Float(value) def load_str(self, value: str) -> Value: """Load a str literal value. @@ -1246,8 +1317,6 @@ def binary_op(self, lreg: Value, rreg: Value, op: str, line: int) -> Value: return self.compare_strings(lreg, rreg, op, line) if is_bytes_rprimitive(ltype) and is_bytes_rprimitive(rtype) and op in ("==", "!="): return self.compare_bytes(lreg, rreg, op, line) - if is_tagged(ltype) and is_tagged(rtype) and op in int_comparison_op_mapping: - return self.compare_tagged(lreg, rreg, op, line) if is_bool_rprimitive(ltype) and is_bool_rprimitive(rtype) and op in BOOL_BINARY_OPS: if op in ComparisonOp.signed_ops: return self.bool_comparison_op(lreg, rreg, op, line) @@ -1269,9 +1338,8 @@ def binary_op(self, lreg: Value, rreg: Value, op: str, line: int) -> Value: if is_fixed_width_rtype(rtype) or is_tagged(rtype): return self.fixed_width_int_op(ltype, lreg, rreg, op_id, line) if isinstance(rreg, Integer): - # TODO: Check what kind of Integer return self.fixed_width_int_op( - ltype, lreg, Integer(rreg.value >> 1, ltype), op_id, line + ltype, lreg, self.coerce(rreg, ltype, line), op_id, line ) elif op in ComparisonOp.signed_ops: if is_int_rprimitive(rtype): @@ -1282,7 +1350,7 @@ def binary_op(self, lreg: Value, rreg: Value, op: str, line: int) -> Value: if is_fixed_width_rtype(rreg.type): return self.comparison_op(lreg, rreg, op_id, line) if isinstance(rreg, Integer): - return self.comparison_op(lreg, Integer(rreg.value >> 1, ltype), op_id, line) + return self.comparison_op(lreg, self.coerce(rreg, ltype, line), op_id, line) elif is_fixed_width_rtype(rtype): if op in FIXED_WIDTH_INT_BINARY_OPS: if op.endswith("="): @@ -1292,9 +1360,8 @@ def binary_op(self, lreg: Value, rreg: Value, op: str, line: int) -> Value: else: op_id = IntOp.DIV if isinstance(lreg, Integer): - # TODO: Check what kind of Integer return self.fixed_width_int_op( - rtype, Integer(lreg.value >> 1, rtype), rreg, op_id, line + rtype, self.coerce(lreg, rtype, line), rreg, op_id, line ) if is_tagged(ltype): return self.fixed_width_int_op(rtype, lreg, rreg, op_id, line) @@ -1308,29 +1375,31 @@ def binary_op(self, lreg: Value, rreg: Value, op: str, line: int) -> Value: lreg = self.coerce(lreg, rtype, line) op_id = ComparisonOp.signed_ops[op] if isinstance(lreg, Integer): - return self.comparison_op(Integer(lreg.value >> 1, rtype), rreg, op_id, line) + return self.comparison_op(self.coerce(lreg, rtype, line), rreg, op_id, line) if is_fixed_width_rtype(lreg.type): return self.comparison_op(lreg, rreg, op_id, line) - # Mixed int comparisons - if op in ("==", "!="): - op_id = ComparisonOp.signed_ops[op] - if is_tagged(ltype) and is_subtype(rtype, ltype): - rreg = self.coerce(rreg, int_rprimitive, line) - return self.comparison_op(lreg, rreg, op_id, line) - if is_tagged(rtype) and is_subtype(ltype, rtype): - lreg = self.coerce(lreg, int_rprimitive, line) - return self.comparison_op(lreg, rreg, op_id, line) - elif op in op in int_comparison_op_mapping: - if is_tagged(ltype) and is_subtype(rtype, ltype): - rreg = self.coerce(rreg, short_int_rprimitive, line) - return self.compare_tagged(lreg, rreg, op, line) - if is_tagged(rtype) and is_subtype(ltype, rtype): - lreg = self.coerce(lreg, short_int_rprimitive, line) - return self.compare_tagged(lreg, rreg, op, line) - - call_c_ops_candidates = binary_ops.get(op, []) - target = self.matching_call_c(call_c_ops_candidates, [lreg, rreg], line) + if is_float_rprimitive(ltype) or is_float_rprimitive(rtype): + if isinstance(lreg, Integer): + lreg = Float(float(lreg.numeric_value())) + elif isinstance(rreg, Integer): + rreg = Float(float(rreg.numeric_value())) + elif is_int_rprimitive(lreg.type): + lreg = self.int_to_float(lreg, line) + elif is_int_rprimitive(rreg.type): + rreg = self.int_to_float(rreg, line) + if is_float_rprimitive(lreg.type) and is_float_rprimitive(rreg.type): + if op in float_comparison_op_to_id: + return self.compare_floats(lreg, rreg, float_comparison_op_to_id[op], line) + if op.endswith("="): + base_op = op[:-1] + else: + base_op = op + if base_op in float_op_to_id: + return self.float_op(lreg, rreg, base_op, line) + + primitive_ops_candidates = binary_ops.get(op, []) + target = self.matching_primitive_op(primitive_ops_candidates, [lreg, rreg], line) assert target, "Unsupported binary operation: %s" % op return target @@ -1346,95 +1415,6 @@ def check_tagged_short_int(self, val: Value, line: int, negated: bool = False) - check = self.comparison_op(bitwise_and, zero, op, line) return check - def compare_tagged(self, lhs: Value, rhs: Value, op: str, line: int) -> Value: - """Compare two tagged integers using given operator (value context).""" - # generate fast binary logic ops on short ints - if is_short_int_rprimitive(lhs.type) and is_short_int_rprimitive(rhs.type): - return self.comparison_op(lhs, rhs, int_comparison_op_mapping[op][0], line) - op_type, c_func_desc, negate_result, swap_op = int_comparison_op_mapping[op] - result = Register(bool_rprimitive) - short_int_block, int_block, out = BasicBlock(), BasicBlock(), BasicBlock() - check_lhs = self.check_tagged_short_int(lhs, line) - if op in ("==", "!="): - check = check_lhs - else: - # for non-equality logical ops (less/greater than, etc.), need to check both sides - check_rhs = self.check_tagged_short_int(rhs, line) - check = self.int_op(bit_rprimitive, check_lhs, check_rhs, IntOp.AND, line) - self.add(Branch(check, short_int_block, int_block, Branch.BOOL)) - self.activate_block(short_int_block) - eq = self.comparison_op(lhs, rhs, op_type, line) - self.add(Assign(result, eq, line)) - self.goto(out) - self.activate_block(int_block) - if swap_op: - args = [rhs, lhs] - else: - args = [lhs, rhs] - call = self.call_c(c_func_desc, args, line) - if negate_result: - # TODO: introduce UnaryIntOp? - call_result = self.unary_op(call, "not", line) - else: - call_result = call - self.add(Assign(result, call_result, line)) - self.goto_and_activate(out) - return result - - def compare_tagged_condition( - self, lhs: Value, rhs: Value, op: str, true: BasicBlock, false: BasicBlock, line: int - ) -> None: - """Compare two tagged integers using given operator (conditional context). - - Assume lhs and rhs are tagged integers. - - Args: - lhs: Left operand - rhs: Right operand - op: Operation, one of '==', '!=', '<', '<=', '>', '<=' - true: Branch target if comparison is true - false: Branch target if comparison is false - """ - is_eq = op in ("==", "!=") - if (is_short_int_rprimitive(lhs.type) and is_short_int_rprimitive(rhs.type)) or ( - is_eq and (is_short_int_rprimitive(lhs.type) or is_short_int_rprimitive(rhs.type)) - ): - # We can skip the tag check - check = self.comparison_op(lhs, rhs, int_comparison_op_mapping[op][0], line) - self.flush_keep_alives() - self.add(Branch(check, true, false, Branch.BOOL)) - return - op_type, c_func_desc, negate_result, swap_op = int_comparison_op_mapping[op] - int_block, short_int_block = BasicBlock(), BasicBlock() - check_lhs = self.check_tagged_short_int(lhs, line, negated=True) - if is_eq or is_short_int_rprimitive(rhs.type): - self.flush_keep_alives() - self.add(Branch(check_lhs, int_block, short_int_block, Branch.BOOL)) - else: - # For non-equality logical ops (less/greater than, etc.), need to check both sides - rhs_block = BasicBlock() - self.add(Branch(check_lhs, int_block, rhs_block, Branch.BOOL)) - self.activate_block(rhs_block) - check_rhs = self.check_tagged_short_int(rhs, line, negated=True) - self.flush_keep_alives() - self.add(Branch(check_rhs, int_block, short_int_block, Branch.BOOL)) - # Arbitrary integers (slow path) - self.activate_block(int_block) - if swap_op: - args = [rhs, lhs] - else: - args = [lhs, rhs] - call = self.call_c(c_func_desc, args, line) - if negate_result: - self.add(Branch(call, false, true, Branch.BOOL)) - else: - self.flush_keep_alives() - self.add(Branch(call, true, false, Branch.BOOL)) - # Short integers (fast path) - self.activate_block(short_int_block) - eq = self.comparison_op(lhs, rhs, op_type, line) - self.add(Branch(eq, true, false, Branch.BOOL)) - def compare_strings(self, lhs: Value, rhs: Value, op: str, line: int) -> Value: """Compare two strings""" compare_result = self.call_c(unicode_compare, [lhs, rhs], line) @@ -1552,10 +1532,21 @@ def unary_op(self, value: Value, expr_op: str, line: int) -> Value: # Translate to '0 - x' return self.int_op(typ, Integer(0, typ), value, IntOp.SUB, line) elif expr_op == "~": - # Translate to 'x ^ -1' - return self.int_op(typ, value, Integer(-1, typ), IntOp.XOR, line) + if typ.is_signed: + # Translate to 'x ^ -1' + return self.int_op(typ, value, Integer(-1, typ), IntOp.XOR, line) + else: + # Translate to 'x ^ 0xff...' + mask = (1 << (typ.size * 8)) - 1 + return self.int_op(typ, value, Integer(mask, typ), IntOp.XOR, line) elif expr_op == "+": return value + if is_float_rprimitive(typ): + if expr_op == "-": + return self.add(FloatNeg(value, line)) + elif expr_op == "+": + return value + if isinstance(value, Integer): # TODO: Overflow? Unsigned? num = value.value @@ -1564,6 +1555,8 @@ def unary_op(self, value: Value, expr_op: str, line: int) -> Value: return Integer(-num, typ, value.line) if is_tagged(typ) and expr_op == "+": return value + if isinstance(value, Float): + return Float(-value.value, value.line) if isinstance(typ, RInstance): if expr_op == "-": method = "__neg__" @@ -1631,26 +1624,30 @@ def new_list_op(self, values: list[Value], line: int) -> Value: # for-loop and inline the SetMem operation, which is faster # than list_build_op, however generates more code. result_list = self.call_c(new_list_op, length, line) - if len(values) == 0: + if not values: return result_list args = [self.coerce(item, object_rprimitive, line) for item in values] - ob_item_ptr = self.add(GetElementPtr(result_list, PyListObject, "ob_item", line)) - ob_item_base = self.add(LoadMem(pointer_rprimitive, ob_item_ptr, line)) + ob_item_base = self.add(PrimitiveOp([result_list], list_items, line)) for i in range(len(values)): - if i == 0: - item_address = ob_item_base - else: - offset = Integer(PLATFORM_SIZE * i, c_pyssize_t_rprimitive, line) - item_address = self.add( - IntOp(pointer_rprimitive, ob_item_base, offset, IntOp.ADD, line) - ) - self.add(SetMem(object_rprimitive, item_address, args[i], line)) + self.primitive_op( + buf_init_item, [ob_item_base, Integer(i, c_pyssize_t_rprimitive), args[i]], line + ) self.add(KeepAlive([result_list])) return result_list def new_set_op(self, values: list[Value], line: int) -> Value: return self.call_c(new_set_op, values, line) + def setup_rarray( + self, item_type: RType, values: Sequence[Value], *, object_ptr: bool = False + ) -> Value: + """Declare and initialize a new RArray, returning its address.""" + array = Register(RArray(item_type, len(values))) + self.add(AssignMulti(array, list(values))) + return self.add( + LoadAddress(object_pointer_rprimitive if object_ptr else c_pointer_rprimitive, array) + ) + def shortcircuit_helper( self, op: str, @@ -1713,6 +1710,8 @@ def bool_value(self, value: Value) -> Value: ): # Directly call the __bool__ method on classes that have it. result = self.gen_method_call(value, "__bool__", [], bool_rprimitive, value.line) + elif is_float_rprimitive(value.type): + result = self.compare_floats(value, Float(0.0), FloatComparisonOp.NEQ, value.line) else: value_type = optional_value_type(value.type) if value_type is not None: @@ -1822,6 +1821,7 @@ def call_c( error_kind, line, var_arg_idx, + is_pure=desc.is_pure, ) ) if desc.is_borrowed: @@ -1878,6 +1878,103 @@ def matching_call_c( return target return None + def primitive_op( + self, + desc: PrimitiveDescription, + args: list[Value], + line: int, + result_type: RType | None = None, + ) -> Value: + """Add a primitive op.""" + # Does this primitive map into calling a Python C API + # or an internal mypyc C API function? + if desc.c_function_name: + # TODO: Generate PrimitiOps here and transform them into CallC + # ops only later in the lowering pass + c_desc = CFunctionDescription( + desc.name, + desc.arg_types, + desc.return_type, + desc.var_arg_type, + desc.truncated_type, + desc.c_function_name, + desc.error_kind, + desc.steals, + desc.is_borrowed, + desc.ordering, + desc.extra_int_constants, + desc.priority, + is_pure=desc.is_pure, + ) + return self.call_c(c_desc, args, line, result_type) + + # This primitve gets transformed in a lowering pass to + # lower-level IR ops using a custom transform function. + + coerced = [] + # Coerce fixed number arguments + for i in range(min(len(args), len(desc.arg_types))): + formal_type = desc.arg_types[i] + arg = args[i] + assert formal_type is not None # TODO + arg = self.coerce(arg, formal_type, line) + coerced.append(arg) + assert desc.ordering is None + assert desc.var_arg_type is None + assert not desc.extra_int_constants + target = self.add(PrimitiveOp(coerced, desc, line=line)) + if desc.is_borrowed: + # If the result is borrowed, force the arguments to be + # kept alive afterwards, as otherwise the result might be + # immediately freed, at the risk of a dangling pointer. + for arg in coerced: + if not isinstance(arg, (Integer, LoadLiteral)): + self.keep_alives.append(arg) + if desc.error_kind == ERR_NEG_INT: + comp = ComparisonOp(target, Integer(0, desc.return_type, line), ComparisonOp.SGE, line) + comp.error_kind = ERR_FALSE + self.add(comp) + + assert desc.truncated_type is None + result = target + if result_type and not is_runtime_subtype(result.type, result_type): + if is_none_rprimitive(result_type): + # Special case None return. The actual result may actually be a bool + # and so we can't just coerce it. + result = self.none() + else: + result = self.coerce(result, result_type, line, can_borrow=desc.is_borrowed) + return result + + def matching_primitive_op( + self, + candidates: list[PrimitiveDescription], + args: list[Value], + line: int, + result_type: RType | None = None, + can_borrow: bool = False, + ) -> Value | None: + matching: PrimitiveDescription | None = None + for desc in candidates: + if len(desc.arg_types) != len(args): + continue + if all( + # formal is not None and # TODO + is_subtype(actual.type, formal) + for actual, formal in zip(args, desc.arg_types) + ) and (not desc.is_borrowed or can_borrow): + if matching: + assert matching.priority != desc.priority, "Ambiguous:\n1) {}\n2) {}".format( + matching, desc + ) + if desc.priority > matching.priority: + matching = desc + else: + matching = desc + if matching: + return self.primitive_op(matching, args, line=line) + return None + def int_op(self, type: RType, lhs: Value, rhs: Value, op: int, line: int = -1) -> Value: """Generate a native integer binary op. @@ -1890,7 +1987,65 @@ def int_op(self, type: RType, lhs: Value, rhs: Value, op: int, line: int = -1) - """ return self.add(IntOp(type, lhs, rhs, op, line)) - def fixed_width_int_op(self, type: RType, lhs: Value, rhs: Value, op: int, line: int) -> Value: + def float_op(self, lhs: Value, rhs: Value, op: str, line: int) -> Value: + """Generate a native float binary arithmetic operation. + + This follows Python semantics (e.g. raise exception on division by zero). + Add a FloatOp directly if you want low-level semantics. + + Args: + op: Binary operator (e.g. '+' or '*') + """ + op_id = float_op_to_id[op] + if op_id in (FloatOp.DIV, FloatOp.MOD): + if not (isinstance(rhs, Float) and rhs.value != 0.0): + c = self.compare_floats(rhs, Float(0.0), FloatComparisonOp.EQ, line) + err, ok = BasicBlock(), BasicBlock() + self.add(Branch(c, err, ok, Branch.BOOL, rare=True)) + self.activate_block(err) + if op_id == FloatOp.DIV: + msg = "float division by zero" + else: + msg = "float modulo" + self.add(RaiseStandardError(RaiseStandardError.ZERO_DIVISION_ERROR, msg, line)) + self.add(Unreachable()) + self.activate_block(ok) + if op_id == FloatOp.MOD: + # Adjust the result to match Python semantics (FloatOp follows C semantics). + return self.float_mod(lhs, rhs, line) + else: + return self.add(FloatOp(lhs, rhs, op_id, line)) + + def float_mod(self, lhs: Value, rhs: Value, line: int) -> Value: + """Perform x % y on floats using Python semantics.""" + mod = self.add(FloatOp(lhs, rhs, FloatOp.MOD, line)) + res = Register(float_rprimitive) + self.add(Assign(res, mod)) + tricky, adjust, copysign, done = BasicBlock(), BasicBlock(), BasicBlock(), BasicBlock() + is_zero = self.add(FloatComparisonOp(res, Float(0.0), FloatComparisonOp.EQ, line)) + self.add(Branch(is_zero, copysign, tricky, Branch.BOOL)) + self.activate_block(tricky) + same_signs = self.is_same_float_signs(lhs, rhs, line) + self.add(Branch(same_signs, done, adjust, Branch.BOOL)) + self.activate_block(adjust) + adj = self.float_op(res, rhs, "+", line) + self.add(Assign(res, adj)) + self.add(Goto(done)) + self.activate_block(copysign) + # If the remainder is zero, CPython ensures the result has the + # same sign as the denominator. + adj = self.call_c(copysign_op, [Float(0.0), rhs], line) + self.add(Assign(res, adj)) + self.add(Goto(done)) + self.activate_block(done) + return res + + def compare_floats(self, lhs: Value, rhs: Value, op: int, line: int) -> Value: + return self.add(FloatComparisonOp(lhs, rhs, op, line)) + + def fixed_width_int_op( + self, type: RPrimitive, lhs: Value, rhs: Value, op: int, line: int + ) -> Value: """Generate a binary op using Python fixed-width integer semantics. These may differ in overflow/rounding behavior from native/C ops. @@ -1902,43 +2057,71 @@ def fixed_width_int_op(self, type: RType, lhs: Value, rhs: Value, op: int, line: lhs = self.coerce(lhs, type, line) rhs = self.coerce(rhs, type, line) if op == IntOp.DIV: - # Inline simple division by a constant, so that C - # compilers can optimize more if isinstance(rhs, Integer) and rhs.value not in (-1, 0): - return self.inline_fixed_width_divide(type, lhs, rhs, line) + if not type.is_signed: + return self.int_op(type, lhs, rhs, IntOp.DIV, line) + else: + # Inline simple division by a constant, so that C + # compilers can optimize more + return self.inline_fixed_width_divide(type, lhs, rhs, line) if is_int64_rprimitive(type): prim = int64_divide_op elif is_int32_rprimitive(type): prim = int32_divide_op + elif is_int16_rprimitive(type): + prim = int16_divide_op + elif is_uint8_rprimitive(type): + self.check_for_zero_division(rhs, type, line) + return self.int_op(type, lhs, rhs, op, line) else: assert False, type return self.call_c(prim, [lhs, rhs], line) if op == IntOp.MOD: - # Inline simple % by a constant, so that C - # compilers can optimize more if isinstance(rhs, Integer) and rhs.value not in (-1, 0): - return self.inline_fixed_width_mod(type, lhs, rhs, line) + if not type.is_signed: + return self.int_op(type, lhs, rhs, IntOp.MOD, line) + else: + # Inline simple % by a constant, so that C + # compilers can optimize more + return self.inline_fixed_width_mod(type, lhs, rhs, line) if is_int64_rprimitive(type): prim = int64_mod_op elif is_int32_rprimitive(type): prim = int32_mod_op + elif is_int16_rprimitive(type): + prim = int16_mod_op + elif is_uint8_rprimitive(type): + self.check_for_zero_division(rhs, type, line) + return self.int_op(type, lhs, rhs, op, line) else: assert False, type return self.call_c(prim, [lhs, rhs], line) return self.int_op(type, lhs, rhs, op, line) + def check_for_zero_division(self, rhs: Value, type: RType, line: int) -> None: + err, ok = BasicBlock(), BasicBlock() + is_zero = self.binary_op(rhs, Integer(0, type), "==", line) + self.add(Branch(is_zero, err, ok, Branch.BOOL)) + self.activate_block(err) + self.add( + RaiseStandardError( + RaiseStandardError.ZERO_DIVISION_ERROR, "integer division or modulo by zero", line + ) + ) + self.add(Unreachable()) + self.activate_block(ok) + def inline_fixed_width_divide(self, type: RType, lhs: Value, rhs: Value, line: int) -> Value: # Perform floor division (native division truncates) res = Register(type) div = self.int_op(type, lhs, rhs, IntOp.DIV, line) self.add(Assign(res, div)) - diff_signs = self.is_different_native_int_signs(type, lhs, rhs, line) + same_signs = self.is_same_native_int_signs(type, lhs, rhs, line) tricky, adjust, done = BasicBlock(), BasicBlock(), BasicBlock() - self.add(Branch(diff_signs, done, tricky, Branch.BOOL)) + self.add(Branch(same_signs, done, tricky, Branch.BOOL)) self.activate_block(tricky) mul = self.int_op(type, res, rhs, IntOp.MUL, line) mul_eq = self.add(ComparisonOp(mul, lhs, ComparisonOp.EQ, line)) - adjust = BasicBlock() self.add(Branch(mul_eq, done, adjust, Branch.BOOL)) self.activate_block(adjust) adj = self.int_op(type, res, Integer(1, type), IntOp.SUB, line) @@ -1952,12 +2135,11 @@ def inline_fixed_width_mod(self, type: RType, lhs: Value, rhs: Value, line: int) res = Register(type) mod = self.int_op(type, lhs, rhs, IntOp.MOD, line) self.add(Assign(res, mod)) - diff_signs = self.is_different_native_int_signs(type, lhs, rhs, line) + same_signs = self.is_same_native_int_signs(type, lhs, rhs, line) tricky, adjust, done = BasicBlock(), BasicBlock(), BasicBlock() - self.add(Branch(diff_signs, done, tricky, Branch.BOOL)) + self.add(Branch(same_signs, done, tricky, Branch.BOOL)) self.activate_block(tricky) is_zero = self.add(ComparisonOp(res, Integer(0, type), ComparisonOp.EQ, line)) - adjust = BasicBlock() self.add(Branch(is_zero, done, adjust, Branch.BOOL)) self.activate_block(adjust) adj = self.int_op(type, res, rhs, IntOp.ADD, line) @@ -1966,11 +2148,16 @@ def inline_fixed_width_mod(self, type: RType, lhs: Value, rhs: Value, line: int) self.activate_block(done) return res - def is_different_native_int_signs(self, type: RType, a: Value, b: Value, line: int) -> Value: + def is_same_native_int_signs(self, type: RType, a: Value, b: Value, line: int) -> Value: neg1 = self.add(ComparisonOp(a, Integer(0, type), ComparisonOp.SLT, line)) neg2 = self.add(ComparisonOp(b, Integer(0, type), ComparisonOp.SLT, line)) return self.add(ComparisonOp(neg1, neg2, ComparisonOp.EQ, line)) + def is_same_float_signs(self, a: Value, b: Value, line: int) -> Value: + neg1 = self.add(FloatComparisonOp(a, Float(0.0), FloatComparisonOp.LT, line)) + neg2 = self.add(FloatComparisonOp(b, Float(0.0), FloatComparisonOp.LT, line)) + return self.add(ComparisonOp(neg1, neg2, ComparisonOp.EQ, line)) + def comparison_op(self, lhs: Value, rhs: Value, op: int, line: int) -> Value: return self.add(ComparisonOp(lhs, rhs, op, line)) @@ -1983,9 +2170,7 @@ def builtin_len(self, val: Value, line: int, use_pyssize_t: bool = False) -> Val typ = val.type size_value = None if is_list_rprimitive(typ) or is_tuple_rprimitive(typ) or is_bytes_rprimitive(typ): - elem_address = self.add(GetElementPtr(val, PyVarObject, "ob_size")) - size_value = self.add(LoadMem(c_pyssize_t_rprimitive, elem_address)) - self.add(KeepAlive([val])) + size_value = self.primitive_op(var_object_size, [val], line) elif is_set_rprimitive(typ): elem_address = self.add(GetElementPtr(val, PySetObject, "used")) size_value = self.add(LoadMem(c_pyssize_t_rprimitive, elem_address)) @@ -2007,7 +2192,8 @@ def builtin_len(self, val: Value, line: int, use_pyssize_t: bool = False) -> Val length = self.gen_method_call(val, "__len__", [], int_rprimitive, line) length = self.coerce(length, int_rprimitive, line) ok, fail = BasicBlock(), BasicBlock() - self.compare_tagged_condition(length, Integer(0), ">=", ok, fail, line) + cond = self.binary_op(length, Integer(0), ">=", line) + self.add_bool_branch(cond, ok, fail) self.activate_block(fail) self.add( RaiseStandardError( @@ -2042,6 +2228,9 @@ def new_tuple_with_length(self, length: Value, line: int) -> Value: """ return self.call_c(new_tuple_with_length_op, [length], line) + def int_to_float(self, n: Value, line: int) -> Value: + return self.call_c(int_to_float_op, [n], line) + # Internal helpers def decompose_union_helper( @@ -2187,6 +2376,10 @@ def _create_dict(self, keys: list[Value], values: list[Value], line: int) -> Val else: return self.call_c(dict_new_op, [], line) + def error(self, msg: str, line: int) -> None: + assert self.errors is not None, "cannot generate errors in this compiler phase" + self.errors.error(msg, self.module_path, line) + def num_positional_args(arg_values: list[Value], arg_kinds: list[ArgKind] | None) -> int: if arg_kinds is None: diff --git a/mypyc/irbuild/main.py b/mypyc/irbuild/main.py index 9bbb90aad207..85b905393af1 100644 --- a/mypyc/irbuild/main.py +++ b/mypyc/irbuild/main.py @@ -130,7 +130,7 @@ def transform_mypy_file(builder: IRBuilder, mypyfile: MypyFile) -> None: ir = builder.mapper.type_to_ir[cls.info] builder.classes.append(ir) - builder.enter("") + builder.enter("") # Make sure we have a builtins import builder.gen_import("builtins", -1) diff --git a/mypyc/irbuild/mapper.py b/mypyc/irbuild/mapper.py index dddb35230fd5..a3abbb1f84fb 100644 --- a/mypyc/irbuild/mapper.py +++ b/mypyc/irbuild/mapper.py @@ -19,6 +19,7 @@ UnboundType, UninhabitedType, UnionType, + find_unpack_in_list, get_proper_type, ) from mypyc.ir.class_ir import ClassIR @@ -32,6 +33,7 @@ bytes_rprimitive, dict_rprimitive, float_rprimitive, + int16_rprimitive, int32_rprimitive, int64_rprimitive, int_rprimitive, @@ -42,6 +44,7 @@ set_rprimitive, str_rprimitive, tuple_rprimitive, + uint8_rprimitive, ) @@ -102,12 +105,19 @@ def type_to_rtype(self, typ: Type | None) -> RType: return int64_rprimitive elif typ.type.fullname == "mypy_extensions.i32": return int32_rprimitive + elif typ.type.fullname == "mypy_extensions.i16": + return int16_rprimitive + elif typ.type.fullname == "mypy_extensions.u8": + return uint8_rprimitive else: return object_rprimitive elif isinstance(typ, TupleType): # Use our unboxed tuples for raw tuples but fall back to - # being boxed for NamedTuple. - if typ.partial_fallback.type.fullname == "builtins.tuple": + # being boxed for NamedTuple or for variadic tuples. + if ( + typ.partial_fallback.type.fullname == "builtins.tuple" + and find_unpack_in_list(typ.items) is None + ): return RTuple([self.type_to_rtype(t) for t in typ.items]) else: return tuple_rprimitive diff --git a/mypyc/irbuild/nonlocalcontrol.py b/mypyc/irbuild/nonlocalcontrol.py index 02dd51283e95..0ac9bd3cee31 100644 --- a/mypyc/irbuild/nonlocalcontrol.py +++ b/mypyc/irbuild/nonlocalcontrol.py @@ -120,8 +120,7 @@ def __init__(self, outer: NonlocalControl) -> None: self.outer = outer @abstractmethod - def gen_cleanup(self, builder: IRBuilder, line: int) -> None: - ... + def gen_cleanup(self, builder: IRBuilder, line: int) -> None: ... def gen_break(self, builder: IRBuilder, line: int) -> None: self.gen_cleanup(builder, line) diff --git a/mypyc/irbuild/prebuildvisitor.py b/mypyc/irbuild/prebuildvisitor.py index d99453955002..17f907d42111 100644 --- a/mypyc/irbuild/prebuildvisitor.py +++ b/mypyc/irbuild/prebuildvisitor.py @@ -1,22 +1,25 @@ from __future__ import annotations from mypy.nodes import ( + Block, Decorator, Expression, FuncDef, FuncItem, + Import, LambdaExpr, MemberExpr, MypyFile, NameExpr, + Node, SymbolNode, Var, ) -from mypy.traverser import TraverserVisitor +from mypy.traverser import ExtendedTraverserVisitor from mypyc.errors import Errors -class PreBuildVisitor(TraverserVisitor): +class PreBuildVisitor(ExtendedTraverserVisitor): """Mypy file AST visitor run before building the IR. This collects various things, including: @@ -26,6 +29,7 @@ class PreBuildVisitor(TraverserVisitor): * Find non-local variables (free variables) * Find property setters * Find decorators of functions + * Find module import groups The main IR build pass uses this information. """ @@ -68,10 +72,26 @@ def __init__( # Map function to indices of decorators to remove self.decorators_to_remove: dict[FuncDef, list[int]] = decorators_to_remove + # A mapping of import groups (a series of Import nodes with + # nothing inbetween) where each group is keyed by its first + # import node. + self.module_import_groups: dict[Import, list[Import]] = {} + self._current_import_group: Import | None = None + self.errors: Errors = errors self.current_file: MypyFile = current_file + def visit(self, o: Node) -> bool: + if not isinstance(o, Import): + self._current_import_group = None + return True + + def visit_block(self, block: Block) -> None: + self._current_import_group = None + super().visit_block(block) + self._current_import_group = None + def visit_decorator(self, dec: Decorator) -> None: if dec.decorators: # Only add the function being decorated if there exist @@ -99,9 +119,10 @@ def visit_decorator(self, dec: Decorator) -> None: self.funcs_to_decorators[dec.func] = decorators_to_store super().visit_decorator(dec) - def visit_func_def(self, fdef: FuncItem) -> None: + def visit_func_def(self, fdef: FuncDef) -> None: # TODO: What about overloaded functions? self.visit_func(fdef) + self.visit_symbol_node(fdef) def visit_lambda_expr(self, expr: LambdaExpr) -> None: self.visit_func(expr) @@ -123,6 +144,14 @@ def visit_func(self, func: FuncItem) -> None: super().visit_func(func) self.funcs.pop() + def visit_import(self, imp: Import) -> None: + if self._current_import_group is not None: + self.module_import_groups[self._current_import_group].append(imp) + else: + self.module_import_groups[imp] = [imp] + self._current_import_group = imp + super().visit_import(imp) + def visit_name_expr(self, expr: NameExpr) -> None: if isinstance(expr.node, (Var, FuncDef)): self.visit_symbol_node(expr.node) diff --git a/mypyc/irbuild/prepare.py b/mypyc/irbuild/prepare.py index b3d10887ce21..29e06439abdd 100644 --- a/mypyc/irbuild/prepare.py +++ b/mypyc/irbuild/prepare.py @@ -85,7 +85,7 @@ def build_type_map( ) class_ir.is_ext_class = is_extension_class(cdef) if class_ir.is_ext_class: - class_ir.deletable = cdef.info.deletable_attributes[:] + class_ir.deletable = cdef.info.deletable_attributes.copy() # If global optimizations are disabled, turn of tracking of class children if not options.global_opts: class_ir.children = None @@ -139,7 +139,7 @@ def is_from_module(node: SymbolNode, module: MypyFile) -> bool: def load_type_map(mapper: Mapper, modules: list[MypyFile], deser_ctx: DeserMaps) -> None: """Populate a Mapper with deserialized IR from a list of modules.""" for module in modules: - for name, node in module.names.items(): + for node in module.names.values(): if isinstance(node.node, TypeInfo) and is_from_module(node.node, module): ir = deser_ctx.classes[node.node.fullname] mapper.type_to_ir[node.node] = ir @@ -153,7 +153,7 @@ def load_type_map(mapper: Mapper, modules: list[MypyFile], deser_ctx: DeserMaps) def get_module_func_defs(module: MypyFile) -> Iterable[FuncDef]: """Collect all of the (non-method) functions declared in a module.""" - for name, node in module.names.items(): + for node in module.names.values(): # We need to filter out functions that are imported or # aliases. The best way to do this seems to be by # checking that the fullname matches. @@ -465,11 +465,10 @@ def prepare_init_method(cdef: ClassDef, ir: ClassIR, module_name: str, mapper: M def prepare_non_ext_class_def( path: str, module_name: str, cdef: ClassDef, errors: Errors, mapper: Mapper ) -> None: - ir = mapper.type_to_ir[cdef.info] info = cdef.info - for name, node in info.names.items(): + for node in info.names.values(): if isinstance(node.node, (FuncDef, Decorator)): prepare_method_def(ir, module_name, cdef, mapper, node.node) elif isinstance(node.node, OverloadedFuncDef): diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 8cb24c5b47da..7c5958457886 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -44,24 +44,30 @@ ) from mypyc.ir.rtypes import ( RInstance, + RPrimitive, RTuple, RType, bool_rprimitive, c_int_rprimitive, dict_rprimitive, + int16_rprimitive, int32_rprimitive, int64_rprimitive, int_rprimitive, is_bool_rprimitive, is_dict_rprimitive, is_fixed_width_rtype, + is_float_rprimitive, + is_int16_rprimitive, is_int32_rprimitive, is_int64_rprimitive, is_int_rprimitive, is_list_rprimitive, + is_uint8_rprimitive, list_rprimitive, set_rprimitive, str_rprimitive, + uint8_rprimitive, ) from mypyc.irbuild.builder import IRBuilder from mypyc.irbuild.for_helpers import ( @@ -162,15 +168,20 @@ def translate_globals(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Va @specialize_function("builtins.complex") @specialize_function("mypy_extensions.i64") @specialize_function("mypy_extensions.i32") +@specialize_function("mypy_extensions.i16") +@specialize_function("mypy_extensions.u8") def translate_builtins_with_unary_dunder( builder: IRBuilder, expr: CallExpr, callee: RefExpr ) -> Value | None: - """Specialize calls on native classes that implement the associated dunder.""" + """Specialize calls on native classes that implement the associated dunder. + + E.g. i64(x) gets specialized to x.__int__() if x is a native instance. + """ if len(expr.args) == 1 and expr.arg_kinds == [ARG_POS] and isinstance(callee, NameExpr): arg = expr.args[0] arg_typ = builder.node_type(arg) shortname = callee.fullname.split(".")[1] - if shortname in ("i64", "i32"): + if shortname in ("i64", "i32", "i16", "u8"): method = "__int__" else: method = f"__{shortname}__" @@ -445,7 +456,7 @@ def translate_sum_call(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> V # handle 'start' argument, if given if len(expr.args) == 2: # ensure call to sum() was properly constructed - if not expr.arg_kinds[1] in (ARG_POS, ARG_NAMED): + if expr.arg_kinds[1] not in (ARG_POS, ARG_NAMED): return None start_expr = expr.args[1] else: @@ -679,9 +690,12 @@ def translate_i64(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value arg_type = builder.node_type(arg) if is_int64_rprimitive(arg_type): return builder.accept(arg) - elif is_int32_rprimitive(arg_type): + elif is_int32_rprimitive(arg_type) or is_int16_rprimitive(arg_type): val = builder.accept(arg) return builder.add(Extend(val, int64_rprimitive, signed=True, line=expr.line)) + elif is_uint8_rprimitive(arg_type): + val = builder.accept(arg) + return builder.add(Extend(val, int64_rprimitive, signed=False, line=expr.line)) elif is_int_rprimitive(arg_type) or is_bool_rprimitive(arg_type): val = builder.accept(arg) return builder.coerce(val, int64_rprimitive, expr.line) @@ -699,12 +713,78 @@ def translate_i32(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value elif is_int64_rprimitive(arg_type): val = builder.accept(arg) return builder.add(Truncate(val, int32_rprimitive, line=expr.line)) + elif is_int16_rprimitive(arg_type): + val = builder.accept(arg) + return builder.add(Extend(val, int32_rprimitive, signed=True, line=expr.line)) + elif is_uint8_rprimitive(arg_type): + val = builder.accept(arg) + return builder.add(Extend(val, int32_rprimitive, signed=False, line=expr.line)) elif is_int_rprimitive(arg_type) or is_bool_rprimitive(arg_type): val = builder.accept(arg) + val = truncate_literal(val, int32_rprimitive) return builder.coerce(val, int32_rprimitive, expr.line) return None +@specialize_function("mypy_extensions.i16") +def translate_i16(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None: + if len(expr.args) != 1 or expr.arg_kinds[0] != ARG_POS: + return None + arg = expr.args[0] + arg_type = builder.node_type(arg) + if is_int16_rprimitive(arg_type): + return builder.accept(arg) + elif is_int32_rprimitive(arg_type) or is_int64_rprimitive(arg_type): + val = builder.accept(arg) + return builder.add(Truncate(val, int16_rprimitive, line=expr.line)) + elif is_uint8_rprimitive(arg_type): + val = builder.accept(arg) + return builder.add(Extend(val, int16_rprimitive, signed=False, line=expr.line)) + elif is_int_rprimitive(arg_type) or is_bool_rprimitive(arg_type): + val = builder.accept(arg) + val = truncate_literal(val, int16_rprimitive) + return builder.coerce(val, int16_rprimitive, expr.line) + return None + + +@specialize_function("mypy_extensions.u8") +def translate_u8(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None: + if len(expr.args) != 1 or expr.arg_kinds[0] != ARG_POS: + return None + arg = expr.args[0] + arg_type = builder.node_type(arg) + if is_uint8_rprimitive(arg_type): + return builder.accept(arg) + elif ( + is_int16_rprimitive(arg_type) + or is_int32_rprimitive(arg_type) + or is_int64_rprimitive(arg_type) + ): + val = builder.accept(arg) + return builder.add(Truncate(val, uint8_rprimitive, line=expr.line)) + elif is_int_rprimitive(arg_type) or is_bool_rprimitive(arg_type): + val = builder.accept(arg) + val = truncate_literal(val, uint8_rprimitive) + return builder.coerce(val, uint8_rprimitive, expr.line) + return None + + +def truncate_literal(value: Value, rtype: RPrimitive) -> Value: + """If value is an integer literal value, truncate it to given native int rtype. + + For example, truncate 256 into 0 if rtype is u8. + """ + if not isinstance(value, Integer): + return value # Not a literal, nothing to do + x = value.numeric_value() + max_unsigned = (1 << (rtype.size * 8)) - 1 + x = x & max_unsigned + if rtype.is_signed and x >= (max_unsigned + 1) // 2: + # Adjust to make it a negative value + x -= max_unsigned + 1 + return Integer(x, rtype) + + @specialize_function("builtins.int") def translate_int(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None: if len(expr.args) != 1 or expr.arg_kinds[0] != ARG_POS: @@ -728,3 +808,15 @@ def translate_bool(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value arg = expr.args[0] src = builder.accept(arg) return builder.builder.bool_value(src) + + +@specialize_function("builtins.float") +def translate_float(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None: + if len(expr.args) != 1 or expr.arg_kinds[0] != ARG_POS: + return None + arg = expr.args[0] + arg_type = builder.node_type(arg) + if is_float_rprimitive(arg_type): + # No-op float conversion. + return builder.accept(arg) + return None diff --git a/mypyc/irbuild/statement.py b/mypyc/irbuild/statement.py index b9754ba1a147..2c17eb2bb14d 100644 --- a/mypyc/irbuild/statement.py +++ b/mypyc/irbuild/statement.py @@ -43,23 +43,30 @@ YieldFromExpr, ) from mypyc.ir.ops import ( + NAMESPACE_MODULE, NO_TRACEBACK_LINE_NO, Assign, BasicBlock, Branch, + InitStatic, Integer, LoadAddress, LoadErrorValue, + LoadLiteral, + LoadStatic, MethodCall, RaiseStandardError, Register, Return, TupleGet, + Unborrow, Unreachable, Value, ) from mypyc.ir.rtypes import ( RInstance, + RTuple, + c_pyssize_t_rprimitive, exc_rtuple, is_tagged, none_rprimitive, @@ -96,7 +103,8 @@ from mypyc.primitives.misc_ops import ( check_stop_op, coro_op, - import_from_op, + import_from_many_op, + import_many_op, send_op, type_op, yield_from_except_op, @@ -110,8 +118,13 @@ def transform_block(builder: IRBuilder, block: Block) -> None: if not block.is_unreachable: + builder.block_reachable_stack.append(True) for stmt in block.body: builder.accept(stmt) + if not builder.block_reachable_stack[-1]: + # The rest of the block is unreachable, so skip it + break + builder.block_reachable_stack.pop() # Raise a RuntimeError if we hit a non-empty unreachable block. # Don't complain about empty unreachable blocks, since mypy inserts # those after `if MYPY`. @@ -145,7 +158,7 @@ def transform_return_stmt(builder: IRBuilder, stmt: ReturnStmt) -> None: def transform_assignment_stmt(builder: IRBuilder, stmt: AssignmentStmt) -> None: lvalues = stmt.lvalues - assert len(lvalues) >= 1 + assert lvalues builder.disallow_class_assignments(lvalues, stmt.line) first_lvalue = lvalues[0] if stmt.type and isinstance(stmt.rvalue, TempNode): @@ -169,7 +182,7 @@ def transform_assignment_stmt(builder: IRBuilder, stmt: AssignmentStmt) -> None: temp = Register(rvalue_reg.type) builder.assign(temp, rvalue_reg, stmt.line) temps.append(temp) - for (left, temp) in zip(first_lvalue.items, temps): + for left, temp in zip(first_lvalue.items, temps): assignment_target = builder.get_assignment_target(left) builder.assign(assignment_target, temp, stmt.line) builder.flush_keep_alives() @@ -177,8 +190,29 @@ def transform_assignment_stmt(builder: IRBuilder, stmt: AssignmentStmt) -> None: line = stmt.rvalue.line rvalue_reg = builder.accept(stmt.rvalue) + if builder.non_function_scope() and stmt.is_final_def: builder.init_final_static(first_lvalue, rvalue_reg) + + # Special-case multiple assignments like 'x, y = expr' to reduce refcount ops. + if ( + isinstance(first_lvalue, (TupleExpr, ListExpr)) + and isinstance(rvalue_reg.type, RTuple) + and len(rvalue_reg.type.types) == len(first_lvalue.items) + and len(lvalues) == 1 + and all(is_simple_lvalue(item) for item in first_lvalue.items) + and any(t.is_refcounted for t in rvalue_reg.type.types) + ): + n = len(first_lvalue.items) + for i in range(n): + target = builder.get_assignment_target(first_lvalue.items[i]) + rvalue_item = builder.add(TupleGet(rvalue_reg, i, borrow=True)) + rvalue_item = builder.add(Unborrow(rvalue_item)) + builder.assign(target, rvalue_item, line) + builder.builder.keep_alive([rvalue_reg], steal=True) + builder.flush_keep_alives() + return + for lvalue in lvalues: target = builder.get_assignment_target(lvalue) builder.assign(target, rvalue_reg, line) @@ -214,35 +248,93 @@ def transform_operator_assignment_stmt(builder: IRBuilder, stmt: OperatorAssignm builder.flush_keep_alives() +def import_globals_id_and_name(module_id: str, as_name: str | None) -> tuple[str, str]: + """Compute names for updating the globals dict with the appropriate module. + + * For 'import foo.bar as baz' we add 'foo.bar' with the name 'baz' + * For 'import foo.bar' we add 'foo' with the name 'foo' + + Typically we then ignore these entries and access things directly + via the module static, but we will use the globals version for + modules that mypy couldn't find, since it doesn't analyze module + references from those properly.""" + if as_name: + globals_id = module_id + globals_name = as_name + else: + globals_id = globals_name = module_id.split(".")[0] + + return globals_id, globals_name + + def transform_import(builder: IRBuilder, node: Import) -> None: if node.is_mypy_only: return - globals = builder.load_globals_dict() - for node_id, as_name in node.ids: - builder.gen_import(node_id, node.line) - - # Update the globals dict with the appropriate module: - # * For 'import foo.bar as baz' we add 'foo.bar' with the name 'baz' - # * For 'import foo.bar' we add 'foo' with the name 'foo' - # Typically we then ignore these entries and access things directly - # via the module static, but we will use the globals version for modules - # that mypy couldn't find, since it doesn't analyze module references - # from those properly. - - # TODO: Don't add local imports to the global namespace - - # Miscompiling imports inside of functions, like below in import from. - if as_name: - name = as_name - base = node_id - else: - base = name = node_id.split(".")[0] - obj = builder.get_module(base, node.line) + # Imports (not from imports!) are processed in an odd way so they can be + # table-driven and compact. Here's how it works: + # + # Import nodes are divided in groups (in the prebuild visitor). Each group + # consists of consecutive Import nodes: + # + # import mod <| group #1 + # import mod2 | + # + # def foo() -> None: + # import mod3 <- group #2 (*) + # + # import mod4 <| group #3 + # import mod5 | + # + # Every time we encounter the first import of a group, build IR to call a + # helper function that will perform all of the group's imports in one go. + if not node.is_top_level: + # (*) Unless the import is within a function. In that case, prioritize + # speed over codesize when generating IR. + globals = builder.load_globals_dict() + for mod_id, as_name in node.ids: + builder.gen_import(mod_id, node.line) + globals_id, globals_name = import_globals_id_and_name(mod_id, as_name) + builder.gen_method_call( + globals, + "__setitem__", + [builder.load_str(globals_name), builder.get_module(globals_id, node.line)], + result_type=None, + line=node.line, + ) + return - builder.gen_method_call( - globals, "__setitem__", [builder.load_str(name), obj], result_type=None, line=node.line - ) + if node not in builder.module_import_groups: + return + + modules = [] + static_ptrs = [] + # To show the right line number on failure, we have to add the traceback + # entry within the helper function (which is admittedly ugly). To drive + # this, we need the line number corresponding to each module. + mod_lines = [] + for import_node in builder.module_import_groups[node]: + for mod_id, as_name in import_node.ids: + builder.imports[mod_id] = None + modules.append((mod_id, *import_globals_id_and_name(mod_id, as_name))) + mod_static = LoadStatic(object_rprimitive, mod_id, namespace=NAMESPACE_MODULE) + static_ptrs.append(builder.add(LoadAddress(object_pointer_rprimitive, mod_static))) + mod_lines.append(Integer(import_node.line, c_pyssize_t_rprimitive)) + + static_array_ptr = builder.builder.setup_rarray(object_pointer_rprimitive, static_ptrs) + import_line_ptr = builder.builder.setup_rarray(c_pyssize_t_rprimitive, mod_lines) + builder.call_c( + import_many_op, + [ + builder.add(LoadLiteral(tuple(modules), object_rprimitive)), + static_array_ptr, + builder.load_globals_dict(), + builder.load_str(builder.module_path), + builder.load_str(builder.fn_info.name), + import_line_ptr, + ], + NO_TRACEBACK_LINE_NO, + ) def transform_import_from(builder: IRBuilder, node: ImportFrom) -> None: @@ -258,29 +350,25 @@ def transform_import_from(builder: IRBuilder, node: ImportFrom) -> None: module_package = "" id = importlib.util.resolve_name("." * node.relative + node.id, module_package) - - globals = builder.load_globals_dict() - imported_names = [name for name, _ in node.names] - module = builder.gen_import_from(id, globals, imported_names, node.line) - - # Copy everything into our module's dict. + builder.imports[id] = None + + names = [name for name, _ in node.names] + as_names = [as_name or name for name, as_name in node.names] + names_literal = builder.add(LoadLiteral(tuple(names), object_rprimitive)) + if as_names == names: + # Reuse names tuple to reduce verbosity. + as_names_literal = names_literal + else: + as_names_literal = builder.add(LoadLiteral(tuple(as_names), object_rprimitive)) # Note that we miscompile import from inside of functions here, - # since that case *shouldn't* load it into the globals dict. + # since that case *shouldn't* load everything into the globals dict. # This probably doesn't matter much and the code runs basically right. - for name, maybe_as_name in node.names: - as_name = maybe_as_name or name - obj = builder.call_c( - import_from_op, - [module, builder.load_str(id), builder.load_str(name), builder.load_str(as_name)], - node.line, - ) - builder.gen_method_call( - globals, - "__setitem__", - [builder.load_str(as_name), obj], - result_type=None, - line=node.line, - ) + module = builder.call_c( + import_from_many_op, + [builder.load_str(id), names_literal, as_names_literal, builder.load_globals_dict()], + node.line, + ) + builder.add(InitStatic(module, id, namespace=NAMESPACE_MODULE)) def transform_import_all(builder: IRBuilder, node: ImportAll) -> None: diff --git a/mypyc/irbuild/visitor.py b/mypyc/irbuild/visitor.py index d8725ee04dc5..e7256f036e4c 100644 --- a/mypyc/irbuild/visitor.py +++ b/mypyc/irbuild/visitor.py @@ -70,6 +70,7 @@ TryStmt, TupleExpr, TypeAliasExpr, + TypeAliasStmt, TypeApplication, TypedDictExpr, TypeVarExpr, @@ -194,6 +195,7 @@ def visit_expression_stmt(self, stmt: ExpressionStmt) -> None: def visit_return_stmt(self, stmt: ReturnStmt) -> None: transform_return_stmt(self.builder, stmt) + self.builder.mark_block_unreachable() def visit_assignment_stmt(self, stmt: AssignmentStmt) -> None: transform_assignment_stmt(self.builder, stmt) @@ -212,12 +214,15 @@ def visit_for_stmt(self, stmt: ForStmt) -> None: def visit_break_stmt(self, stmt: BreakStmt) -> None: transform_break_stmt(self.builder, stmt) + self.builder.mark_block_unreachable() def visit_continue_stmt(self, stmt: ContinueStmt) -> None: transform_continue_stmt(self.builder, stmt) + self.builder.mark_block_unreachable() def visit_raise_stmt(self, stmt: RaiseStmt) -> None: transform_raise_stmt(self.builder, stmt) + self.builder.mark_block_unreachable() def visit_try_stmt(self, stmt: TryStmt) -> None: transform_try_stmt(self.builder, stmt) @@ -245,6 +250,9 @@ def visit_nonlocal_decl(self, stmt: NonlocalDecl) -> None: def visit_match_stmt(self, stmt: MatchStmt) -> None: transform_match_stmt(self.builder, stmt) + def visit_type_alias_stmt(self, stmt: TypeAliasStmt) -> None: + self.bail('The "type" statement is not yet supported by mypyc', stmt.line) + # Expressions def visit_name_expr(self, expr: NameExpr) -> Value: diff --git a/mypyc/lib-rt/CPy.h b/mypyc/lib-rt/CPy.h index 016a6d3ea9e0..1a03f049ecb0 100644 --- a/mypyc/lib-rt/CPy.h +++ b/mypyc/lib-rt/CPy.h @@ -41,7 +41,6 @@ typedef struct tuple_T3OOO { PyObject *f1; PyObject *f2; } tuple_T3OOO; -static tuple_T3OOO tuple_undefined_T3OOO = { NULL, NULL, NULL }; #endif // Our return tuple wrapper for dictionary iteration helper. @@ -52,7 +51,6 @@ typedef struct tuple_T3CIO { CPyTagged f1; // Last dict offset PyObject *f2; // Next dictionary key or value } tuple_T3CIO; -static tuple_T3CIO tuple_undefined_T3CIO = { 2, CPY_INT_TAG, NULL }; #endif // Same as above but for both key and value. @@ -64,7 +62,6 @@ typedef struct tuple_T4CIOO { PyObject *f2; // Next dictionary key PyObject *f3; // Next dictionary value } tuple_T4CIOO; -static tuple_T4CIOO tuple_undefined_T4CIOO = { 2, CPY_INT_TAG, NULL, NULL }; #endif @@ -147,9 +144,9 @@ CPyTagged CPyTagged_Lshift(CPyTagged left, CPyTagged right); bool CPyTagged_IsEq_(CPyTagged left, CPyTagged right); bool CPyTagged_IsLt_(CPyTagged left, CPyTagged right); PyObject *CPyTagged_Str(CPyTagged n); +CPyTagged CPyTagged_FromFloat(double f); PyObject *CPyLong_FromStrWithBase(PyObject *o, CPyTagged base); PyObject *CPyLong_FromStr(PyObject *o); -PyObject *CPyLong_FromFloat(PyObject *o); PyObject *CPyBool_Str(bool b); int64_t CPyLong_AsInt64(PyObject *o); int64_t CPyInt64_Divide(int64_t x, int64_t y); @@ -158,6 +155,13 @@ int32_t CPyLong_AsInt32(PyObject *o); int32_t CPyInt32_Divide(int32_t x, int32_t y); int32_t CPyInt32_Remainder(int32_t x, int32_t y); void CPyInt32_Overflow(void); +int16_t CPyLong_AsInt16(PyObject *o); +int16_t CPyInt16_Divide(int16_t x, int16_t y); +int16_t CPyInt16_Remainder(int16_t x, int16_t y); +void CPyInt16_Overflow(void); +uint8_t CPyLong_AsUInt8(PyObject *o); +void CPyUInt8_Overflow(void); +double CPyTagged_TrueDivide(CPyTagged x, CPyTagged y); static inline int CPyTagged_CheckLong(CPyTagged x) { return x & CPY_INT_TAG; @@ -283,6 +287,24 @@ static inline bool CPyTagged_IsLe(CPyTagged left, CPyTagged right) { } +// Float operations + + +double CPyFloat_FloorDivide(double x, double y); +double CPyFloat_Pow(double x, double y); +double CPyFloat_Sin(double x); +double CPyFloat_Cos(double x); +double CPyFloat_Tan(double x); +double CPyFloat_Sqrt(double x); +double CPyFloat_Exp(double x); +double CPyFloat_Log(double x); +CPyTagged CPyFloat_Floor(double x); +CPyTagged CPyFloat_Ceil(double x); +double CPyFloat_FromTagged(CPyTagged x); +bool CPyFloat_IsInf(double x); +bool CPyFloat_IsNaN(double x); + + // Generic operations (that work with arbitrary types) @@ -452,7 +474,6 @@ PyObject *CPyBytes_Join(PyObject *sep, PyObject *iter); int CPyBytes_Compare(PyObject *left, PyObject *right); - // Set operations @@ -523,13 +544,8 @@ void CPy_AttributeError(const char *filename, const char *funcname, const char * // Misc operations -#if PY_VERSION_HEX >= 0x03080000 #define CPy_TRASHCAN_BEGIN(op, dealloc) Py_TRASHCAN_BEGIN(op, dealloc) #define CPy_TRASHCAN_END(op) Py_TRASHCAN_END -#else -#define CPy_TRASHCAN_BEGIN(op, dealloc) Py_TRASHCAN_SAFE_BEGIN(op) -#define CPy_TRASHCAN_END(op) Py_TRASHCAN_SAFE_END(op) -#endif // Tweaked version of _PyArg_Parser in CPython typedef struct CPyArg_Parser { @@ -604,8 +620,10 @@ PyObject *CPy_Super(PyObject *builtins, PyObject *self); PyObject *CPy_CallReverseOpMethod(PyObject *left, PyObject *right, const char *op, _Py_Identifier *method); -PyObject *CPyImport_ImportFrom(PyObject *module, PyObject *package_name, - PyObject *import_name, PyObject *as_name); +bool CPyImport_ImportMany(PyObject *modules, CPyModule **statics[], PyObject *globals, + PyObject *tb_path, PyObject *tb_function, Py_ssize_t *tb_lines); +PyObject *CPyImport_ImportFromMany(PyObject *mod_id, PyObject *names, PyObject *as_names, + PyObject *globals); PyObject *CPySingledispatch_RegisterFunction(PyObject *singledispatch_func, PyObject *cls, PyObject *func); diff --git a/mypyc/lib-rt/bytes_ops.c b/mypyc/lib-rt/bytes_ops.c index bece7c13c957..4da62be11571 100644 --- a/mypyc/lib-rt/bytes_ops.c +++ b/mypyc/lib-rt/bytes_ops.c @@ -6,7 +6,7 @@ #include "CPy.h" // Returns -1 on error, 0 on inequality, 1 on equality. -// +// // Falls back to PyObject_RichCompareBool. int CPyBytes_Compare(PyObject *left, PyObject *right) { if (PyBytes_CheckExact(left) && PyBytes_CheckExact(right)) { diff --git a/mypyc/lib-rt/exc_ops.c b/mypyc/lib-rt/exc_ops.c index 219914bf3470..d8307ecf21f8 100644 --- a/mypyc/lib-rt/exc_ops.c +++ b/mypyc/lib-rt/exc_ops.c @@ -24,6 +24,12 @@ void CPy_Reraise(void) { } void CPyErr_SetObjectAndTraceback(PyObject *type, PyObject *value, PyObject *traceback) { + if (!PyType_Check(type) && value == Py_None) { + // The first argument must be an exception instance + value = type; + type = (PyObject *)Py_TYPE(value); + } + // Set the value and traceback of an error. Because calling // PyErr_Restore takes away a reference to each object passed in // as an argument, we manually increase the reference count of @@ -230,7 +236,11 @@ void CPy_AddTraceback(const char *filename, const char *funcname, int line, PyOb return; error: +#if CPY_3_12_FEATURES + _PyErr_ChainExceptions1(exc); +#else _PyErr_ChainExceptions(exc, val, tb); +#endif } CPy_NOINLINE diff --git a/mypyc/lib-rt/float_ops.c b/mypyc/lib-rt/float_ops.c new file mode 100644 index 000000000000..d8c6f25955fa --- /dev/null +++ b/mypyc/lib-rt/float_ops.c @@ -0,0 +1,192 @@ +// Float primitive operations +// +// These are registered in mypyc.primitives.float_ops. + +#include +#include "CPy.h" + + +static double CPy_DomainError(void) { + PyErr_SetString(PyExc_ValueError, "math domain error"); + return CPY_FLOAT_ERROR; +} + +static double CPy_MathRangeError(void) { + PyErr_SetString(PyExc_OverflowError, "math range error"); + return CPY_FLOAT_ERROR; +} + +double CPyFloat_FromTagged(CPyTagged x) { + if (CPyTagged_CheckShort(x)) { + return CPyTagged_ShortAsSsize_t(x); + } + double result = PyFloat_AsDouble(CPyTagged_LongAsObject(x)); + if (unlikely(result == -1.0) && PyErr_Occurred()) { + return CPY_FLOAT_ERROR; + } + return result; +} + +double CPyFloat_Sin(double x) { + double v = sin(x); + if (unlikely(isnan(v)) && !isnan(x)) { + return CPy_DomainError(); + } + return v; +} + +double CPyFloat_Cos(double x) { + double v = cos(x); + if (unlikely(isnan(v)) && !isnan(x)) { + return CPy_DomainError(); + } + return v; +} + +double CPyFloat_Tan(double x) { + if (unlikely(isinf(x))) { + return CPy_DomainError(); + } + return tan(x); +} + +double CPyFloat_Sqrt(double x) { + if (x < 0.0) { + return CPy_DomainError(); + } + return sqrt(x); +} + +double CPyFloat_Exp(double x) { + double v = exp(x); + if (unlikely(v == INFINITY) && x != INFINITY) { + return CPy_MathRangeError(); + } + return v; +} + +double CPyFloat_Log(double x) { + if (x <= 0.0) { + return CPy_DomainError(); + } + return log(x); +} + +CPyTagged CPyFloat_Floor(double x) { + double v = floor(x); + return CPyTagged_FromFloat(v); +} + +CPyTagged CPyFloat_Ceil(double x) { + double v = ceil(x); + return CPyTagged_FromFloat(v); +} + +bool CPyFloat_IsInf(double x) { + return isinf(x) != 0; +} + +bool CPyFloat_IsNaN(double x) { + return isnan(x) != 0; +} + +// From CPython 3.10.0, Objects/floatobject.c +static void +_float_div_mod(double vx, double wx, double *floordiv, double *mod) +{ + double div; + *mod = fmod(vx, wx); + /* fmod is typically exact, so vx-mod is *mathematically* an + exact multiple of wx. But this is fp arithmetic, and fp + vx - mod is an approximation; the result is that div may + not be an exact integral value after the division, although + it will always be very close to one. + */ + div = (vx - *mod) / wx; + if (*mod) { + /* ensure the remainder has the same sign as the denominator */ + if ((wx < 0) != (*mod < 0)) { + *mod += wx; + div -= 1.0; + } + } + else { + /* the remainder is zero, and in the presence of signed zeroes + fmod returns different results across platforms; ensure + it has the same sign as the denominator. */ + *mod = copysign(0.0, wx); + } + /* snap quotient to nearest integral value */ + if (div) { + *floordiv = floor(div); + if (div - *floordiv > 0.5) { + *floordiv += 1.0; + } + } + else { + /* div is zero - get the same sign as the true quotient */ + *floordiv = copysign(0.0, vx / wx); /* zero w/ sign of vx/wx */ + } +} + +double CPyFloat_FloorDivide(double x, double y) { + double mod, floordiv; + if (y == 0) { + PyErr_SetString(PyExc_ZeroDivisionError, "float floor division by zero"); + return CPY_FLOAT_ERROR; + } + _float_div_mod(x, y, &floordiv, &mod); + return floordiv; +} + +// Adapted from CPython 3.10.7 +double CPyFloat_Pow(double x, double y) { + if (!isfinite(x) || !isfinite(y)) { + if (isnan(x)) + return y == 0.0 ? 1.0 : x; /* NaN**0 = 1 */ + else if (isnan(y)) + return x == 1.0 ? 1.0 : y; /* 1**NaN = 1 */ + else if (isinf(x)) { + int odd_y = isfinite(y) && fmod(fabs(y), 2.0) == 1.0; + if (y > 0.0) + return odd_y ? x : fabs(x); + else if (y == 0.0) + return 1.0; + else /* y < 0. */ + return odd_y ? copysign(0.0, x) : 0.0; + } + else if (isinf(y)) { + if (fabs(x) == 1.0) + return 1.0; + else if (y > 0.0 && fabs(x) > 1.0) + return y; + else if (y < 0.0 && fabs(x) < 1.0) { + #if PY_VERSION_HEX < 0x030B0000 + if (x == 0.0) { /* 0**-inf: divide-by-zero */ + return CPy_DomainError(); + } + #endif + return -y; /* result is +inf */ + } else + return 0.0; + } + } + double r = pow(x, y); + if (!isfinite(r)) { + if (isnan(r)) { + return CPy_DomainError(); + } + /* + an infinite result here arises either from: + (A) (+/-0.)**negative (-> divide-by-zero) + (B) overflow of x**y with x and y finite + */ + else if (isinf(r)) { + if (x == 0.0) + return CPy_DomainError(); + else + return CPy_MathRangeError(); + } + } + return r; +} diff --git a/mypyc/lib-rt/int_ops.c b/mypyc/lib-rt/int_ops.c index 5ea2f65d5776..b57d88c6ac93 100644 --- a/mypyc/lib-rt/int_ops.c +++ b/mypyc/lib-rt/int_ops.c @@ -293,13 +293,14 @@ PyObject *CPyLong_FromStr(PyObject *o) { return CPyLong_FromStrWithBase(o, base); } -PyObject *CPyLong_FromFloat(PyObject *o) { - if (PyLong_Check(o)) { - CPy_INCREF(o); - return o; - } else { - return PyLong_FromDouble(PyFloat_AS_DOUBLE(o)); +CPyTagged CPyTagged_FromFloat(double f) { + if (f < ((double)CPY_TAGGED_MAX + 1.0) && f > (CPY_TAGGED_MIN - 1.0)) { + return (Py_ssize_t)f << 1; } + PyObject *o = PyLong_FromDouble(f); + if (o == NULL) + return CPY_INT_TAG; + return CPyTagged_StealFromObject(o); } PyObject *CPyBool_Str(bool b) { @@ -307,10 +308,10 @@ PyObject *CPyBool_Str(bool b) { } static void CPyLong_NormalizeUnsigned(PyLongObject *v) { - Py_ssize_t i = v->ob_base.ob_size; - while (i > 0 && v->ob_digit[i - 1] == 0) + Py_ssize_t i = CPY_LONG_SIZE_UNSIGNED(v); + while (i > 0 && CPY_LONG_DIGIT(v, i - 1) == 0) i--; - v->ob_base.ob_size = i; + CPyLong_SetUnsignedSize(v, i); } // Bitwise op '&', '|' or '^' using the generic (slow) API @@ -360,8 +361,8 @@ static digit *GetIntDigits(CPyTagged n, Py_ssize_t *size, digit *buf) { return buf; } else { PyLongObject *obj = (PyLongObject *)CPyTagged_LongAsObject(n); - *size = obj->ob_base.ob_size; - return obj->ob_digit; + *size = CPY_LONG_SIZE_SIGNED(obj); + return &CPY_LONG_DIGIT(obj, 0); } } @@ -398,20 +399,20 @@ static CPyTagged BitwiseLongOp(CPyTagged a, CPyTagged b, char op) { Py_ssize_t i; if (op == '&') { for (i = 0; i < asize; i++) { - r->ob_digit[i] = adigits[i] & bdigits[i]; + CPY_LONG_DIGIT(r, i) = adigits[i] & bdigits[i]; } } else { if (op == '|') { for (i = 0; i < asize; i++) { - r->ob_digit[i] = adigits[i] | bdigits[i]; + CPY_LONG_DIGIT(r, i) = adigits[i] | bdigits[i]; } } else { for (i = 0; i < asize; i++) { - r->ob_digit[i] = adigits[i] ^ bdigits[i]; + CPY_LONG_DIGIT(r, i) = adigits[i] ^ bdigits[i]; } } for (; i < bsize; i++) { - r->ob_digit[i] = bdigits[i]; + CPY_LONG_DIGIT(r, i) = bdigits[i]; } } CPyLong_NormalizeUnsigned(r); @@ -520,7 +521,7 @@ int64_t CPyLong_AsInt64(PyObject *o) { Py_ssize_t size = Py_SIZE(lobj); if (likely(size == 1)) { // Fast path - return lobj->ob_digit[0]; + return CPY_LONG_DIGIT(lobj, 0); } else if (likely(size == 0)) { return 0; } @@ -575,14 +576,25 @@ int64_t CPyInt64_Remainder(int64_t x, int64_t y) { int32_t CPyLong_AsInt32(PyObject *o) { if (likely(PyLong_Check(o))) { + #if CPY_3_12_FEATURES + PyLongObject *lobj = (PyLongObject *)o; + size_t tag = CPY_LONG_TAG(lobj); + if (likely(tag == (1 << CPY_NON_SIZE_BITS))) { + // Fast path + return CPY_LONG_DIGIT(lobj, 0); + } else if (likely(tag == CPY_SIGN_ZERO)) { + return 0; + } + #else PyLongObject *lobj = (PyLongObject *)o; Py_ssize_t size = lobj->ob_base.ob_size; if (likely(size == 1)) { // Fast path - return lobj->ob_digit[0]; + return CPY_LONG_DIGIT(lobj, 0); } else if (likely(size == 0)) { return 0; } + #endif } // Slow path int overflow; @@ -639,3 +651,153 @@ int32_t CPyInt32_Remainder(int32_t x, int32_t y) { void CPyInt32_Overflow() { PyErr_SetString(PyExc_OverflowError, "int too large to convert to i32"); } + +int16_t CPyLong_AsInt16(PyObject *o) { + if (likely(PyLong_Check(o))) { + #if CPY_3_12_FEATURES + PyLongObject *lobj = (PyLongObject *)o; + size_t tag = CPY_LONG_TAG(lobj); + if (likely(tag == (1 << CPY_NON_SIZE_BITS))) { + // Fast path + digit x = CPY_LONG_DIGIT(lobj, 0); + if (x < 0x8000) + return x; + } else if (likely(tag == CPY_SIGN_ZERO)) { + return 0; + } + #else + PyLongObject *lobj = (PyLongObject *)o; + Py_ssize_t size = lobj->ob_base.ob_size; + if (likely(size == 1)) { + // Fast path + digit x = lobj->ob_digit[0]; + if (x < 0x8000) + return x; + } else if (likely(size == 0)) { + return 0; + } + #endif + } + // Slow path + int overflow; + long result = PyLong_AsLongAndOverflow(o, &overflow); + if (result > 0x7fff || result < -0x8000) { + overflow = 1; + result = -1; + } + if (result == -1) { + if (PyErr_Occurred()) { + return CPY_LL_INT_ERROR; + } else if (overflow) { + PyErr_SetString(PyExc_OverflowError, "int too large to convert to i16"); + return CPY_LL_INT_ERROR; + } + } + return result; +} + +int16_t CPyInt16_Divide(int16_t x, int16_t y) { + if (y == 0) { + PyErr_SetString(PyExc_ZeroDivisionError, "integer division or modulo by zero"); + return CPY_LL_INT_ERROR; + } + if (y == -1 && x == INT16_MIN) { + PyErr_SetString(PyExc_OverflowError, "integer division overflow"); + return CPY_LL_INT_ERROR; + } + int16_t d = x / y; + // Adjust for Python semantics + if (((x < 0) != (y < 0)) && d * y != x) { + d--; + } + return d; +} + +int16_t CPyInt16_Remainder(int16_t x, int16_t y) { + if (y == 0) { + PyErr_SetString(PyExc_ZeroDivisionError, "integer division or modulo by zero"); + return CPY_LL_INT_ERROR; + } + // Edge case: avoid core dump + if (y == -1 && x == INT16_MIN) { + return 0; + } + int16_t d = x % y; + // Adjust for Python semantics + if (((x < 0) != (y < 0)) && d != 0) { + d += y; + } + return d; +} + +void CPyInt16_Overflow() { + PyErr_SetString(PyExc_OverflowError, "int too large to convert to i16"); +} + + +uint8_t CPyLong_AsUInt8(PyObject *o) { + if (likely(PyLong_Check(o))) { + #if CPY_3_12_FEATURES + PyLongObject *lobj = (PyLongObject *)o; + size_t tag = CPY_LONG_TAG(lobj); + if (likely(tag == (1 << CPY_NON_SIZE_BITS))) { + // Fast path + digit x = CPY_LONG_DIGIT(lobj, 0); + if (x < 256) + return x; + } else if (likely(tag == CPY_SIGN_ZERO)) { + return 0; + } + #else + PyLongObject *lobj = (PyLongObject *)o; + Py_ssize_t size = lobj->ob_base.ob_size; + if (likely(size == 1)) { + // Fast path + digit x = lobj->ob_digit[0]; + if (x < 256) + return x; + } else if (likely(size == 0)) { + return 0; + } + #endif + } + // Slow path + int overflow; + long result = PyLong_AsLongAndOverflow(o, &overflow); + if (result < 0 || result >= 256) { + overflow = 1; + result = -1; + } + if (result == -1) { + if (PyErr_Occurred()) { + return CPY_LL_UINT_ERROR; + } else if (overflow) { + PyErr_SetString(PyExc_OverflowError, "int too large or small to convert to u8"); + return CPY_LL_UINT_ERROR; + } + } + return result; +} + +void CPyUInt8_Overflow() { + PyErr_SetString(PyExc_OverflowError, "int too large or small to convert to u8"); +} + +double CPyTagged_TrueDivide(CPyTagged x, CPyTagged y) { + if (unlikely(y == 0)) { + PyErr_SetString(PyExc_ZeroDivisionError, "division by zero"); + return CPY_FLOAT_ERROR; + } + if (likely(!CPyTagged_CheckLong(x) && !CPyTagged_CheckLong(y))) { + return (double)((Py_ssize_t)x >> 1) / (double)((Py_ssize_t)y >> 1); + } else { + PyObject *xo = CPyTagged_AsObject(x); + PyObject *yo = CPyTagged_AsObject(y); + PyObject *result = PyNumber_TrueDivide(xo, yo); + if (result == NULL) { + return CPY_FLOAT_ERROR; + } + return PyFloat_AsDouble(result); + } + return 1.0; +} diff --git a/mypyc/lib-rt/misc_ops.c b/mypyc/lib-rt/misc_ops.c index 5fda78704bbc..f28eeb57e646 100644 --- a/mypyc/lib-rt/misc_ops.c +++ b/mypyc/lib-rt/misc_ops.c @@ -177,42 +177,6 @@ PyObject *CPyType_FromTemplate(PyObject *template, if (!name) goto error; - // If there is a metaclass other than type, we would like to call - // its __new__ function. Unfortunately there doesn't seem to be a - // good way to mix a C extension class and creating it via a - // metaclass. We need to do it anyways, though, in order to - // support subclassing Generic[T] prior to Python 3.7. - // - // We solve this with a kind of atrocious hack: create a parallel - // class using the metaclass, determine the bases of the real - // class by pulling them out of the parallel class, creating the - // real class, and then merging its dict back into the original - // class. There are lots of cases where this won't really work, - // but for the case of GenericMeta setting a bunch of properties - // on the class we should be fine. - if (metaclass != &PyType_Type) { - assert(bases && "non-type metaclasses require non-NULL bases"); - - PyObject *ns = PyDict_New(); - if (!ns) - goto error; - - if (bases != orig_bases) { - if (PyDict_SetItemString(ns, "__orig_bases__", orig_bases) < 0) - goto error; - } - - dummy_class = (PyTypeObject *)PyObject_CallFunctionObjArgs( - (PyObject *)metaclass, name, bases, ns, NULL); - Py_DECREF(ns); - if (!dummy_class) - goto error; - - Py_DECREF(bases); - bases = dummy_class->tp_bases; - Py_INCREF(bases); - } - // Allocate the type and then copy the main stuff in. t = (PyHeapTypeObject*)PyType_GenericAlloc(&PyType_Type, 0); if (!t) @@ -669,9 +633,62 @@ CPy_Super(PyObject *builtins, PyObject *self) { return result; } +static bool import_single(PyObject *mod_id, PyObject **mod_static, + PyObject *globals_id, PyObject *globals_name, PyObject *globals) { + if (*mod_static == Py_None) { + CPyModule *mod = PyImport_Import(mod_id); + if (mod == NULL) { + return false; + } + *mod_static = mod; + } + + PyObject *mod_dict = PyImport_GetModuleDict(); + CPyModule *globals_mod = CPyDict_GetItem(mod_dict, globals_id); + if (globals_mod == NULL) { + return false; + } + int ret = CPyDict_SetItem(globals, globals_name, globals_mod); + Py_DECREF(globals_mod); + if (ret < 0) { + return false; + } + + return true; +} + +// Table-driven import helper. See transform_import() in irbuild for the details. +bool CPyImport_ImportMany(PyObject *modules, CPyModule **statics[], PyObject *globals, + PyObject *tb_path, PyObject *tb_function, Py_ssize_t *tb_lines) { + for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(modules); i++) { + PyObject *module = PyTuple_GET_ITEM(modules, i); + PyObject *mod_id = PyTuple_GET_ITEM(module, 0); + PyObject *globals_id = PyTuple_GET_ITEM(module, 1); + PyObject *globals_name = PyTuple_GET_ITEM(module, 2); + + if (!import_single(mod_id, statics[i], globals_id, globals_name, globals)) { + assert(PyErr_Occurred() && "error indicator should be set on bad import!"); + PyObject *typ, *val, *tb; + PyErr_Fetch(&typ, &val, &tb); + const char *path = PyUnicode_AsUTF8(tb_path); + if (path == NULL) { + path = ""; + } + const char *function = PyUnicode_AsUTF8(tb_function); + if (function == NULL) { + function = ""; + } + PyErr_Restore(typ, val, tb); + CPy_AddTraceback(path, function, tb_lines[i], globals); + return false; + } + } + return true; +} + // This helper function is a simplification of cpython/ceval.c/import_from() -PyObject *CPyImport_ImportFrom(PyObject *module, PyObject *package_name, - PyObject *import_name, PyObject *as_name) { +static PyObject *CPyImport_ImportFrom(PyObject *module, PyObject *package_name, + PyObject *import_name, PyObject *as_name) { // check if the imported module has an attribute by that name PyObject *x = PyObject_GetAttr(module, import_name); if (x == NULL) { @@ -702,6 +719,31 @@ PyObject *CPyImport_ImportFrom(PyObject *module, PyObject *package_name, return NULL; } +PyObject *CPyImport_ImportFromMany(PyObject *mod_id, PyObject *names, PyObject *as_names, + PyObject *globals) { + PyObject *mod = PyImport_ImportModuleLevelObject(mod_id, globals, 0, names, 0); + if (mod == NULL) { + return NULL; + } + + for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(names); i++) { + PyObject *name = PyTuple_GET_ITEM(names, i); + PyObject *as_name = PyTuple_GET_ITEM(as_names, i); + PyObject *obj = CPyImport_ImportFrom(mod, mod_id, name, as_name); + if (obj == NULL) { + Py_DECREF(mod); + return NULL; + } + int ret = CPyDict_SetItem(globals, as_name, obj); + Py_DECREF(obj); + if (ret < 0) { + Py_DECREF(mod); + return NULL; + } + } + return mod; +} + // From CPython static PyObject * CPy_BinopTypeError(PyObject *left, PyObject *right, const char *op) { diff --git a/mypyc/lib-rt/mypyc_util.h b/mypyc/lib-rt/mypyc_util.h index 0fae239cbb9e..3c888a581a33 100644 --- a/mypyc/lib-rt/mypyc_util.h +++ b/mypyc/lib-rt/mypyc_util.h @@ -53,9 +53,15 @@ typedef PyObject CPyModule; // Tag bit used for long integers #define CPY_INT_TAG 1 -// Error value for fixed-width (low-level) integers +// Error value for signed fixed-width (low-level) integers #define CPY_LL_INT_ERROR -113 +// Error value for unsigned fixed-width (low-level) integers +#define CPY_LL_UINT_ERROR 239 + +// Error value for floats +#define CPY_FLOAT_ERROR -113.0 + typedef void (*CPyVTableItem)(void); static inline CPyTagged CPyTagged_ShortFromInt(int x) { @@ -66,4 +72,47 @@ static inline CPyTagged CPyTagged_ShortFromSsize_t(Py_ssize_t x) { return x << 1; } +// Are we targeting Python 3.12 or newer? +#define CPY_3_12_FEATURES (PY_VERSION_HEX >= 0x030c0000) + +#if CPY_3_12_FEATURES + +// Same as macros in CPython internal/pycore_long.h, but with a CPY_ prefix +#define CPY_NON_SIZE_BITS 3 +#define CPY_SIGN_ZERO 1 +#define CPY_SIGN_NEGATIVE 2 +#define CPY_SIGN_MASK 3 + +#define CPY_LONG_DIGIT(o, n) ((o)->long_value.ob_digit[n]) + +// Only available on Python 3.12 and later +#define CPY_LONG_TAG(o) ((o)->long_value.lv_tag) +#define CPY_LONG_IS_NEGATIVE(o) (((o)->long_value.lv_tag & CPY_SIGN_MASK) == CPY_SIGN_NEGATIVE) +// Only available on Python 3.12 and later +#define CPY_LONG_SIZE(o) ((o)->long_value.lv_tag >> CPY_NON_SIZE_BITS) +// Number of digits; negative for negative ints +#define CPY_LONG_SIZE_SIGNED(o) (CPY_LONG_IS_NEGATIVE(o) ? -CPY_LONG_SIZE(o) : CPY_LONG_SIZE(o)) +// Number of digits, assuming int is non-negative +#define CPY_LONG_SIZE_UNSIGNED(o) CPY_LONG_SIZE(o) + +static inline void CPyLong_SetUnsignedSize(PyLongObject *o, Py_ssize_t n) { + if (n == 0) + o->long_value.lv_tag = CPY_SIGN_ZERO; + else + o->long_value.lv_tag = n << CPY_NON_SIZE_BITS; +} + +#else + +#define CPY_LONG_DIGIT(o, n) ((o)->ob_digit[n]) +#define CPY_LONG_IS_NEGATIVE(o) (((o)->ob_base.ob_size < 0) +#define CPY_LONG_SIZE_SIGNED(o) ((o)->ob_base.ob_size) +#define CPY_LONG_SIZE_UNSIGNED(o) ((o)->ob_base.ob_size) + +static inline void CPyLong_SetUnsignedSize(PyLongObject *o, Py_ssize_t n) { + o->ob_base.ob_size = n; +} + +#endif + #endif diff --git a/mypyc/lib-rt/pythonsupport.h b/mypyc/lib-rt/pythonsupport.h index 8a1159a98853..f7d501f44a27 100644 --- a/mypyc/lib-rt/pythonsupport.h +++ b/mypyc/lib-rt/pythonsupport.h @@ -13,6 +13,10 @@ #include #include "mypyc_util.h" +#if CPY_3_12_FEATURES +#include "internal/pycore_frame.h" +#endif + #ifdef __cplusplus extern "C" { #endif @@ -125,6 +129,65 @@ init_subclass(PyTypeObject *type, PyObject *kwds) return 0; } +#if CPY_3_12_FEATURES + +static inline Py_ssize_t +CPyLong_AsSsize_tAndOverflow(PyObject *vv, int *overflow) +{ + /* This version by Tim Peters */ + PyLongObject *v = (PyLongObject *)vv; + size_t x, prev; + Py_ssize_t res; + Py_ssize_t i; + int sign; + + *overflow = 0; + + res = -1; + i = CPY_LONG_TAG(v); + + // TODO: Combine zero and non-zero cases helow? + if (likely(i == (1 << CPY_NON_SIZE_BITS))) { + res = CPY_LONG_DIGIT(v, 0); + } else if (likely(i == CPY_SIGN_ZERO)) { + res = 0; + } else if (i == ((1 << CPY_NON_SIZE_BITS) | CPY_SIGN_NEGATIVE)) { + res = -(sdigit)CPY_LONG_DIGIT(v, 0); + } else { + sign = 1; + x = 0; + if (i & CPY_SIGN_NEGATIVE) { + sign = -1; + } + i >>= CPY_NON_SIZE_BITS; + while (--i >= 0) { + prev = x; + x = (x << PyLong_SHIFT) + CPY_LONG_DIGIT(v, i); + if ((x >> PyLong_SHIFT) != prev) { + *overflow = sign; + goto exit; + } + } + /* Haven't lost any bits, but casting to long requires extra + * care (see comment above). + */ + if (x <= (size_t)CPY_TAGGED_MAX) { + res = (Py_ssize_t)x * sign; + } + else if (sign < 0 && x == CPY_TAGGED_ABS_MIN) { + res = CPY_TAGGED_MIN; + } + else { + *overflow = sign; + /* res is already set to -1 */ + } + } + exit: + return res; +} + +#else + // Adapted from longobject.c in Python 3.7.0 /* This function adapted from PyLong_AsLongLongAndOverflow, but with @@ -152,11 +215,11 @@ CPyLong_AsSsize_tAndOverflow(PyObject *vv, int *overflow) i = Py_SIZE(v); if (likely(i == 1)) { - res = v->ob_digit[0]; + res = CPY_LONG_DIGIT(v, 0); } else if (likely(i == 0)) { res = 0; } else if (i == -1) { - res = -(sdigit)v->ob_digit[0]; + res = -(sdigit)CPY_LONG_DIGIT(v, 0); } else { sign = 1; x = 0; @@ -166,7 +229,7 @@ CPyLong_AsSsize_tAndOverflow(PyObject *vv, int *overflow) } while (--i >= 0) { prev = x; - x = (x << PyLong_SHIFT) + v->ob_digit[i]; + x = (x << PyLong_SHIFT) + CPY_LONG_DIGIT(v, i); if ((x >> PyLong_SHIFT) != prev) { *overflow = sign; goto exit; @@ -190,6 +253,8 @@ CPyLong_AsSsize_tAndOverflow(PyObject *vv, int *overflow) return res; } +#endif + // Adapted from listobject.c in Python 3.7.0 static int list_resize(PyListObject *self, Py_ssize_t newsize) @@ -289,21 +354,6 @@ list_count(PyListObject *self, PyObject *value) return CPyTagged_ShortFromSsize_t(count); } -#if PY_VERSION_HEX < 0x03080000 -static PyObject * -_PyDict_GetItemStringWithError(PyObject *v, const char *key) -{ - PyObject *kv, *rv; - kv = PyUnicode_FromString(key); - if (kv == NULL) { - return NULL; - } - rv = PyDict_GetItemWithError(v, kv); - Py_DECREF(kv); - return rv; -} -#endif - #define CPyUnicode_EqualToASCIIString(x, y) _PyUnicode_EqualToASCIIString(x, y) // Adapted from genobject.c in Python 3.7.2 @@ -389,6 +439,31 @@ _CPyObject_HasAttrId(PyObject *v, _Py_Identifier *name) { _PyObject_CallMethodIdObjArgs((self), (name), (arg), NULL) #endif +#if CPY_3_12_FEATURES + +// These are copied from genobject.c in Python 3.12 + +/* Returns a borrowed reference */ +static inline PyCodeObject * +_PyGen_GetCode(PyGenObject *gen) { + _PyInterpreterFrame *frame = (_PyInterpreterFrame *)(gen->gi_iframe); + return frame->f_code; +} + +static int +gen_is_coroutine(PyObject *o) +{ + if (PyGen_CheckExact(o)) { + PyCodeObject *code = _PyGen_GetCode((PyGenObject*)o); + if (code->co_flags & CO_ITERABLE_COROUTINE) { + return 1; + } + } + return 0; +} + +#else + // Copied from genobject.c in Python 3.10 static int gen_is_coroutine(PyObject *o) @@ -402,6 +477,8 @@ gen_is_coroutine(PyObject *o) return 0; } +#endif + /* * This helper function returns an awaitable for `o`: * - `o` if `o` is a coroutine-object; diff --git a/mypyc/lib-rt/setup.py b/mypyc/lib-rt/setup.py index e04d7041ad72..ef81b794c9bd 100644 --- a/mypyc/lib-rt/setup.py +++ b/mypyc/lib-rt/setup.py @@ -5,29 +5,66 @@ from __future__ import annotations +import os +import subprocess import sys +from distutils.command.build_ext import build_ext from distutils.core import Extension, setup +from typing import Any +kwargs: dict[str, Any] if sys.platform == "darwin": kwargs = {"language": "c++"} compile_args = [] else: - kwargs = {} # type: ignore + kwargs = {} compile_args = ["--std=c++11"] + +class build_ext_custom(build_ext): + def get_library_names(self): + return ["gtest"] + + def run(self): + gtest_dir = os.path.abspath( + os.path.join(os.path.dirname(__file__), "..", "external", "googletest") + ) + + os.makedirs(self.build_temp, exist_ok=True) + + # Build Google Test, the C++ framework we use for testing C code. + # The source code for Google Test is copied to this repository. + subprocess.check_call( + ["make", "-f", os.path.join(gtest_dir, "make", "Makefile"), f"GTEST_DIR={gtest_dir}"], + cwd=self.build_temp, + ) + + self.library_dirs = [self.build_temp] + + return build_ext.run(self) + + setup( name="test_capi", version="0.1", ext_modules=[ Extension( "test_capi", - ["test_capi.cc", "init.c", "int_ops.c", "list_ops.c", "exc_ops.c", "generic_ops.c"], + [ + "test_capi.cc", + "init.c", + "int_ops.c", + "float_ops.c", + "list_ops.c", + "exc_ops.c", + "generic_ops.c", + ], depends=["CPy.h", "mypyc_util.h", "pythonsupport.h"], extra_compile_args=["-Wno-unused-function", "-Wno-sign-compare"] + compile_args, - library_dirs=["../external/googletest/make"], libraries=["gtest"], include_dirs=["../external/googletest", "../external/googletest/include"], **kwargs, ) ], + cmdclass={"build_ext": build_ext_custom}, ) diff --git a/mypyc/lower/__init__.py b/mypyc/lower/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/mypyc/lower/int_ops.py b/mypyc/lower/int_ops.py new file mode 100644 index 000000000000..adfb4c21e2de --- /dev/null +++ b/mypyc/lower/int_ops.py @@ -0,0 +1,113 @@ +"""Convert tagged int primitive ops to lower-level ops.""" + +from __future__ import annotations + +from typing import NamedTuple + +from mypyc.ir.ops import Assign, BasicBlock, Branch, ComparisonOp, Register, Value +from mypyc.ir.rtypes import bool_rprimitive, is_short_int_rprimitive +from mypyc.irbuild.ll_builder import LowLevelIRBuilder +from mypyc.lower.registry import lower_primitive_op +from mypyc.primitives.int_ops import int_equal_, int_less_than_ +from mypyc.primitives.registry import CFunctionDescription + + +# Description for building int comparison ops +# +# Fields: +# binary_op_variant: identify which IntOp to use when operands are short integers +# c_func_description: the C function to call when operands are tagged integers +# c_func_negated: whether to negate the C function call's result +# c_func_swap_operands: whether to swap lhs and rhs when call the function +class IntComparisonOpDescription(NamedTuple): + binary_op_variant: int + c_func_description: CFunctionDescription + c_func_negated: bool + c_func_swap_operands: bool + + +# Provide mapping from textual op to short int's op variant and boxed int's description. +# Note that these are not complete implementations and require extra IR. +int_comparison_op_mapping: dict[str, IntComparisonOpDescription] = { + "==": IntComparisonOpDescription(ComparisonOp.EQ, int_equal_, False, False), + "!=": IntComparisonOpDescription(ComparisonOp.NEQ, int_equal_, True, False), + "<": IntComparisonOpDescription(ComparisonOp.SLT, int_less_than_, False, False), + "<=": IntComparisonOpDescription(ComparisonOp.SLE, int_less_than_, True, True), + ">": IntComparisonOpDescription(ComparisonOp.SGT, int_less_than_, False, True), + ">=": IntComparisonOpDescription(ComparisonOp.SGE, int_less_than_, True, False), +} + + +def compare_tagged(self: LowLevelIRBuilder, lhs: Value, rhs: Value, op: str, line: int) -> Value: + """Compare two tagged integers using given operator (value context).""" + # generate fast binary logic ops on short ints + if (is_short_int_rprimitive(lhs.type) or is_short_int_rprimitive(rhs.type)) and op in ( + "==", + "!=", + ): + quick = True + else: + quick = is_short_int_rprimitive(lhs.type) and is_short_int_rprimitive(rhs.type) + if quick: + return self.comparison_op(lhs, rhs, int_comparison_op_mapping[op][0], line) + op_type, c_func_desc, negate_result, swap_op = int_comparison_op_mapping[op] + result = Register(bool_rprimitive) + short_int_block, int_block, out = BasicBlock(), BasicBlock(), BasicBlock() + check_lhs = self.check_tagged_short_int(lhs, line, negated=True) + if op in ("==", "!="): + self.add(Branch(check_lhs, int_block, short_int_block, Branch.BOOL)) + else: + # for non-equality logical ops (less/greater than, etc.), need to check both sides + short_lhs = BasicBlock() + self.add(Branch(check_lhs, int_block, short_lhs, Branch.BOOL)) + self.activate_block(short_lhs) + check_rhs = self.check_tagged_short_int(rhs, line, negated=True) + self.add(Branch(check_rhs, int_block, short_int_block, Branch.BOOL)) + self.activate_block(int_block) + if swap_op: + args = [rhs, lhs] + else: + args = [lhs, rhs] + call = self.call_c(c_func_desc, args, line) + if negate_result: + # TODO: introduce UnaryIntOp? + call_result = self.unary_op(call, "not", line) + else: + call_result = call + self.add(Assign(result, call_result, line)) + self.goto(out) + self.activate_block(short_int_block) + eq = self.comparison_op(lhs, rhs, op_type, line) + self.add(Assign(result, eq, line)) + self.goto_and_activate(out) + return result + + +@lower_primitive_op("int_eq") +def lower_int_eq(builder: LowLevelIRBuilder, args: list[Value], line: int) -> Value: + return compare_tagged(builder, args[0], args[1], "==", line) + + +@lower_primitive_op("int_ne") +def lower_int_ne(builder: LowLevelIRBuilder, args: list[Value], line: int) -> Value: + return compare_tagged(builder, args[0], args[1], "!=", line) + + +@lower_primitive_op("int_lt") +def lower_int_lt(builder: LowLevelIRBuilder, args: list[Value], line: int) -> Value: + return compare_tagged(builder, args[0], args[1], "<", line) + + +@lower_primitive_op("int_le") +def lower_int_le(builder: LowLevelIRBuilder, args: list[Value], line: int) -> Value: + return compare_tagged(builder, args[0], args[1], "<=", line) + + +@lower_primitive_op("int_gt") +def lower_int_gt(builder: LowLevelIRBuilder, args: list[Value], line: int) -> Value: + return compare_tagged(builder, args[0], args[1], ">", line) + + +@lower_primitive_op("int_ge") +def lower_int_ge(builder: LowLevelIRBuilder, args: list[Value], line: int) -> Value: + return compare_tagged(builder, args[0], args[1], ">=", line) diff --git a/mypyc/lower/list_ops.py b/mypyc/lower/list_ops.py new file mode 100644 index 000000000000..0d2e3e7169d8 --- /dev/null +++ b/mypyc/lower/list_ops.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +from mypyc.common import PLATFORM_SIZE +from mypyc.ir.ops import GetElementPtr, Integer, IntOp, LoadMem, SetMem, Value +from mypyc.ir.rtypes import ( + PyListObject, + c_pyssize_t_rprimitive, + object_rprimitive, + pointer_rprimitive, +) +from mypyc.irbuild.ll_builder import LowLevelIRBuilder +from mypyc.lower.registry import lower_primitive_op + + +@lower_primitive_op("buf_init_item") +def buf_init_item(builder: LowLevelIRBuilder, args: list[Value], line: int) -> Value: + """Initialize an item in a buffer of "PyObject *" values at given index. + + This can be used to initialize the data buffer of a freshly allocated list + object. + """ + base = args[0] + index_value = args[1] + value = args[2] + assert isinstance(index_value, Integer) + index = index_value.numeric_value() + if index == 0: + ptr = base + else: + ptr = builder.add( + IntOp( + pointer_rprimitive, + base, + Integer(index * PLATFORM_SIZE, c_pyssize_t_rprimitive), + IntOp.ADD, + line, + ) + ) + return builder.add(SetMem(object_rprimitive, ptr, value, line)) + + +@lower_primitive_op("list_items") +def list_items(builder: LowLevelIRBuilder, args: list[Value], line: int) -> Value: + ob_item_ptr = builder.add(GetElementPtr(args[0], PyListObject, "ob_item", line)) + return builder.add(LoadMem(pointer_rprimitive, ob_item_ptr, line)) diff --git a/mypyc/lower/misc_ops.py b/mypyc/lower/misc_ops.py new file mode 100644 index 000000000000..1effcd4f42ac --- /dev/null +++ b/mypyc/lower/misc_ops.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +from mypyc.ir.ops import GetElementPtr, LoadMem, Value +from mypyc.ir.rtypes import PyVarObject, c_pyssize_t_rprimitive +from mypyc.irbuild.ll_builder import LowLevelIRBuilder +from mypyc.lower.registry import lower_primitive_op + + +@lower_primitive_op("var_object_size") +def var_object_size(builder: LowLevelIRBuilder, args: list[Value], line: int) -> Value: + elem_address = builder.add(GetElementPtr(args[0], PyVarObject, "ob_size")) + return builder.add(LoadMem(c_pyssize_t_rprimitive, elem_address)) diff --git a/mypyc/lower/registry.py b/mypyc/lower/registry.py new file mode 100644 index 000000000000..084d57df4608 --- /dev/null +++ b/mypyc/lower/registry.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from typing import Callable, Final, List + +from mypyc.ir.ops import Value +from mypyc.irbuild.ll_builder import LowLevelIRBuilder + +LowerFunc = Callable[[LowLevelIRBuilder, List[Value], int], Value] + + +lowering_registry: Final[dict[str, LowerFunc]] = {} + + +def lower_primitive_op(name: str) -> Callable[[LowerFunc], LowerFunc]: + """Register a handler that generates low-level IR for a primitive op.""" + + def wrapper(f: LowerFunc) -> LowerFunc: + assert name not in lowering_registry + lowering_registry[name] = f + return f + + return wrapper + + +# Import various modules that set up global state. +from mypyc.lower import int_ops, list_ops, misc_ops # noqa: F401 diff --git a/mypyc/primitives/dict_ops.py b/mypyc/primitives/dict_ops.py index 9f477d0b7b90..ce7b9bb8d70e 100644 --- a/mypyc/primitives/dict_ops.py +++ b/mypyc/primitives/dict_ops.py @@ -113,7 +113,7 @@ # Operation used for **value in dict displays. # This is mostly like dict.update(obj), but has customized error handling. dict_update_in_display_op = custom_op( - arg_types=[dict_rprimitive, dict_rprimitive], + arg_types=[dict_rprimitive, object_rprimitive], return_type=c_int_rprimitive, c_function_name="CPyDict_UpdateInDisplay", error_kind=ERR_NEG_INT, diff --git a/mypyc/primitives/float_ops.py b/mypyc/primitives/float_ops.py index 535606df6176..14e8d4caf09c 100644 --- a/mypyc/primitives/float_ops.py +++ b/mypyc/primitives/float_ops.py @@ -2,18 +2,41 @@ from __future__ import annotations -from mypyc.ir.ops import ERR_MAGIC -from mypyc.ir.rtypes import float_rprimitive, object_rprimitive, str_rprimitive -from mypyc.primitives.registry import function_op, load_address_op +from mypyc.ir.ops import ERR_MAGIC, ERR_MAGIC_OVERLAPPING, ERR_NEVER +from mypyc.ir.rtypes import ( + bool_rprimitive, + float_rprimitive, + int_rprimitive, + object_rprimitive, + str_rprimitive, +) +from mypyc.primitives.registry import binary_op, function_op, load_address_op # Get the 'builtins.float' type object. load_address_op(name="builtins.float", type=object_rprimitive, src="PyFloat_Type") +binary_op( + name="//", + arg_types=[float_rprimitive, float_rprimitive], + return_type=float_rprimitive, + c_function_name="CPyFloat_FloorDivide", + error_kind=ERR_MAGIC_OVERLAPPING, +) + +# float(int) +int_to_float_op = function_op( + name="builtins.float", + arg_types=[int_rprimitive], + return_type=float_rprimitive, + c_function_name="CPyFloat_FromTagged", + error_kind=ERR_MAGIC_OVERLAPPING, +) + # float(str) function_op( name="builtins.float", arg_types=[str_rprimitive], - return_type=float_rprimitive, + return_type=object_rprimitive, c_function_name="PyFloat_FromString", error_kind=ERR_MAGIC, ) @@ -23,6 +46,123 @@ name="builtins.abs", arg_types=[float_rprimitive], return_type=float_rprimitive, - c_function_name="PyNumber_Absolute", + c_function_name="fabs", + error_kind=ERR_NEVER, +) + +# math.sin(float) +function_op( + name="math.sin", + arg_types=[float_rprimitive], + return_type=float_rprimitive, + c_function_name="CPyFloat_Sin", + error_kind=ERR_MAGIC_OVERLAPPING, +) + +# math.cos(float) +function_op( + name="math.cos", + arg_types=[float_rprimitive], + return_type=float_rprimitive, + c_function_name="CPyFloat_Cos", + error_kind=ERR_MAGIC_OVERLAPPING, +) + +# math.tan(float) +function_op( + name="math.tan", + arg_types=[float_rprimitive], + return_type=float_rprimitive, + c_function_name="CPyFloat_Tan", + error_kind=ERR_MAGIC_OVERLAPPING, +) + +# math.sqrt(float) +function_op( + name="math.sqrt", + arg_types=[float_rprimitive], + return_type=float_rprimitive, + c_function_name="CPyFloat_Sqrt", + error_kind=ERR_MAGIC_OVERLAPPING, +) + +# math.exp(float) +function_op( + name="math.exp", + arg_types=[float_rprimitive], + return_type=float_rprimitive, + c_function_name="CPyFloat_Exp", + error_kind=ERR_MAGIC_OVERLAPPING, +) + +# math.log(float) +function_op( + name="math.log", + arg_types=[float_rprimitive], + return_type=float_rprimitive, + c_function_name="CPyFloat_Log", + error_kind=ERR_MAGIC_OVERLAPPING, +) + +# math.floor(float) +function_op( + name="math.floor", + arg_types=[float_rprimitive], + return_type=int_rprimitive, + c_function_name="CPyFloat_Floor", error_kind=ERR_MAGIC, ) + +# math.ceil(float) +function_op( + name="math.ceil", + arg_types=[float_rprimitive], + return_type=int_rprimitive, + c_function_name="CPyFloat_Ceil", + error_kind=ERR_MAGIC, +) + +# math.fabs(float) +function_op( + name="math.fabs", + arg_types=[float_rprimitive], + return_type=float_rprimitive, + c_function_name="fabs", + error_kind=ERR_NEVER, +) + +# math.pow(float, float) +pow_op = function_op( + name="math.pow", + arg_types=[float_rprimitive, float_rprimitive], + return_type=float_rprimitive, + c_function_name="CPyFloat_Pow", + error_kind=ERR_MAGIC_OVERLAPPING, +) + +# math.copysign(float, float) +copysign_op = function_op( + name="math.copysign", + arg_types=[float_rprimitive, float_rprimitive], + return_type=float_rprimitive, + c_function_name="copysign", + error_kind=ERR_NEVER, +) + +# math.isinf(float) +function_op( + name="math.isinf", + arg_types=[float_rprimitive], + return_type=bool_rprimitive, + c_function_name="CPyFloat_IsInf", + error_kind=ERR_NEVER, +) + +# math.isnan(float) +function_op( + name="math.isnan", + arg_types=[float_rprimitive], + return_type=bool_rprimitive, + c_function_name="CPyFloat_IsNaN", + error_kind=ERR_NEVER, +) diff --git a/mypyc/primitives/int_ops.py b/mypyc/primitives/int_ops.py index 7eda9bab7e3c..2eff233403f4 100644 --- a/mypyc/primitives/int_ops.py +++ b/mypyc/primitives/int_ops.py @@ -10,15 +10,20 @@ from __future__ import annotations -from typing import NamedTuple - -from mypyc.ir.ops import ERR_ALWAYS, ERR_MAGIC, ERR_MAGIC_OVERLAPPING, ERR_NEVER, ComparisonOp +from mypyc.ir.ops import ( + ERR_ALWAYS, + ERR_MAGIC, + ERR_MAGIC_OVERLAPPING, + ERR_NEVER, + PrimitiveDescription, +) from mypyc.ir.rtypes import ( RType, bit_rprimitive, bool_rprimitive, c_pyssize_t_rprimitive, float_rprimitive, + int16_rprimitive, int32_rprimitive, int64_rprimitive, int_rprimitive, @@ -37,7 +42,13 @@ # Constructors for builtins.int and native int types have the same behavior. In # interpreted mode, native int types are just aliases to 'int'. -for int_name in ("builtins.int", "mypy_extensions.i64", "mypy_extensions.i32"): +for int_name in ( + "builtins.int", + "mypy_extensions.i64", + "mypy_extensions.i32", + "mypy_extensions.i16", + "mypy_extensions.u8", +): # These int constructors produce object_rprimitives that then need to be unboxed # I guess unboxing ourselves would save a check and branch though? @@ -50,8 +61,8 @@ function_op( name=int_name, arg_types=[float_rprimitive], - return_type=object_rprimitive, - c_function_name="CPyLong_FromFloat", + return_type=int_rprimitive, + c_function_name="CPyTagged_FromFloat", error_kind=ERR_MAGIC, ) @@ -94,6 +105,26 @@ ) +def int_binary_primitive( + op: str, primitive_name: str, return_type: RType = int_rprimitive, error_kind: int = ERR_NEVER +) -> PrimitiveDescription: + return binary_op( + name=op, + arg_types=[int_rprimitive, int_rprimitive], + return_type=return_type, + primitive_name=primitive_name, + error_kind=error_kind, + ) + + +int_eq = int_binary_primitive(op="==", primitive_name="int_eq", return_type=bit_rprimitive) +int_ne = int_binary_primitive(op="!=", primitive_name="int_ne", return_type=bit_rprimitive) +int_lt = int_binary_primitive(op="<", primitive_name="int_lt", return_type=bit_rprimitive) +int_le = int_binary_primitive(op="<=", primitive_name="int_le", return_type=bit_rprimitive) +int_gt = int_binary_primitive(op=">", primitive_name="int_gt", return_type=bit_rprimitive) +int_ge = int_binary_primitive(op=">=", primitive_name="int_ge", return_type=bit_rprimitive) + + def int_binary_op( name: str, c_function_name: str, @@ -126,6 +157,10 @@ def int_binary_op( int_binary_op(">>", "CPyTagged_Rshift", error_kind=ERR_MAGIC) int_binary_op("<<", "CPyTagged_Lshift", error_kind=ERR_MAGIC) +int_binary_op( + "/", "CPyTagged_TrueDivide", return_type=float_rprimitive, error_kind=ERR_MAGIC_OVERLAPPING +) + # This should work because assignment operators are parsed differently # and the code in irbuild that handles it does the assignment # regardless of whether or not the operator works in place anyway. @@ -157,19 +192,6 @@ def int_unary_op(name: str, c_function_name: str) -> CFunctionDescription: # Primitives related to integer comparison operations: -# Description for building int comparison ops -# -# Fields: -# binary_op_variant: identify which IntOp to use when operands are short integers -# c_func_description: the C function to call when operands are tagged integers -# c_func_negated: whether to negate the C function call's result -# c_func_swap_operands: whether to swap lhs and rhs when call the function -class IntComparisonOpDescription(NamedTuple): - binary_op_variant: int - c_func_description: CFunctionDescription - c_func_negated: bool - c_func_swap_operands: bool - # Equals operation on two boxed tagged integers int_equal_ = custom_op( @@ -177,6 +199,7 @@ class IntComparisonOpDescription(NamedTuple): return_type=bit_rprimitive, c_function_name="CPyTagged_IsEq_", error_kind=ERR_NEVER, + is_pure=True, ) # Less than operation on two boxed tagged integers @@ -185,19 +208,9 @@ class IntComparisonOpDescription(NamedTuple): return_type=bit_rprimitive, c_function_name="CPyTagged_IsLt_", error_kind=ERR_NEVER, + is_pure=True, ) -# Provide mapping from textual op to short int's op variant and boxed int's description. -# Note that these are not complete implementations and require extra IR. -int_comparison_op_mapping: dict[str, IntComparisonOpDescription] = { - "==": IntComparisonOpDescription(ComparisonOp.EQ, int_equal_, False, False), - "!=": IntComparisonOpDescription(ComparisonOp.NEQ, int_equal_, True, False), - "<": IntComparisonOpDescription(ComparisonOp.SLT, int_less_than_, False, False), - "<=": IntComparisonOpDescription(ComparisonOp.SLE, int_less_than_, True, True), - ">": IntComparisonOpDescription(ComparisonOp.SGT, int_less_than_, False, True), - ">=": IntComparisonOpDescription(ComparisonOp.SGE, int_less_than_, True, False), -} - int64_divide_op = custom_op( arg_types=[int64_rprimitive, int64_rprimitive], return_type=int64_rprimitive, @@ -226,6 +239,20 @@ class IntComparisonOpDescription(NamedTuple): error_kind=ERR_MAGIC_OVERLAPPING, ) +int16_divide_op = custom_op( + arg_types=[int16_rprimitive, int16_rprimitive], + return_type=int16_rprimitive, + c_function_name="CPyInt16_Divide", + error_kind=ERR_MAGIC_OVERLAPPING, +) + +int16_mod_op = custom_op( + arg_types=[int16_rprimitive, int16_rprimitive], + return_type=int16_rprimitive, + c_function_name="CPyInt16_Remainder", + error_kind=ERR_MAGIC_OVERLAPPING, +) + # Convert tagged int (as PyObject *) to i64 int_to_int64_op = custom_op( arg_types=[object_rprimitive], @@ -262,3 +289,17 @@ class IntComparisonOpDescription(NamedTuple): c_function_name="CPyInt32_Overflow", error_kind=ERR_ALWAYS, ) + +int16_overflow = custom_op( + arg_types=[], + return_type=void_rtype, + c_function_name="CPyInt16_Overflow", + error_kind=ERR_ALWAYS, +) + +uint8_overflow = custom_op( + arg_types=[], + return_type=void_rtype, + c_function_name="CPyUInt8_Overflow", + error_kind=ERR_ALWAYS, +) diff --git a/mypyc/primitives/list_ops.py b/mypyc/primitives/list_ops.py index 7fe3157f3a38..cb75e19a8dea 100644 --- a/mypyc/primitives/list_ops.py +++ b/mypyc/primitives/list_ops.py @@ -11,12 +11,14 @@ int_rprimitive, list_rprimitive, object_rprimitive, + pointer_rprimitive, short_int_rprimitive, ) from mypyc.primitives.registry import ( ERR_NEG_INT, binary_op, custom_op, + custom_primitive_op, function_op, load_address_op, method_op, @@ -60,6 +62,14 @@ steals=True, ) +# Get pointer to list items (ob_item PyListObject field) +list_items = custom_primitive_op( + name="list_items", + arg_types=[list_rprimitive], + return_type=pointer_rprimitive, + error_kind=ERR_NEVER, +) + # list[index] (for an integer index) list_get_item_op = method_op( name="__getitem__", diff --git a/mypyc/primitives/misc_ops.py b/mypyc/primitives/misc_ops.py index 07df9c69714b..fea62bbb19c4 100644 --- a/mypyc/primitives/misc_ops.py +++ b/mypyc/primitives/misc_ops.py @@ -7,15 +7,23 @@ bit_rprimitive, bool_rprimitive, c_int_rprimitive, + c_pointer_rprimitive, c_pyssize_t_rprimitive, dict_rprimitive, int_rprimitive, - list_rprimitive, object_pointer_rprimitive, object_rprimitive, + pointer_rprimitive, str_rprimitive, + void_rtype, +) +from mypyc.primitives.registry import ( + ERR_NEG_INT, + custom_op, + custom_primitive_op, + function_op, + load_address_op, ) -from mypyc.primitives.registry import ERR_NEG_INT, custom_op, function_op, load_address_op # Get the 'bool' type object. load_address_op(name="builtins.bool", type=object_rprimitive, src="PyBool_Type") @@ -112,7 +120,7 @@ is_borrowed=True, ) -# Import a module +# Import a module (plain) import_op = custom_op( arg_types=[str_rprimitive], return_type=object_rprimitive, @@ -120,25 +128,26 @@ error_kind=ERR_MAGIC, ) -# Import with extra arguments (used in from import handling) -import_extra_args_op = custom_op( +# Table-driven import op. +import_many_op = custom_op( arg_types=[ - str_rprimitive, - dict_rprimitive, - dict_rprimitive, - list_rprimitive, - c_int_rprimitive, + object_rprimitive, + c_pointer_rprimitive, + object_rprimitive, + object_rprimitive, + object_rprimitive, + c_pointer_rprimitive, ], - return_type=object_rprimitive, - c_function_name="PyImport_ImportModuleLevelObject", - error_kind=ERR_MAGIC, + return_type=bit_rprimitive, + c_function_name="CPyImport_ImportMany", + error_kind=ERR_FALSE, ) -# Import-from helper op -import_from_op = custom_op( - arg_types=[object_rprimitive, str_rprimitive, str_rprimitive, str_rprimitive], +# From import helper op +import_from_many_op = custom_op( + arg_types=[object_rprimitive, object_rprimitive, object_rprimitive, object_rprimitive], return_type=object_rprimitive, - c_function_name="CPyImport_ImportFrom", + c_function_name="CPyImport_ImportFromMany", error_kind=ERR_MAGIC, ) @@ -231,10 +240,28 @@ ) -# register an implementation for a singledispatch function +# Register an implementation for a singledispatch function register_function = custom_op( arg_types=[object_rprimitive, object_rprimitive, object_rprimitive], return_type=object_rprimitive, c_function_name="CPySingledispatch_RegisterFunction", error_kind=ERR_MAGIC, ) + + +# Initialize a PyObject * item in a memory buffer (steal the value) +buf_init_item = custom_primitive_op( + name="buf_init_item", + arg_types=[pointer_rprimitive, c_pyssize_t_rprimitive, object_rprimitive], + return_type=void_rtype, + error_kind=ERR_NEVER, + steals=[False, False, True], +) + +# Get length of PyVarObject instance (e.g. list or tuple) +var_object_size = custom_primitive_op( + name="var_object_size", + arg_types=[object_rprimitive], + return_type=c_pyssize_t_rprimitive, + error_kind=ERR_NEVER, +) diff --git a/mypyc/primitives/registry.py b/mypyc/primitives/registry.py index 1e2cf2695ee7..5190b01adf4a 100644 --- a/mypyc/primitives/registry.py +++ b/mypyc/primitives/registry.py @@ -37,10 +37,9 @@ from __future__ import annotations -from typing import List, NamedTuple, Optional, Tuple -from typing_extensions import Final +from typing import Final, NamedTuple -from mypyc.ir.ops import StealsDescription +from mypyc.ir.ops import PrimitiveDescription, StealsDescription from mypyc.ir.rtypes import RType # Error kind for functions that return negative integer on exception. This @@ -50,17 +49,18 @@ class CFunctionDescription(NamedTuple): name: str - arg_types: List[RType] + arg_types: list[RType] return_type: RType - var_arg_type: Optional[RType] - truncated_type: Optional[RType] + var_arg_type: RType | None + truncated_type: RType | None c_function_name: str error_kind: int steals: StealsDescription is_borrowed: bool - ordering: Optional[List[int]] - extra_int_constants: List[Tuple[int, RType]] + ordering: list[int] | None + extra_int_constants: list[tuple[int, RType]] priority: int + is_pure: bool # A description for C load operations including LoadGlobal and LoadAddress @@ -77,7 +77,7 @@ class LoadAddressDescription(NamedTuple): function_ops: dict[str, list[CFunctionDescription]] = {} # CallC op for binary ops -binary_ops: dict[str, list[CFunctionDescription]] = {} +binary_ops: dict[str, list[PrimitiveDescription]] = {} # CallC op for unary ops unary_ops: dict[str, list[CFunctionDescription]] = {} @@ -94,10 +94,11 @@ def method_op( var_arg_type: RType | None = None, truncated_type: RType | None = None, ordering: list[int] | None = None, - extra_int_constants: list[tuple[int, RType]] = [], + extra_int_constants: list[tuple[int, RType]] | None = None, steals: StealsDescription = False, is_borrowed: bool = False, priority: int = 1, + is_pure: bool = False, ) -> CFunctionDescription: """Define a c function call op that replaces a method call. @@ -122,7 +123,11 @@ def method_op( steals: description of arguments that this steals (ref count wise) is_borrowed: if True, returned value is borrowed (no need to decrease refcount) priority: if multiple ops match, the one with the highest priority is picked + is_pure: if True, declare that the C function has no side effects, takes immutable + arguments, and never raises an exception """ + if extra_int_constants is None: + extra_int_constants = [] ops = method_call_ops.setdefault(name, []) desc = CFunctionDescription( name, @@ -137,6 +142,7 @@ def method_op( ordering, extra_int_constants, priority, + is_pure=is_pure, ) ops.append(desc) return desc @@ -151,7 +157,7 @@ def function_op( var_arg_type: RType | None = None, truncated_type: RType | None = None, ordering: list[int] | None = None, - extra_int_constants: list[tuple[int, RType]] = [], + extra_int_constants: list[tuple[int, RType]] | None = None, steals: StealsDescription = False, is_borrowed: bool = False, priority: int = 1, @@ -166,6 +172,8 @@ def function_op( name: full name of the function arg_types: positional argument types for which this applies """ + if extra_int_constants is None: + extra_int_constants = [] ops = function_ops.setdefault(name, []) desc = CFunctionDescription( name, @@ -180,6 +188,7 @@ def function_op( ordering, extra_int_constants, priority, + is_pure=False, ) ops.append(desc) return desc @@ -189,16 +198,17 @@ def binary_op( name: str, arg_types: list[RType], return_type: RType, - c_function_name: str, error_kind: int, + c_function_name: str | None = None, + primitive_name: str | None = None, var_arg_type: RType | None = None, truncated_type: RType | None = None, ordering: list[int] | None = None, - extra_int_constants: list[tuple[int, RType]] = [], + extra_int_constants: list[tuple[int, RType]] | None = None, steals: StealsDescription = False, is_borrowed: bool = False, priority: int = 1, -) -> CFunctionDescription: +) -> PrimitiveDescription: """Define a c function call op for a binary operation. This will be automatically generated by matching against the AST. @@ -206,20 +216,25 @@ def binary_op( Most arguments are similar to method_op(), but exactly two argument types are expected. """ + assert c_function_name is not None or primitive_name is not None + assert not (c_function_name is not None and primitive_name is not None) + if extra_int_constants is None: + extra_int_constants = [] ops = binary_ops.setdefault(name, []) - desc = CFunctionDescription( - name, - arg_types, - return_type, - var_arg_type, - truncated_type, - c_function_name, - error_kind, - steals, - is_borrowed, - ordering, - extra_int_constants, - priority, + desc = PrimitiveDescription( + name=primitive_name or name, + arg_types=arg_types, + return_type=return_type, + var_arg_type=var_arg_type, + truncated_type=truncated_type, + c_function_name=c_function_name, + error_kind=error_kind, + steals=steals, + is_borrowed=is_borrowed, + ordering=ordering, + extra_int_constants=extra_int_constants, + priority=priority, + is_pure=False, ) ops.append(desc) return desc @@ -233,14 +248,18 @@ def custom_op( var_arg_type: RType | None = None, truncated_type: RType | None = None, ordering: list[int] | None = None, - extra_int_constants: list[tuple[int, RType]] = [], + extra_int_constants: list[tuple[int, RType]] | None = None, steals: StealsDescription = False, is_borrowed: bool = False, + *, + is_pure: bool = False, ) -> CFunctionDescription: """Create a one-off CallC op that can't be automatically generated from the AST. Most arguments are similar to method_op(). """ + if extra_int_constants is None: + extra_int_constants = [] return CFunctionDescription( "", arg_types, @@ -254,6 +273,44 @@ def custom_op( ordering, extra_int_constants, 0, + is_pure=is_pure, + ) + + +def custom_primitive_op( + name: str, + arg_types: list[RType], + return_type: RType, + error_kind: int, + c_function_name: str | None = None, + var_arg_type: RType | None = None, + truncated_type: RType | None = None, + ordering: list[int] | None = None, + extra_int_constants: list[tuple[int, RType]] | None = None, + steals: StealsDescription = False, + is_borrowed: bool = False, + is_pure: bool = False, +) -> PrimitiveDescription: + """Define a primitive op that can't be automatically generated based on the AST. + + Most arguments are similar to method_op(). + """ + if extra_int_constants is None: + extra_int_constants = [] + return PrimitiveDescription( + name=name, + arg_types=arg_types, + return_type=return_type, + var_arg_type=var_arg_type, + truncated_type=truncated_type, + c_function_name=c_function_name, + error_kind=error_kind, + steals=steals, + is_borrowed=is_borrowed, + ordering=ordering, + extra_int_constants=extra_int_constants, + priority=0, + is_pure=is_pure, ) @@ -265,10 +322,11 @@ def unary_op( error_kind: int, truncated_type: RType | None = None, ordering: list[int] | None = None, - extra_int_constants: list[tuple[int, RType]] = [], + extra_int_constants: list[tuple[int, RType]] | None = None, steals: StealsDescription = False, is_borrowed: bool = False, priority: int = 1, + is_pure: bool = False, ) -> CFunctionDescription: """Define a c function call op for an unary operation. @@ -277,6 +335,8 @@ def unary_op( Most arguments are similar to method_op(), but exactly one argument type is expected. """ + if extra_int_constants is None: + extra_int_constants = [] ops = unary_ops.setdefault(name, []) desc = CFunctionDescription( name, @@ -291,6 +351,7 @@ def unary_op( ordering, extra_int_constants, priority, + is_pure=is_pure, ) ops.append(desc) return desc @@ -302,11 +363,10 @@ def load_address_op(name: str, type: RType, src: str) -> LoadAddressDescription: return LoadAddressDescription(name, type, src) +# Import various modules that set up global state. import mypyc.primitives.bytes_ops import mypyc.primitives.dict_ops import mypyc.primitives.float_ops - -# Import various modules that set up global state. import mypyc.primitives.int_ops import mypyc.primitives.list_ops import mypyc.primitives.misc_ops diff --git a/mypyc/rt_subtype.py b/mypyc/rt_subtype.py index f3fe1a442d22..004e56ed75bc 100644 --- a/mypyc/rt_subtype.py +++ b/mypyc/rt_subtype.py @@ -51,7 +51,7 @@ def visit_rinstance(self, left: RInstance) -> bool: return is_subtype(left, self.right) def visit_runion(self, left: RUnion) -> bool: - return is_subtype(left, self.right) + return not self.right.is_unboxed and is_subtype(left, self.right) def visit_rprimitive(self, left: RPrimitive) -> bool: if is_short_int_rprimitive(left) and is_int_rprimitive(self.right): diff --git a/mypyc/test-data/analysis.test b/mypyc/test-data/analysis.test index efd219cc222a..35677b8ea56d 100644 --- a/mypyc/test-data/analysis.test +++ b/mypyc/test-data/analysis.test @@ -10,40 +10,27 @@ def f(a: int) -> None: [out] def f(a): a, x :: int - r0 :: native_int - r1, r2, r3 :: bit + r0 :: bit y, z :: int L0: x = 2 - r0 = x & 1 - r1 = r0 != 0 - if r1 goto L1 else goto L2 :: bool + r0 = int_eq x, a + if r0 goto L1 else goto L2 :: bool L1: - r2 = CPyTagged_IsEq_(x, a) - if r2 goto L3 else goto L4 :: bool -L2: - r3 = x == a - if r3 goto L3 else goto L4 :: bool -L3: y = 2 - goto L5 -L4: + goto L3 +L2: z = 2 -L5: +L3: return 1 (0, 0) {a} {a, x} (0, 1) {a, x} {a, x} (0, 2) {a, x} {a, x} -(0, 3) {a, x} {a, x} -(1, 0) {a, x} {a, x} -(1, 1) {a, x} {a, x} -(2, 0) {a, x} {a, x} -(2, 1) {a, x} {a, x} -(3, 0) {a, x} {a, x, y} -(3, 1) {a, x, y} {a, x, y} -(4, 0) {a, x} {a, x, z} -(4, 1) {a, x, z} {a, x, z} -(5, 0) {a, x, y, z} {a, x, y, z} +(1, 0) {a, x} {a, x, y} +(1, 1) {a, x, y} {a, x, y} +(2, 0) {a, x} {a, x, z} +(2, 1) {a, x, z} {a, x, z} +(3, 0) {a, x, y, z} {a, x, y, z} [case testSimple_Liveness] def f(a: int) -> int: @@ -58,7 +45,7 @@ def f(a): r0 :: bit L0: x = 2 - r0 = x == 2 + r0 = int_eq x, 2 if r0 goto L1 else goto L2 :: bool L1: return a @@ -124,7 +111,7 @@ def f(a): r0 :: bit y, x :: int L0: - r0 = a == 2 + r0 = int_eq a, 2 if r0 goto L1 else goto L2 :: bool L1: y = 2 @@ -161,40 +148,27 @@ def f(n: int) -> None: [out] def f(n): n :: int - r0 :: native_int - r1, r2, r3 :: bit - r4, m :: int + r0 :: bit + r1, m :: int L0: L1: - r0 = n & 1 - r1 = r0 != 0 - if r1 goto L2 else goto L3 :: bool + r0 = int_lt n, 10 + if r0 goto L2 else goto L3 :: bool L2: - r2 = CPyTagged_IsLt_(n, 10) - if r2 goto L4 else goto L5 :: bool -L3: - r3 = n < 10 :: signed - if r3 goto L4 else goto L5 :: bool -L4: - r4 = CPyTagged_Add(n, 2) - n = r4 + r1 = CPyTagged_Add(n, 2) + n = r1 m = n goto L1 -L5: +L3: return 1 (0, 0) {n} {n} (1, 0) {n} {n} (1, 1) {n} {n} -(1, 2) {n} {n} (2, 0) {n} {n} (2, 1) {n} {n} +(2, 2) {n} {m, n} +(2, 3) {m, n} {m, n} (3, 0) {n} {n} -(3, 1) {n} {n} -(4, 0) {n} {n} -(4, 1) {n} {n} -(4, 2) {n} {m, n} -(4, 3) {m, n} {m, n} -(5, 0) {n} {n} [case testMultiPass_Liveness] def f(n: int) -> None: @@ -208,67 +182,40 @@ def f(n: int) -> None: [out] def f(n): n, x, y :: int - r0 :: native_int - r1, r2, r3 :: bit - r4 :: native_int - r5, r6, r7 :: bit + r0, r1 :: bit L0: x = 2 y = 2 L1: - r0 = n & 1 - r1 = r0 != 0 - if r1 goto L2 else goto L3 :: bool + r0 = int_lt n, 2 + if r0 goto L2 else goto L6 :: bool L2: - r2 = CPyTagged_IsLt_(n, 2) - if r2 goto L4 else goto L10 :: bool + n = y L3: - r3 = n < 2 :: signed - if r3 goto L4 else goto L10 :: bool + r1 = int_lt n, 4 + if r1 goto L4 else goto L5 :: bool L4: - n = y -L5: - r4 = n & 1 - r5 = r4 != 0 - if r5 goto L6 else goto L7 :: bool -L6: - r6 = CPyTagged_IsLt_(n, 4) - if r6 goto L8 else goto L9 :: bool -L7: - r7 = n < 4 :: signed - if r7 goto L8 else goto L9 :: bool -L8: n = 2 n = x - goto L5 -L9: + goto L3 +L5: goto L1 -L10: +L6: return 1 (0, 0) {n} {n, x} (0, 1) {n, x} {n, x, y} (0, 2) {n, x, y} {n, x, y} -(1, 0) {n, x, y} {n, r0, x, y} -(1, 1) {n, r0, x, y} {n, r1, x, y} -(1, 2) {n, r1, x, y} {n, x, y} -(2, 0) {n, x, y} {r2, x, y} -(2, 1) {r2, x, y} {x, y} -(3, 0) {n, x, y} {r3, x, y} -(3, 1) {r3, x, y} {x, y} -(4, 0) {x, y} {n, x, y} -(4, 1) {n, x, y} {n, x, y} -(5, 0) {n, x, y} {n, r4, x, y} -(5, 1) {n, r4, x, y} {n, r5, x, y} -(5, 2) {n, r5, x, y} {n, x, y} -(6, 0) {n, x, y} {n, r6, x, y} -(6, 1) {n, r6, x, y} {n, x, y} -(7, 0) {n, x, y} {n, r7, x, y} -(7, 1) {n, r7, x, y} {n, x, y} -(8, 0) {x, y} {x, y} -(8, 1) {x, y} {n, x, y} -(8, 2) {n, x, y} {n, x, y} -(9, 0) {n, x, y} {n, x, y} -(10, 0) {} {} +(1, 0) {n, x, y} {r0, x, y} +(1, 1) {r0, x, y} {x, y} +(2, 0) {x, y} {n, x, y} +(2, 1) {n, x, y} {n, x, y} +(3, 0) {n, x, y} {n, r1, x, y} +(3, 1) {n, r1, x, y} {n, x, y} +(4, 0) {x, y} {x, y} +(4, 1) {x, y} {n, x, y} +(4, 2) {n, x, y} {n, x, y} +(5, 0) {n, x, y} {n, x, y} +(6, 0) {} {} [case testCall_Liveness] def f(x: int) -> int: @@ -309,80 +256,35 @@ def f(a: int) -> None: [out] def f(a): a :: int - r0 :: native_int - r1 :: bit - r2 :: native_int - r3, r4, r5 :: bit - r6 :: native_int - r7 :: bit - r8 :: native_int - r9, r10, r11 :: bit + r0, r1 :: bit y, x :: int L0: L1: - r0 = a & 1 - r1 = r0 != 0 - if r1 goto L3 else goto L2 :: bool + r0 = int_lt a, a + if r0 goto L2 else goto L6 :: bool L2: - r2 = a & 1 - r3 = r2 != 0 - if r3 goto L3 else goto L4 :: bool L3: - r4 = CPyTagged_IsLt_(a, a) - if r4 goto L5 else goto L12 :: bool + r1 = int_lt a, a + if r1 goto L4 else goto L5 :: bool L4: - r5 = a < a :: signed - if r5 goto L5 else goto L12 :: bool -L5: -L6: - r6 = a & 1 - r7 = r6 != 0 - if r7 goto L8 else goto L7 :: bool -L7: - r8 = a & 1 - r9 = r8 != 0 - if r9 goto L8 else goto L9 :: bool -L8: - r10 = CPyTagged_IsLt_(a, a) - if r10 goto L10 else goto L11 :: bool -L9: - r11 = a < a :: signed - if r11 goto L10 else goto L11 :: bool -L10: y = a - goto L6 -L11: + goto L3 +L5: x = a goto L1 -L12: +L6: return 1 (0, 0) {a} {a} (1, 0) {a, x, y} {a, x, y} (1, 1) {a, x, y} {a, x, y} -(1, 2) {a, x, y} {a, x, y} (2, 0) {a, x, y} {a, x, y} -(2, 1) {a, x, y} {a, x, y} -(2, 2) {a, x, y} {a, x, y} (3, 0) {a, x, y} {a, x, y} (3, 1) {a, x, y} {a, x, y} (4, 0) {a, x, y} {a, x, y} (4, 1) {a, x, y} {a, x, y} (5, 0) {a, x, y} {a, x, y} +(5, 1) {a, x, y} {a, x, y} (6, 0) {a, x, y} {a, x, y} -(6, 1) {a, x, y} {a, x, y} -(6, 2) {a, x, y} {a, x, y} -(7, 0) {a, x, y} {a, x, y} -(7, 1) {a, x, y} {a, x, y} -(7, 2) {a, x, y} {a, x, y} -(8, 0) {a, x, y} {a, x, y} -(8, 1) {a, x, y} {a, x, y} -(9, 0) {a, x, y} {a, x, y} -(9, 1) {a, x, y} {a, x, y} -(10, 0) {a, x, y} {a, x, y} -(10, 1) {a, x, y} {a, x, y} -(11, 0) {a, x, y} {a, x, y} -(11, 1) {a, x, y} {a, x, y} -(12, 0) {a, x, y} {a, x, y} [case testTrivial_BorrowedArgument] def f(a: int, b: int) -> int: @@ -421,40 +323,27 @@ def f(a: int) -> int: [out] def f(a): a :: int - r0 :: native_int - r1, r2, r3 :: bit + r0 :: bit x :: int L0: - r0 = a & 1 - r1 = r0 != 0 - if r1 goto L1 else goto L2 :: bool + r0 = int_eq a, a + if r0 goto L1 else goto L2 :: bool L1: - r2 = CPyTagged_IsEq_(a, a) - if r2 goto L3 else goto L4 :: bool -L2: - r3 = a == a - if r3 goto L3 else goto L4 :: bool -L3: x = 4 a = 2 - goto L5 -L4: + goto L3 +L2: x = 2 -L5: +L3: return x (0, 0) {a} {a} (0, 1) {a} {a} -(0, 2) {a} {a} (1, 0) {a} {a} -(1, 1) {a} {a} +(1, 1) {a} {} +(1, 2) {} {} (2, 0) {a} {a} (2, 1) {a} {a} -(3, 0) {a} {a} -(3, 1) {a} {} -(3, 2) {} {} -(4, 0) {a} {a} -(4, 1) {a} {a} -(5, 0) {} {} +(3, 0) {} {} [case testLoop_BorrowedArgument] def f(a: int) -> int: @@ -467,55 +356,33 @@ def f(a: int) -> int: [out] def f(a): a, sum, i :: int - r0 :: native_int - r1 :: bit - r2 :: native_int - r3, r4, r5 :: bit - r6, r7 :: int + r0 :: bit + r1, r2 :: int L0: sum = 0 i = 0 L1: - r0 = i & 1 - r1 = r0 != 0 - if r1 goto L3 else goto L2 :: bool + r0 = int_le i, a + if r0 goto L2 else goto L3 :: bool L2: - r2 = a & 1 - r3 = r2 != 0 - if r3 goto L3 else goto L4 :: bool -L3: - r4 = CPyTagged_IsLt_(a, i) - if r4 goto L6 else goto L5 :: bool -L4: - r5 = i <= a :: signed - if r5 goto L5 else goto L6 :: bool -L5: - r6 = CPyTagged_Add(sum, i) - sum = r6 - r7 = CPyTagged_Add(i, 2) - i = r7 + r1 = CPyTagged_Add(sum, i) + sum = r1 + r2 = CPyTagged_Add(i, 2) + i = r2 goto L1 -L6: +L3: return sum (0, 0) {a} {a} (0, 1) {a} {a} (0, 2) {a} {a} (1, 0) {a} {a} (1, 1) {a} {a} -(1, 2) {a} {a} (2, 0) {a} {a} (2, 1) {a} {a} (2, 2) {a} {a} +(2, 3) {a} {a} +(2, 4) {a} {a} (3, 0) {a} {a} -(3, 1) {a} {a} -(4, 0) {a} {a} -(4, 1) {a} {a} -(5, 0) {a} {a} -(5, 1) {a} {a} -(5, 2) {a} {a} -(5, 3) {a} {a} -(5, 4) {a} {a} -(6, 0) {a} {a} [case testError] def f(x: List[int]) -> None: pass # E: Name "List" is not defined \ diff --git a/mypyc/test-data/driver/driver.py b/mypyc/test-data/driver/driver.py index 6717f402f72d..c9d179224a30 100644 --- a/mypyc/test-data/driver/driver.py +++ b/mypyc/test-data/driver/driver.py @@ -18,7 +18,7 @@ try: test_func() except Exception as e: - failures.append(sys.exc_info()) + failures.append((name, sys.exc_info())) if failures: from traceback import print_exception, format_tb @@ -32,12 +32,17 @@ def extract_line(tb): return m.group(1) # Sort failures by line number of test function. - failures = sorted(failures, key=lambda e: extract_line(e[2])) + failures = sorted(failures, key=lambda e: extract_line(e[1][2])) # If there are multiple failures, print stack traces of all but the final failure. - for e in failures[:-1]: + for name, e in failures[:-1]: + print(f'<< {name} >>') + sys.stdout.flush() print_exception(*e) print() + sys.stdout.flush() # Raise exception for the last failure. Test runner will show the traceback. - raise failures[-1][1] + print(f'<< {failures[-1][0]} >>') + sys.stdout.flush() + raise failures[-1][1][1] diff --git a/mypyc/test-data/exceptions.test b/mypyc/test-data/exceptions.test index 187551249676..1ec03dd9a671 100644 --- a/mypyc/test-data/exceptions.test +++ b/mypyc/test-data/exceptions.test @@ -34,7 +34,7 @@ def f(x, y, z): x :: list y, z :: int r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: object r4 :: bit @@ -111,56 +111,42 @@ def sum(a: List[int], l: int) -> int: def sum(a, l): a :: list l, sum, i :: int - r0 :: native_int - r1 :: bit - r2 :: native_int - r3, r4, r5 :: bit - r6 :: object - r7, r8, r9, r10 :: int + r0 :: bit + r1 :: object + r2, r3, r4, r5 :: int L0: sum = 0 i = 0 L1: - r0 = i & 1 - r1 = r0 != 0 - if r1 goto L3 else goto L2 :: bool + r0 = int_lt i, l + if r0 goto L2 else goto L7 :: bool L2: - r2 = l & 1 - r3 = r2 != 0 - if r3 goto L3 else goto L4 :: bool + r1 = CPyList_GetItemBorrow(a, i) + if is_error(r1) goto L8 (error at sum:6) else goto L3 L3: - r4 = CPyTagged_IsLt_(i, l) - if r4 goto L5 else goto L10 :: bool + r2 = unbox(int, r1) + if is_error(r2) goto L8 (error at sum:6) else goto L4 L4: - r5 = i < l :: signed - if r5 goto L5 else goto L10 :: bool -L5: - r6 = CPyList_GetItemBorrow(a, i) - if is_error(r6) goto L11 (error at sum:6) else goto L6 -L6: - r7 = unbox(int, r6) - if is_error(r7) goto L11 (error at sum:6) else goto L7 -L7: - r8 = CPyTagged_Add(sum, r7) + r3 = CPyTagged_Add(sum, r2) dec_ref sum :: int - dec_ref r7 :: int - sum = r8 - r9 = CPyTagged_Add(i, 2) + dec_ref r2 :: int + sum = r3 + r4 = CPyTagged_Add(i, 2) dec_ref i :: int - i = r9 + i = r4 goto L1 -L8: +L5: return sum -L9: - r10 = :: int - return r10 -L10: +L6: + r5 = :: int + return r5 +L7: dec_ref i :: int - goto L8 -L11: + goto L5 +L8: dec_ref sum :: int dec_ref i :: int - goto L9 + goto L6 [case testTryExcept] def g() -> None: @@ -528,10 +514,10 @@ def f(): L0: return 0 def g(): - r0 :: int64 + r0 :: i64 r1 :: bit r2 :: object - r3 :: int64 + r3 :: i64 L0: r0 = f() r1 = r0 == -113 @@ -542,7 +528,7 @@ L2: r2 = PyErr_Occurred() if not is_error(r2) goto L3 (error at g:7) else goto L1 L3: - r3 = :: int64 + r3 = :: i64 return r3 [case testExceptionWithNativeAttributeGetAndSet] @@ -570,6 +556,34 @@ L0: c.x = r1 return 1 +[case testExceptionWithOverlappingFloatErrorValue] +def f() -> float: + return 0.0 + +def g() -> float: + return f() +[out] +def f(): +L0: + return 0.0 +def g(): + r0 :: float + r1 :: bit + r2 :: object + r3 :: float +L0: + r0 = f() + r1 = r0 == -113.0 + if r1 goto L2 else goto L1 :: bool +L1: + return r0 +L2: + r2 = PyErr_Occurred() + if not is_error(r2) goto L3 (error at g:5) else goto L1 +L3: + r3 = :: float + return r3 + [case testExceptionWithLowLevelIntAttribute] from mypy_extensions import i32, i64 @@ -584,16 +598,16 @@ def f(c: C) -> None: [out] def C.__init__(self, x, y): self :: __main__.C - x :: int32 - y :: int64 + x :: i32 + y :: i64 L0: self.x = x self.y = y return 1 def f(c): c :: __main__.C - r0 :: int32 - r1 :: int64 + r0 :: i32 + r1 :: i64 L0: r0 = c.x r1 = c.y @@ -608,15 +622,15 @@ def f(x: i64) -> i64: return y [out] def f(x): - x, r0, y :: int64 - __locals_bitmap0 :: uint32 + x, r0, y :: i64 + __locals_bitmap0 :: u32 r1 :: bit - r2, r3 :: uint32 + r2, r3 :: u32 r4 :: bit r5 :: bool - r6 :: int64 + r6 :: i64 L0: - r0 = :: int64 + r0 = :: i64 y = r0 __locals_bitmap0 = 0 r1 = x != 0 @@ -637,5 +651,49 @@ L4: L5: return y L6: - r6 = :: int64 + r6 = :: i64 return r6 + +[case testExceptionWithFloatAttribute] +class C: + def __init__(self, x: float, y: float) -> None: + self.x = x + if x: + self.y = y + +def f(c: C) -> float: + return c.x + c.y +[out] +def C.__init__(self, x, y): + self :: __main__.C + x, y :: float + r0 :: bit +L0: + self.x = x + r0 = x != 0.0 + if r0 goto L1 else goto L2 :: bool +L1: + self.y = y +L2: + return 1 +def f(c): + c :: __main__.C + r0, r1 :: float + r2 :: bit + r3 :: float + r4 :: object + r5 :: float +L0: + r0 = c.x + r1 = c.y + r2 = r1 == -113.0 + if r2 goto L2 else goto L1 :: bool +L1: + r3 = r0 + r1 + return r3 +L2: + r4 = PyErr_Occurred() + if not is_error(r4) goto L3 (error at f:8) else goto L1 +L3: + r5 = :: float + return r5 diff --git a/mypyc/test-data/fixtures/ir.py b/mypyc/test-data/fixtures/ir.py index 27e225f273bc..bf06613ad2a8 100644 --- a/mypyc/test-data/fixtures/ir.py +++ b/mypyc/test-data/fixtures/ir.py @@ -1,6 +1,7 @@ # These builtins stubs are used implicitly in AST to IR generation # test cases. +import _typeshed from typing import ( TypeVar, Generic, List, Iterator, Iterable, Dict, Optional, Tuple, Any, Set, overload, Mapping, Union, Callable, Sequence, FrozenSet, Protocol @@ -86,6 +87,8 @@ def __init__(self) -> None: pass @overload def __init__(self, x: object) -> None: pass def __add__(self, x: str) -> str: pass + def __mul__(self, x: int) -> str: pass + def __rmul__(self, x: int) -> str: pass def __eq__(self, x: object) -> bool: pass def __ne__(self, x: object) -> bool: pass def __lt__(self, x: str) -> bool: ... @@ -111,19 +114,31 @@ def encode(self, x: str=..., y: str=...) -> bytes: ... class float: def __init__(self, x: object) -> None: pass def __add__(self, n: float) -> float: pass + def __radd__(self, n: float) -> float: pass def __sub__(self, n: float) -> float: pass + def __rsub__(self, n: float) -> float: pass def __mul__(self, n: float) -> float: pass def __truediv__(self, n: float) -> float: pass + def __floordiv__(self, n: float) -> float: pass + def __mod__(self, n: float) -> float: pass def __pow__(self, n: float) -> float: pass def __neg__(self) -> float: pass def __pos__(self) -> float: pass def __abs__(self) -> float: pass def __invert__(self) -> float: pass + def __eq__(self, x: object) -> bool: pass + def __ne__(self, x: object) -> bool: pass + def __lt__(self, x: float) -> bool: ... + def __le__(self, x: float) -> bool: ... + def __gt__(self, x: float) -> bool: ... + def __ge__(self, x: float) -> bool: ... class complex: def __init__(self, x: object, y: object = None) -> None: pass def __add__(self, n: complex) -> complex: pass + def __radd__(self, n: float) -> complex: pass def __sub__(self, n: complex) -> complex: pass + def __rsub__(self, n: float) -> complex: pass def __mul__(self, n: complex) -> complex: pass def __truediv__(self, n: complex) -> complex: pass def __neg__(self) -> complex: pass @@ -134,6 +149,8 @@ def __init__(self) -> None: ... @overload def __init__(self, x: object) -> None: ... def __add__(self, x: bytes) -> bytes: ... + def __mul__(self, x: int) -> bytes: ... + def __rmul__(self, x: int) -> bytes: ... def __eq__(self, x: object) -> bool: ... def __ne__(self, x: object) -> bool: ... @overload @@ -288,6 +305,7 @@ class ValueError(Exception): pass class AttributeError(Exception): pass class ImportError(Exception): pass class NameError(Exception): pass +class UnboundLocalError(NameError): pass class LookupError(Exception): pass class KeyError(LookupError): pass class IndexError(LookupError): pass diff --git a/mypyc/test-data/fixtures/testutil.py b/mypyc/test-data/fixtures/testutil.py index 7b4fcc9fc1ca..7f00ee5aea00 100644 --- a/mypyc/test-data/fixtures/testutil.py +++ b/mypyc/test-data/fixtures/testutil.py @@ -2,10 +2,43 @@ from contextlib import contextmanager from collections.abc import Iterator +import math from typing import ( Any, Iterator, TypeVar, Generator, Optional, List, Tuple, Sequence, Union, Callable, Awaitable, ) +from typing import Final + +FLOAT_MAGIC: Final = -113.0 + +# Various different float values +float_vals = [ + float(n) * 0.25 for n in range(-10, 10) +] + [ + -0.0, + 1.0/3.0, + math.sqrt(2.0), + 1.23e200, + -2.34e200, + 5.43e-100, + -6.532e-200, + float('inf'), + -float('inf'), + float('nan'), + FLOAT_MAGIC, + math.pi, + 2.0 * math.pi, + math.pi / 2.0, + -math.pi / 2.0, + -1.7976931348623158e+308, # Smallest finite value + -2.2250738585072014e-308, # Closest to zero negative normal value + -7.5491e-312, # Arbitrary negative subnormal value + -5e-324, # Closest to zero negative subnormal value + 1.7976931348623158e+308, # Largest finite value + 2.2250738585072014e-308, # Closest to zero positive normal value + -6.3492e-312, # Arbitrary positive subnormal value + 5e-324, # Closest to zero positive subnormal value +] @contextmanager def assertRaises(typ: type, msg: str = '') -> Iterator[None]: @@ -17,6 +50,12 @@ def assertRaises(typ: type, msg: str = '') -> Iterator[None]: else: assert False, f"Expected {typ.__name__} but got no exception" +def assertDomainError() -> Any: + return assertRaises(ValueError, "math domain error") + +def assertMathRangeError() -> Any: + return assertRaises(OverflowError, "math range error") + T = TypeVar('T') U = TypeVar('U') V = TypeVar('V') diff --git a/mypyc/test-data/fixtures/typing-full.pyi b/mypyc/test-data/fixtures/typing-full.pyi index c36b1001106e..52bca09a1dec 100644 --- a/mypyc/test-data/fixtures/typing-full.pyi +++ b/mypyc/test-data/fixtures/typing-full.pyi @@ -125,6 +125,7 @@ class Sequence(Iterable[T_co], Container[T_co]): def __getitem__(self, n: Any) -> T_co: pass class Mapping(Iterable[T], Generic[T, T_co], metaclass=ABCMeta): + def keys(self) -> Iterable[T]: pass # Approximate return type def __getitem__(self, key: T) -> T_co: pass @overload def get(self, k: T) -> Optional[T_co]: pass diff --git a/mypyc/test-data/irbuild-any.test b/mypyc/test-data/irbuild-any.test index 8d4e085179ae..0d14e1a5dfc8 100644 --- a/mypyc/test-data/irbuild-any.test +++ b/mypyc/test-data/irbuild-any.test @@ -51,7 +51,7 @@ def f(a, n, c): r5 :: int r6 :: str r7 :: object - r8 :: int32 + r8 :: i32 r9 :: bit L0: r0 = box(int, n) @@ -99,14 +99,14 @@ def f2(a, n, l): n :: int l :: list r0, r1, r2, r3, r4 :: object - r5 :: int32 + r5 :: i32 r6 :: bit r7 :: object - r8 :: int32 + r8 :: i32 r9, r10 :: bit r11 :: list r12 :: object - r13, r14, r15 :: ptr + r13 :: ptr L0: r0 = box(int, n) r1 = PyObject_GetItem(a, r0) @@ -121,11 +121,9 @@ L0: r10 = CPyList_SetItem(l, n, a) r11 = PyList_New(2) r12 = box(int, n) - r13 = get_element_ptr r11 ob_item :: PyListObject - r14 = load_mem r13 :: ptr* - set_mem r14, a :: builtins.object* - r15 = r14 + WORD_SIZE*1 - set_mem r15, r12 :: builtins.object* + r13 = list_items r11 + buf_init_item r13, 0, a + buf_init_item r13, 1, r12 keep_alive r11 return 1 def f3(a, n): @@ -187,15 +185,14 @@ def f() -> None: def f(): r0, r1 :: object r2, a :: int - r3, r4, b :: float + r3, b :: float L0: r0 = object 1 r1 = PyNumber_Absolute(r0) r2 = unbox(int, r1) a = r2 - r3 = 1.1 - r4 = PyNumber_Absolute(r3) - b = r4 + r3 = fabs(1.1) + b = r3 return 1 [case testFunctionBasedOps] @@ -228,13 +225,12 @@ L0: def f3(): r0, r1, r2, r3 :: object r4 :: int - r5 :: object + r5 :: float L0: r0 = object 2 r1 = object 5 r2 = object 3 r3 = PyNumber_Power(r0, r1, r2) r4 = unbox(int, r3) - r5 = box(int, r4) + r5 = CPyFloat_FromTagged(r4) return r5 - diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index a06977d037b2..11df241b5074 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -76,27 +76,13 @@ def f(x: int, y: int) -> int: [out] def f(x, y): x, y :: int - r0 :: native_int - r1 :: bit - r2 :: native_int - r3, r4, r5 :: bit + r0 :: bit L0: - r0 = x & 1 - r1 = r0 != 0 - if r1 goto L2 else goto L1 :: bool + r0 = int_lt x, y + if r0 goto L1 else goto L2 :: bool L1: - r2 = y & 1 - r3 = r2 != 0 - if r3 goto L2 else goto L3 :: bool -L2: - r4 = CPyTagged_IsLt_(x, y) - if r4 goto L4 else goto L5 :: bool -L3: - r5 = x < y :: signed - if r5 goto L4 else goto L5 :: bool -L4: x = 2 -L5: +L2: return x [case testIfElse] @@ -109,30 +95,16 @@ def f(x: int, y: int) -> int: [out] def f(x, y): x, y :: int - r0 :: native_int - r1 :: bit - r2 :: native_int - r3, r4, r5 :: bit + r0 :: bit L0: - r0 = x & 1 - r1 = r0 != 0 - if r1 goto L2 else goto L1 :: bool + r0 = int_lt x, y + if r0 goto L1 else goto L2 :: bool L1: - r2 = y & 1 - r3 = r2 != 0 - if r3 goto L2 else goto L3 :: bool -L2: - r4 = CPyTagged_IsLt_(x, y) - if r4 goto L4 else goto L5 :: bool -L3: - r5 = x < y :: signed - if r5 goto L4 else goto L5 :: bool -L4: x = 2 - goto L6 -L5: + goto L3 +L2: x = 4 -L6: +L3: return x [case testAnd1] @@ -145,48 +117,19 @@ def f(x: int, y: int) -> int: [out] def f(x, y): x, y :: int - r0 :: native_int - r1 :: bit - r2 :: native_int - r3, r4, r5 :: bit - r6 :: native_int - r7 :: bit - r8 :: native_int - r9, r10, r11 :: bit -L0: - r0 = x & 1 - r1 = r0 != 0 - if r1 goto L2 else goto L1 :: bool + r0, r1 :: bit +L0: + r0 = int_lt x, y + if r0 goto L1 else goto L3 :: bool L1: - r2 = y & 1 - r3 = r2 != 0 - if r3 goto L2 else goto L3 :: bool + r1 = int_gt x, y + if r1 goto L2 else goto L3 :: bool L2: - r4 = CPyTagged_IsLt_(x, y) - if r4 goto L4 else goto L9 :: bool -L3: - r5 = x < y :: signed - if r5 goto L4 else goto L9 :: bool -L4: - r6 = x & 1 - r7 = r6 != 0 - if r7 goto L6 else goto L5 :: bool -L5: - r8 = y & 1 - r9 = r8 != 0 - if r9 goto L6 else goto L7 :: bool -L6: - r10 = CPyTagged_IsLt_(y, x) - if r10 goto L8 else goto L9 :: bool -L7: - r11 = x > y :: signed - if r11 goto L8 else goto L9 :: bool -L8: x = 2 - goto L10 -L9: + goto L4 +L3: x = 4 -L10: +L4: return x [case testAnd2] @@ -221,48 +164,19 @@ def f(x: int, y: int) -> int: [out] def f(x, y): x, y :: int - r0 :: native_int - r1 :: bit - r2 :: native_int - r3, r4, r5 :: bit - r6 :: native_int - r7 :: bit - r8 :: native_int - r9, r10, r11 :: bit -L0: - r0 = x & 1 - r1 = r0 != 0 - if r1 goto L2 else goto L1 :: bool + r0, r1 :: bit +L0: + r0 = int_lt x, y + if r0 goto L2 else goto L1 :: bool L1: - r2 = y & 1 - r3 = r2 != 0 - if r3 goto L2 else goto L3 :: bool + r1 = int_gt x, y + if r1 goto L2 else goto L3 :: bool L2: - r4 = CPyTagged_IsLt_(x, y) - if r4 goto L8 else goto L4 :: bool -L3: - r5 = x < y :: signed - if r5 goto L8 else goto L4 :: bool -L4: - r6 = x & 1 - r7 = r6 != 0 - if r7 goto L6 else goto L5 :: bool -L5: - r8 = y & 1 - r9 = r8 != 0 - if r9 goto L6 else goto L7 :: bool -L6: - r10 = CPyTagged_IsLt_(y, x) - if r10 goto L8 else goto L9 :: bool -L7: - r11 = x > y :: signed - if r11 goto L8 else goto L9 :: bool -L8: x = 2 - goto L10 -L9: + goto L4 +L3: x = 4 -L10: +L4: return x [case testOr2] @@ -295,27 +209,13 @@ def f(x: int, y: int) -> int: [out] def f(x, y): x, y :: int - r0 :: native_int - r1 :: bit - r2 :: native_int - r3, r4, r5 :: bit + r0 :: bit L0: - r0 = x & 1 - r1 = r0 != 0 - if r1 goto L2 else goto L1 :: bool + r0 = int_lt x, y + if r0 goto L2 else goto L1 :: bool L1: - r2 = y & 1 - r3 = r2 != 0 - if r3 goto L2 else goto L3 :: bool -L2: - r4 = CPyTagged_IsLt_(x, y) - if r4 goto L5 else goto L4 :: bool -L3: - r5 = x < y :: signed - if r5 goto L5 else goto L4 :: bool -L4: x = 2 -L5: +L2: return x [case testNotAnd] @@ -326,45 +226,16 @@ def f(x: int, y: int) -> int: [out] def f(x, y): x, y :: int - r0 :: native_int - r1 :: bit - r2 :: native_int - r3, r4, r5 :: bit - r6 :: native_int - r7 :: bit - r8 :: native_int - r9, r10, r11 :: bit -L0: - r0 = x & 1 - r1 = r0 != 0 - if r1 goto L2 else goto L1 :: bool + r0, r1 :: bit +L0: + r0 = int_lt x, y + if r0 goto L1 else goto L2 :: bool L1: - r2 = y & 1 - r3 = r2 != 0 - if r3 goto L2 else goto L3 :: bool + r1 = int_gt x, y + if r1 goto L3 else goto L2 :: bool L2: - r4 = CPyTagged_IsLt_(x, y) - if r4 goto L4 else goto L8 :: bool -L3: - r5 = x < y :: signed - if r5 goto L4 else goto L8 :: bool -L4: - r6 = x & 1 - r7 = r6 != 0 - if r7 goto L6 else goto L5 :: bool -L5: - r8 = y & 1 - r9 = r8 != 0 - if r9 goto L6 else goto L7 :: bool -L6: - r10 = CPyTagged_IsLt_(y, x) - if r10 goto L9 else goto L8 :: bool -L7: - r11 = x > y :: signed - if r11 goto L9 else goto L8 :: bool -L8: x = 2 -L9: +L3: return x [case testWhile] @@ -375,31 +246,17 @@ def f(x: int, y: int) -> int: [out] def f(x, y): x, y :: int - r0 :: native_int - r1 :: bit - r2 :: native_int - r3, r4, r5 :: bit - r6 :: int + r0 :: bit + r1 :: int L0: L1: - r0 = x & 1 - r1 = r0 != 0 - if r1 goto L3 else goto L2 :: bool + r0 = int_gt x, y + if r0 goto L2 else goto L3 :: bool L2: - r2 = y & 1 - r3 = r2 != 0 - if r3 goto L3 else goto L4 :: bool -L3: - r4 = CPyTagged_IsLt_(y, x) - if r4 goto L5 else goto L6 :: bool -L4: - r5 = x > y :: signed - if r5 goto L5 else goto L6 :: bool -L5: - r6 = CPyTagged_Subtract(x, y) - x = r6 + r1 = CPyTagged_Subtract(x, y) + x = r1 goto L1 -L6: +L3: return x [case testWhile2] @@ -411,32 +268,18 @@ def f(x: int, y: int) -> int: [out] def f(x, y): x, y :: int - r0 :: native_int - r1 :: bit - r2 :: native_int - r3, r4, r5 :: bit - r6 :: int + r0 :: bit + r1 :: int L0: x = 2 L1: - r0 = x & 1 - r1 = r0 != 0 - if r1 goto L3 else goto L2 :: bool + r0 = int_gt x, y + if r0 goto L2 else goto L3 :: bool L2: - r2 = y & 1 - r3 = r2 != 0 - if r3 goto L3 else goto L4 :: bool -L3: - r4 = CPyTagged_IsLt_(y, x) - if r4 goto L5 else goto L6 :: bool -L4: - r5 = x > y :: signed - if r5 goto L5 else goto L6 :: bool -L5: - r6 = CPyTagged_Subtract(x, y) - x = r6 + r1 = CPyTagged_Subtract(x, y) + x = r1 goto L1 -L6: +L3: return x [case testImplicitNoneReturn] @@ -466,30 +309,16 @@ def f(x: int, y: int) -> None: [out] def f(x, y): x, y :: int - r0 :: native_int - r1 :: bit - r2 :: native_int - r3, r4, r5 :: bit + r0 :: bit L0: - r0 = x & 1 - r1 = r0 != 0 - if r1 goto L2 else goto L1 :: bool + r0 = int_lt x, y + if r0 goto L1 else goto L2 :: bool L1: - r2 = y & 1 - r3 = r2 != 0 - if r3 goto L2 else goto L3 :: bool -L2: - r4 = CPyTagged_IsLt_(x, y) - if r4 goto L4 else goto L5 :: bool -L3: - r5 = x < y :: signed - if r5 goto L4 else goto L5 :: bool -L4: x = 2 - goto L6 -L5: + goto L3 +L2: y = 4 -L6: +L3: return 1 [case testRecursion] @@ -501,29 +330,21 @@ def f(n: int) -> int: [out] def f(n): n :: int - r0 :: native_int - r1, r2, r3 :: bit - r4, r5, r6, r7, r8 :: int + r0 :: bit + r1, r2, r3, r4, r5 :: int L0: - r0 = n & 1 - r1 = r0 != 0 - if r1 goto L1 else goto L2 :: bool + r0 = int_le n, 2 + if r0 goto L1 else goto L2 :: bool L1: - r2 = CPyTagged_IsLt_(2, n) - if r2 goto L4 else goto L3 :: bool + return 2 L2: - r3 = n <= 2 :: signed - if r3 goto L3 else goto L4 :: bool + r1 = CPyTagged_Subtract(n, 2) + r2 = f(r1) + r3 = CPyTagged_Subtract(n, 4) + r4 = f(r3) + r5 = CPyTagged_Add(r2, r4) + return r5 L3: - return 2 -L4: - r4 = CPyTagged_Subtract(n, 2) - r5 = f(r4) - r6 = CPyTagged_Subtract(n, 4) - r7 = f(r6) - r8 = CPyTagged_Add(r5, r7) - return r8 -L5: unreachable [case testReportTypeCheckError] @@ -550,33 +371,25 @@ def f(n: int) -> int: [out] def f(n): n :: int - r0 :: native_int - r1, r2, r3 :: bit + r0 :: bit x :: int - r4 :: bit + r1 :: bit L0: - r0 = n & 1 - r1 = r0 != 0 - if r1 goto L1 else goto L2 :: bool + r0 = int_lt n, 0 + if r0 goto L1 else goto L2 :: bool L1: - r2 = CPyTagged_IsLt_(n, 0) - if r2 goto L3 else goto L4 :: bool + x = 2 + goto L6 L2: - r3 = n < 0 :: signed - if r3 goto L3 else goto L4 :: bool + r1 = int_eq n, 0 + if r1 goto L3 else goto L4 :: bool L3: x = 2 - goto L8 + goto L5 L4: - r4 = n == 0 - if r4 goto L5 else goto L6 :: bool + x = 4 L5: - x = 2 - goto L7 L6: - x = 4 -L7: -L8: return x [case testUnaryMinus] @@ -598,7 +411,7 @@ def f(n): r0 :: bit r1 :: int L0: - r0 = n == 0 + r0 = int_eq n, 0 if r0 goto L1 else goto L2 :: bool L1: r1 = 0 @@ -682,14 +495,106 @@ L0: r5 = unbox(int, r4) return r5 -[case testFromImport] -from testmodule import g +[case testImport_toplevel] +import sys +import enum as enum2 +import collections.abc +import collections.abc as abc2 +_ = "filler" +import single +single.hello() + +[file single.py] +def hello() -> None: + print("hello, world") + +[out] +def __top_level__(): + r0, r1 :: object + r2 :: bit + r3 :: str + r4 :: object + r5, r6, r7, r8 :: object_ptr + r9 :: object_ptr[4] + r10 :: c_ptr + r11 :: native_int[4] + r12 :: c_ptr + r13 :: object + r14 :: dict + r15, r16 :: str + r17 :: bit + r18 :: str + r19 :: dict + r20 :: str + r21 :: i32 + r22 :: bit + r23 :: object_ptr + r24 :: object_ptr[1] + r25 :: c_ptr + r26 :: native_int[1] + r27 :: c_ptr + r28 :: object + r29 :: dict + r30, r31 :: str + r32 :: bit + r33 :: object + r34 :: str + r35, r36 :: object +L0: + r0 = builtins :: module + r1 = load_address _Py_NoneStruct + r2 = r0 != r1 + if r2 goto L2 else goto L1 :: bool +L1: + r3 = 'builtins' + r4 = PyImport_Import(r3) + builtins = r4 :: module +L2: + r5 = load_address sys :: module + r6 = load_address enum :: module + r7 = load_address collections.abc :: module + r8 = load_address collections.abc :: module + r9 = [r5, r6, r7, r8] + r10 = load_address r9 + r11 = [1, 2, 3, 4] + r12 = load_address r11 + r13 = (('sys', 'sys', 'sys'), ('enum', 'enum', 'enum2'), ('collections.abc', 'collections', 'collections'), ('collections.abc', 'collections.abc', 'abc2')) + r14 = __main__.globals :: static + r15 = 'main' + r16 = '' + r17 = CPyImport_ImportMany(r13, r10, r14, r15, r16, r12) + r18 = 'filler' + r19 = __main__.globals :: static + r20 = '_' + r21 = CPyDict_SetItem(r19, r20, r18) + r22 = r21 >= 0 :: signed + r23 = load_address single :: module + r24 = [r23] + r25 = load_address r24 + r26 = [6] + r27 = load_address r26 + r28 = (('single', 'single', 'single'),) + r29 = __main__.globals :: static + r30 = 'main' + r31 = '' + r32 = CPyImport_ImportMany(r28, r25, r29, r30, r31, r27) + r33 = single :: module + r34 = 'hello' + r35 = CPyObject_GetAttr(r33, r34) + r36 = PyObject_CallFunctionObjArgs(r35, 0) + return 1 + +[case testFromImport_toplevel] +from testmodule import g, h +from testmodule import h as two def f(x: int) -> int: - return g(x) + return g(x) + h() + two() [file testmodule.py] def g(x: int) -> int: return x + 1 +def h() -> int: + return 2 [out] def f(x): x :: int @@ -697,6 +602,14 @@ def f(x): r1 :: str r2, r3, r4 :: object r5 :: int + r6 :: dict + r7 :: str + r8, r9 :: object + r10, r11 :: int + r12 :: dict + r13 :: str + r14, r15 :: object + r16, r17 :: int L0: r0 = __main__.globals :: static r1 = 'g' @@ -704,7 +617,52 @@ L0: r3 = box(int, x) r4 = PyObject_CallFunctionObjArgs(r2, r3, 0) r5 = unbox(int, r4) - return r5 + r6 = __main__.globals :: static + r7 = 'h' + r8 = CPyDict_GetItem(r6, r7) + r9 = PyObject_CallFunctionObjArgs(r8, 0) + r10 = unbox(int, r9) + r11 = CPyTagged_Add(r5, r10) + r12 = __main__.globals :: static + r13 = 'two' + r14 = CPyDict_GetItem(r12, r13) + r15 = PyObject_CallFunctionObjArgs(r14, 0) + r16 = unbox(int, r15) + r17 = CPyTagged_Add(r11, r16) + return r17 +def __top_level__(): + r0, r1 :: object + r2 :: bit + r3 :: str + r4, r5 :: object + r6 :: str + r7 :: dict + r8, r9, r10 :: object + r11 :: str + r12 :: dict + r13 :: object +L0: + r0 = builtins :: module + r1 = load_address _Py_NoneStruct + r2 = r0 != r1 + if r2 goto L2 else goto L1 :: bool +L1: + r3 = 'builtins' + r4 = PyImport_Import(r3) + builtins = r4 :: module +L2: + r5 = ('g', 'h') + r6 = 'testmodule' + r7 = __main__.globals :: static + r8 = CPyImport_ImportFromMany(r6, r5, r5, r7) + testmodule = r8 :: module + r9 = ('h',) + r10 = ('two',) + r11 = 'testmodule' + r12 = __main__.globals :: static + r13 = CPyImport_ImportFromMany(r11, r9, r10, r12) + testmodule = r13 :: module + return 1 [case testPrintFullname] import builtins @@ -803,21 +761,20 @@ def g(y): r0 :: None r1 :: list r2 :: object - r3, r4 :: ptr - r5 :: None - r6 :: object - r7 :: None + r3 :: ptr + r4 :: None + r5 :: object + r6 :: None L0: r0 = g(y) r1 = PyList_New(1) r2 = object 1 - r3 = get_element_ptr r1 ob_item :: PyListObject - r4 = load_mem r3 :: ptr* - set_mem r4, r2 :: builtins.object* + r3 = list_items r1 + buf_init_item r3, 0, r2 keep_alive r1 - r5 = g(r1) - r6 = box(None, 1) - r7 = g(r6) + r4 = g(r1) + r5 = box(None, 1) + r6 = g(r5) return 1 [case testCoerceToObject1] @@ -831,28 +788,27 @@ def g(y: object) -> object: def g(y): y, r0, r1 :: object r2 :: list - r3, r4 :: ptr + r3 :: ptr a :: list - r5 :: tuple[int, int] - r6 :: object - r7 :: bit - r8, r9 :: object + r4 :: tuple[int, int] + r5 :: object + r6 :: bit + r7, r8 :: object L0: r0 = object 1 r1 = g(r0) r2 = PyList_New(1) - r3 = get_element_ptr r2 ob_item :: PyListObject - r4 = load_mem r3 :: ptr* - set_mem r4, y :: builtins.object* + r3 = list_items r2 + buf_init_item r3, 0, y keep_alive r2 a = r2 - r5 = (2, 4) - r6 = box(tuple[int, int], r5) - r7 = CPyList_SetItem(a, 0, r6) - r8 = box(bool, 1) - y = r8 - r9 = object 3 - return r9 + r4 = (2, 4) + r5 = box(tuple[int, int], r4) + r6 = CPyList_SetItem(a, 0, r5) + r7 = box(bool, 1) + y = r7 + r8 = object 3 + return r8 [case testCoerceToObject2] class A: @@ -1001,7 +957,7 @@ def f(x: Any, y: Any, z: Any) -> None: [out] def f(x, y, z): x, y, z :: object - r0 :: int32 + r0 :: i32 r1 :: bit L0: r0 = PyObject_SetItem(x, y, z) @@ -1016,35 +972,27 @@ def assign_and_return_float_sum() -> float: return f1 * f2 + f3 [out] def assign_and_return_float_sum(): - r0, f1, r1, f2, r2, f3 :: float - r3 :: object - r4 :: float - r5 :: object - r6 :: float -L0: - r0 = 1.0 - f1 = r0 - r1 = 2.0 - f2 = r1 - r2 = 3.0 - f3 = r2 - r3 = PyNumber_Multiply(f1, f2) - r4 = cast(float, r3) - r5 = PyNumber_Add(r4, f3) - r6 = cast(float, r5) - return r6 + f1, f2, f3, r0, r1 :: float +L0: + f1 = 1.0 + f2 = 2.0 + f3 = 3.0 + r0 = f1 * f2 + r1 = r0 + f3 + return r1 [case testLoadComplex] def load() -> complex: - return 5j+1.0 + real = 1 + return 5j+real [out] def load(): - r0 :: object - r1 :: float - r2 :: object + real :: int + r0, r1, r2 :: object L0: + real = 2 r0 = 5j - r1 = 1.0 + r1 = box(int, real) r2 = PyNumber_Add(r0, r1) return r2 @@ -1135,27 +1083,19 @@ def f(x: int) -> int: [out] def absolute_value(x): x :: int - r0 :: native_int - r1, r2, r3 :: bit - r4, r5 :: int + r0 :: bit + r1, r2 :: int L0: - r0 = x & 1 - r1 = r0 != 0 - if r1 goto L1 else goto L2 :: bool + r0 = int_gt x, 0 + if r0 goto L1 else goto L2 :: bool L1: - r2 = CPyTagged_IsLt_(0, x) - if r2 goto L3 else goto L4 :: bool + r1 = x + goto L3 L2: - r3 = x > 0 :: signed - if r3 goto L3 else goto L4 :: bool + r2 = CPyTagged_Negate(x) + r1 = r2 L3: - r4 = x - goto L5 -L4: - r5 = CPyTagged_Negate(x) - r4 = r5 -L5: - return r4 + return r1 def call_native_function(x): x, r0 :: int L0: @@ -1176,10 +1116,8 @@ L0: r5 = unbox(int, r4) return r5 def return_float(): - r0 :: float L0: - r0 = 5.0 - return r0 + return 5.0 def return_callable_type(): r0 :: dict r1 :: str @@ -1196,7 +1134,7 @@ L0: r0 = return_callable_type() f = r0 r1 = PyObject_CallFunctionObjArgs(f, 0) - r2 = cast(float, r1) + r2 = unbox(float, r1) return r2 [case testCallableTypesWithKeywordArgs] @@ -1290,13 +1228,13 @@ def lst(x: List[int]) -> int: [out] def obj(x): x :: object - r0 :: int32 + r0 :: i32 r1 :: bit r2 :: bool L0: r0 = PyObject_IsTrue(x) r1 = r0 >= 0 :: signed - r2 = truncate r0: int32 to builtins.bool + r2 = truncate r0: i32 to builtins.bool if r2 goto L1 else goto L2 :: bool L1: return 2 @@ -1318,17 +1256,14 @@ L3: unreachable def lst(x): x :: list - r0 :: ptr - r1 :: native_int - r2 :: short_int - r3 :: bit + r0 :: native_int + r1 :: short_int + r2 :: bit L0: - r0 = get_element_ptr x ob_size :: PyVarObject - r1 = load_mem r0 :: native_int* - keep_alive x - r2 = r1 << 1 - r3 = r2 != 0 - if r3 goto L1 else goto L2 :: bool + r0 = var_object_size x + r1 = r0 << 1 + r2 = int_ne r1, 0 + if r2 goto L1 else goto L2 :: bool L1: return 2 L2: @@ -1398,7 +1333,7 @@ def opt_o(x): r0 :: object r1 :: bit r2 :: object - r3 :: int32 + r3 :: i32 r4 :: bit r5 :: bool L0: @@ -1409,7 +1344,7 @@ L1: r2 = cast(object, x) r3 = PyObject_IsTrue(r2) r4 = r3 >= 0 :: signed - r5 = truncate r3: int32 to builtins.bool + r5 = truncate r3: i32 to builtins.bool if r5 goto L2 else goto L3 :: bool L2: return 2 @@ -1481,7 +1416,7 @@ def __top_level__(): r5 :: dict r6 :: str r7 :: object - r8 :: int32 + r8 :: i32 r9 :: bit r10 :: dict r11 :: str @@ -1596,7 +1531,7 @@ def main() -> None: def foo(x): x :: union[int, str] r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: bool r4 :: __main__.B @@ -1605,7 +1540,7 @@ L0: r0 = load_address PyLong_Type r1 = PyObject_IsInstance(x, r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool if r3 goto L1 else goto L2 :: bool L1: r4 = B() @@ -1721,12 +1656,12 @@ def h(): r3 :: object r4 :: list r5 :: object - r6, r7 :: ptr - r8, r9 :: object - r10 :: tuple - r11 :: dict - r12 :: object - r13 :: tuple[int, int, int] + r6 :: ptr + r7, r8 :: object + r9 :: tuple + r10 :: dict + r11 :: object + r12 :: tuple[int, int, int] L0: r0 = (4, 6) r1 = __main__.globals :: static @@ -1734,17 +1669,16 @@ L0: r3 = CPyDict_GetItem(r1, r2) r4 = PyList_New(1) r5 = object 1 - r6 = get_element_ptr r4 ob_item :: PyListObject - r7 = load_mem r6 :: ptr* - set_mem r7, r5 :: builtins.object* + r6 = list_items r4 + buf_init_item r6, 0, r5 keep_alive r4 - r8 = box(tuple[int, int], r0) - r9 = CPyList_Extend(r4, r8) - r10 = PyList_AsTuple(r4) - r11 = PyDict_New() - r12 = PyObject_Call(r3, r10, r11) - r13 = unbox(tuple[int, int, int], r12) - return r13 + r7 = box(tuple[int, int], r0) + r8 = CPyList_Extend(r4, r7) + r9 = PyList_AsTuple(r4) + r10 = PyDict_New() + r11 = PyObject_Call(r3, r9, r10) + r12 = unbox(tuple[int, int, int], r11) + return r12 [case testStar2Args] from typing import Tuple @@ -1768,7 +1702,7 @@ def g(): r8 :: str r9 :: object r10 :: dict - r11 :: int32 + r11 :: i32 r12 :: bit r13 :: tuple r14 :: object @@ -1798,7 +1732,7 @@ def h(): r6 :: str r7 :: object r8 :: dict - r9 :: int32 + r9 :: i32 r10 :: bit r11 :: object r12 :: tuple @@ -1909,94 +1843,59 @@ def f() -> List[int]: def f(): r0, r1 :: list r2, r3, r4 :: object - r5, r6, r7, r8 :: ptr - r9 :: short_int - r10 :: ptr - r11 :: native_int - r12 :: short_int - r13 :: bit - r14 :: object - r15, x :: int - r16 :: native_int - r17, r18 :: bit - r19 :: bool - r20, r21 :: bit - r22 :: native_int - r23, r24 :: bit - r25 :: bool - r26, r27 :: bit - r28 :: int - r29 :: object - r30 :: int32 - r31 :: bit - r32 :: short_int + r5 :: ptr + r6 :: short_int + r7 :: native_int + r8 :: short_int + r9 :: bit + r10 :: object + r11, x :: int + r12, r13 :: bit + r14 :: int + r15 :: object + r16 :: i32 + r17 :: bit + r18 :: short_int L0: r0 = PyList_New(0) r1 = PyList_New(3) r2 = object 1 r3 = object 2 r4 = object 3 - r5 = get_element_ptr r1 ob_item :: PyListObject - r6 = load_mem r5 :: ptr* - set_mem r6, r2 :: builtins.object* - r7 = r6 + WORD_SIZE*1 - set_mem r7, r3 :: builtins.object* - r8 = r6 + WORD_SIZE*2 - set_mem r8, r4 :: builtins.object* + r5 = list_items r1 + buf_init_item r5, 0, r2 + buf_init_item r5, 1, r3 + buf_init_item r5, 2, r4 keep_alive r1 - r9 = 0 + r6 = 0 L1: - r10 = get_element_ptr r1 ob_size :: PyVarObject - r11 = load_mem r10 :: native_int* - keep_alive r1 - r12 = r11 << 1 - r13 = r9 < r12 :: signed - if r13 goto L2 else goto L14 :: bool + r7 = var_object_size r1 + r8 = r7 << 1 + r9 = int_lt r6, r8 + if r9 goto L2 else goto L8 :: bool L2: - r14 = CPyList_GetItemUnsafe(r1, r9) - r15 = unbox(int, r14) - x = r15 - r16 = x & 1 - r17 = r16 == 0 - if r17 goto L3 else goto L4 :: bool + r10 = CPyList_GetItemUnsafe(r1, r6) + r11 = unbox(int, r10) + x = r11 + r12 = int_ne x, 4 + if r12 goto L4 else goto L3 :: bool L3: - r18 = x != 4 - r19 = r18 - goto L5 + goto L7 L4: - r20 = CPyTagged_IsEq_(x, 4) - r21 = r20 ^ 1 - r19 = r21 + r13 = int_ne x, 6 + if r13 goto L6 else goto L5 :: bool L5: - if r19 goto L7 else goto L6 :: bool + goto L7 L6: - goto L13 + r14 = CPyTagged_Multiply(x, x) + r15 = box(int, r14) + r16 = PyList_Append(r0, r15) + r17 = r16 >= 0 :: signed L7: - r22 = x & 1 - r23 = r22 == 0 - if r23 goto L8 else goto L9 :: bool -L8: - r24 = x != 6 - r25 = r24 - goto L10 -L9: - r26 = CPyTagged_IsEq_(x, 6) - r27 = r26 ^ 1 - r25 = r27 -L10: - if r25 goto L12 else goto L11 :: bool -L11: - goto L13 -L12: - r28 = CPyTagged_Multiply(x, x) - r29 = box(int, r28) - r30 = PyList_Append(r0, r29) - r31 = r30 >= 0 :: signed -L13: - r32 = r9 + 2 - r9 = r32 + r18 = r6 + 2 + r6 = r18 goto L1 -L14: +L8: return r0 [case testDictComprehension] @@ -2008,95 +1907,60 @@ def f(): r0 :: dict r1 :: list r2, r3, r4 :: object - r5, r6, r7, r8 :: ptr - r9 :: short_int - r10 :: ptr - r11 :: native_int - r12 :: short_int - r13 :: bit - r14 :: object - r15, x :: int - r16 :: native_int - r17, r18 :: bit - r19 :: bool - r20, r21 :: bit - r22 :: native_int - r23, r24 :: bit - r25 :: bool - r26, r27 :: bit - r28 :: int - r29, r30 :: object - r31 :: int32 - r32 :: bit - r33 :: short_int + r5 :: ptr + r6 :: short_int + r7 :: native_int + r8 :: short_int + r9 :: bit + r10 :: object + r11, x :: int + r12, r13 :: bit + r14 :: int + r15, r16 :: object + r17 :: i32 + r18 :: bit + r19 :: short_int L0: r0 = PyDict_New() r1 = PyList_New(3) r2 = object 1 r3 = object 2 r4 = object 3 - r5 = get_element_ptr r1 ob_item :: PyListObject - r6 = load_mem r5 :: ptr* - set_mem r6, r2 :: builtins.object* - r7 = r6 + WORD_SIZE*1 - set_mem r7, r3 :: builtins.object* - r8 = r6 + WORD_SIZE*2 - set_mem r8, r4 :: builtins.object* + r5 = list_items r1 + buf_init_item r5, 0, r2 + buf_init_item r5, 1, r3 + buf_init_item r5, 2, r4 keep_alive r1 - r9 = 0 + r6 = 0 L1: - r10 = get_element_ptr r1 ob_size :: PyVarObject - r11 = load_mem r10 :: native_int* - keep_alive r1 - r12 = r11 << 1 - r13 = r9 < r12 :: signed - if r13 goto L2 else goto L14 :: bool + r7 = var_object_size r1 + r8 = r7 << 1 + r9 = int_lt r6, r8 + if r9 goto L2 else goto L8 :: bool L2: - r14 = CPyList_GetItemUnsafe(r1, r9) - r15 = unbox(int, r14) - x = r15 - r16 = x & 1 - r17 = r16 == 0 - if r17 goto L3 else goto L4 :: bool + r10 = CPyList_GetItemUnsafe(r1, r6) + r11 = unbox(int, r10) + x = r11 + r12 = int_ne x, 4 + if r12 goto L4 else goto L3 :: bool L3: - r18 = x != 4 - r19 = r18 - goto L5 + goto L7 L4: - r20 = CPyTagged_IsEq_(x, 4) - r21 = r20 ^ 1 - r19 = r21 + r13 = int_ne x, 6 + if r13 goto L6 else goto L5 :: bool L5: - if r19 goto L7 else goto L6 :: bool + goto L7 L6: - goto L13 + r14 = CPyTagged_Multiply(x, x) + r15 = box(int, x) + r16 = box(int, r14) + r17 = CPyDict_SetItem(r0, r15, r16) + r18 = r17 >= 0 :: signed L7: - r22 = x & 1 - r23 = r22 == 0 - if r23 goto L8 else goto L9 :: bool -L8: - r24 = x != 6 - r25 = r24 - goto L10 -L9: - r26 = CPyTagged_IsEq_(x, 6) - r27 = r26 ^ 1 - r25 = r27 -L10: - if r25 goto L12 else goto L11 :: bool -L11: - goto L13 -L12: - r28 = CPyTagged_Multiply(x, x) - r29 = box(int, x) - r30 = box(int, r28) - r31 = CPyDict_SetItem(r0, r29, r30) - r32 = r31 >= 0 :: signed -L13: - r33 = r9 + 2 - r9 = r33 + r19 = r6 + 2 + r6 = r19 goto L1 -L14: +L8: return r0 [case testLoopsMultipleAssign] @@ -2109,82 +1973,73 @@ def f(l: List[Tuple[int, int, int]]) -> List[int]: def f(l): l :: list r0 :: short_int - r1 :: ptr - r2 :: native_int - r3 :: short_int - r4 :: bit - r5 :: object - r6 :: tuple[int, int, int] - r7, x, r8, y, r9, z :: int - r10 :: short_int - r11 :: ptr - r12 :: native_int - r13 :: list + r1 :: native_int + r2 :: short_int + r3 :: bit + r4 :: object + r5 :: tuple[int, int, int] + r6, x, r7, y, r8, z :: int + r9 :: short_int + r10 :: native_int + r11 :: list + r12 :: short_int + r13 :: native_int r14 :: short_int - r15 :: ptr - r16 :: native_int - r17 :: short_int - r18 :: bit - r19 :: object - r20 :: tuple[int, int, int] - r21, x_2, r22, y_2, r23, z_2, r24, r25 :: int - r26 :: object - r27 :: bit - r28 :: short_int + r15 :: bit + r16 :: object + r17 :: tuple[int, int, int] + r18, x_2, r19, y_2, r20, z_2, r21, r22 :: int + r23 :: object + r24 :: bit + r25 :: short_int L0: r0 = 0 L1: - r1 = get_element_ptr l ob_size :: PyVarObject - r2 = load_mem r1 :: native_int* - keep_alive l - r3 = r2 << 1 - r4 = r0 < r3 :: signed - if r4 goto L2 else goto L4 :: bool + r1 = var_object_size l + r2 = r1 << 1 + r3 = int_lt r0, r2 + if r3 goto L2 else goto L4 :: bool L2: - r5 = CPyList_GetItemUnsafe(l, r0) - r6 = unbox(tuple[int, int, int], r5) - r7 = r6[0] - x = r7 - r8 = r6[1] - y = r8 - r9 = r6[2] - z = r9 + r4 = CPyList_GetItemUnsafe(l, r0) + r5 = unbox(tuple[int, int, int], r4) + r6 = r5[0] + x = r6 + r7 = r5[1] + y = r7 + r8 = r5[2] + z = r8 L3: - r10 = r0 + 2 - r0 = r10 + r9 = r0 + 2 + r0 = r9 goto L1 L4: - r11 = get_element_ptr l ob_size :: PyVarObject - r12 = load_mem r11 :: native_int* - keep_alive l - r13 = PyList_New(r12) - r14 = 0 + r10 = var_object_size l + r11 = PyList_New(r10) + r12 = 0 L5: - r15 = get_element_ptr l ob_size :: PyVarObject - r16 = load_mem r15 :: native_int* - keep_alive l - r17 = r16 << 1 - r18 = r14 < r17 :: signed - if r18 goto L6 else goto L8 :: bool + r13 = var_object_size l + r14 = r13 << 1 + r15 = int_lt r12, r14 + if r15 goto L6 else goto L8 :: bool L6: - r19 = CPyList_GetItemUnsafe(l, r14) - r20 = unbox(tuple[int, int, int], r19) - r21 = r20[0] - x_2 = r21 - r22 = r20[1] - y_2 = r22 - r23 = r20[2] - z_2 = r23 - r24 = CPyTagged_Add(x_2, y_2) - r25 = CPyTagged_Add(r24, z_2) - r26 = box(int, r25) - r27 = CPyList_SetItemUnsafe(r13, r14, r26) + r16 = CPyList_GetItemUnsafe(l, r12) + r17 = unbox(tuple[int, int, int], r16) + r18 = r17[0] + x_2 = r18 + r19 = r17[1] + y_2 = r19 + r20 = r17[2] + z_2 = r20 + r21 = CPyTagged_Add(x_2, y_2) + r22 = CPyTagged_Add(r21, z_2) + r23 = box(int, r22) + r24 = CPyList_SetItemUnsafe(r11, r12, r23) L7: - r28 = r14 + 2 - r14 = r28 + r25 = r12 + 2 + r12 = r25 goto L5 L8: - return r13 + return r11 [case testProperty] class PropertyHolder: @@ -2276,79 +2131,61 @@ def __top_level__(): r0, r1 :: object r2 :: bit r3 :: str - r4 :: object - r5 :: dict - r6, r7, r8 :: str - r9 :: list - r10, r11, r12, r13 :: ptr + r4, r5 :: object + r6 :: str + r7 :: dict + r8 :: object + r9, r10 :: str + r11 :: object + r12 :: tuple[str, object] + r13 :: object r14 :: str r15 :: object - r16, r17, r18 :: str + r16 :: tuple[str, object] + r17 :: object + r18 :: tuple[object, object] r19 :: object - r20 :: str - r21 :: int32 - r22 :: bit - r23, r24, r25 :: str - r26 :: object - r27 :: str - r28 :: int32 - r29 :: bit - r30, r31, r32 :: str - r33 :: object - r34 :: str - r35 :: int32 - r36 :: bit - r37, r38 :: str - r39 :: object - r40 :: tuple[str, object] - r41 :: object - r42 :: str - r43 :: object - r44 :: tuple[str, object] - r45 :: object - r46 :: tuple[object, object] - r47 :: object - r48 :: dict - r49 :: str - r50, r51 :: object + r20 :: dict + r21 :: str + r22, r23 :: object + r24 :: dict + r25 :: str + r26 :: i32 + r27 :: bit + r28 :: str + r29 :: dict + r30 :: str + r31, r32, r33 :: object + r34 :: tuple + r35 :: dict + r36 :: str + r37 :: i32 + r38 :: bit + r39 :: dict + r40 :: str + r41, r42, r43 :: object + r44 :: dict + r45 :: str + r46 :: i32 + r47 :: bit + r48 :: str + r49 :: dict + r50 :: str + r51 :: object r52 :: dict r53 :: str - r54 :: int32 - r55 :: bit - r56 :: str - r57 :: dict - r58 :: str - r59, r60, r61 :: object - r62 :: tuple - r63 :: dict - r64 :: str - r65 :: int32 - r66 :: bit - r67 :: dict - r68 :: str - r69, r70, r71 :: object - r72 :: dict - r73 :: str - r74 :: int32 - r75 :: bit - r76 :: str - r77 :: dict - r78 :: str - r79 :: object - r80 :: dict - r81 :: str - r82, r83 :: object - r84 :: dict - r85 :: str - r86 :: int32 - r87 :: bit - r88 :: list - r89, r90, r91 :: object - r92, r93, r94, r95 :: ptr - r96 :: dict - r97 :: str - r98 :: int32 - r99 :: bit + r54, r55 :: object + r56 :: dict + r57 :: str + r58 :: i32 + r59 :: bit + r60 :: list + r61, r62, r63 :: object + r64 :: ptr + r65 :: dict + r66 :: str + r67 :: i32 + r68 :: bit L0: r0 = builtins :: module r1 = load_address _Py_NoneStruct @@ -2359,110 +2196,75 @@ L1: r4 = PyImport_Import(r3) builtins = r4 :: module L2: - r5 = __main__.globals :: static - r6 = 'List' - r7 = 'NewType' - r8 = 'NamedTuple' - r9 = PyList_New(3) - r10 = get_element_ptr r9 ob_item :: PyListObject - r11 = load_mem r10 :: ptr* - set_mem r11, r6 :: builtins.object* - r12 = r11 + WORD_SIZE*1 - set_mem r12, r7 :: builtins.object* - r13 = r11 + WORD_SIZE*2 - set_mem r13, r8 :: builtins.object* - keep_alive r9 - r14 = 'typing' - r15 = PyImport_ImportModuleLevelObject(r14, r5, 0, r9, 0) - typing = r15 :: module - r16 = 'typing' - r17 = 'List' - r18 = 'List' - r19 = CPyImport_ImportFrom(r15, r16, r17, r18) - r20 = 'List' - r21 = CPyDict_SetItem(r5, r20, r19) - r22 = r21 >= 0 :: signed - r23 = 'typing' - r24 = 'NewType' - r25 = 'NewType' - r26 = CPyImport_ImportFrom(r15, r23, r24, r25) - r27 = 'NewType' - r28 = CPyDict_SetItem(r5, r27, r26) - r29 = r28 >= 0 :: signed - r30 = 'typing' - r31 = 'NamedTuple' - r32 = 'NamedTuple' - r33 = CPyImport_ImportFrom(r15, r30, r31, r32) - r34 = 'NamedTuple' - r35 = CPyDict_SetItem(r5, r34, r33) - r36 = r35 >= 0 :: signed - r37 = 'Lol' - r38 = 'a' - r39 = load_address PyLong_Type - r40 = (r38, r39) - r41 = box(tuple[str, object], r40) - r42 = 'b' - r43 = load_address PyUnicode_Type - r44 = (r42, r43) - r45 = box(tuple[str, object], r44) - r46 = (r41, r45) - r47 = box(tuple[object, object], r46) - r48 = __main__.globals :: static - r49 = 'NamedTuple' - r50 = CPyDict_GetItem(r48, r49) - r51 = PyObject_CallFunctionObjArgs(r50, r37, r47, 0) + r5 = ('List', 'NewType', 'NamedTuple') + r6 = 'typing' + r7 = __main__.globals :: static + r8 = CPyImport_ImportFromMany(r6, r5, r5, r7) + typing = r8 :: module + r9 = 'Lol' + r10 = 'a' + r11 = load_address PyLong_Type + r12 = (r10, r11) + r13 = box(tuple[str, object], r12) + r14 = 'b' + r15 = load_address PyUnicode_Type + r16 = (r14, r15) + r17 = box(tuple[str, object], r16) + r18 = (r13, r17) + r19 = box(tuple[object, object], r18) + r20 = __main__.globals :: static + r21 = 'NamedTuple' + r22 = CPyDict_GetItem(r20, r21) + r23 = PyObject_CallFunctionObjArgs(r22, r9, r19, 0) + r24 = __main__.globals :: static + r25 = 'Lol' + r26 = CPyDict_SetItem(r24, r25, r23) + r27 = r26 >= 0 :: signed + r28 = '' + r29 = __main__.globals :: static + r30 = 'Lol' + r31 = CPyDict_GetItem(r29, r30) + r32 = object 1 + r33 = PyObject_CallFunctionObjArgs(r31, r32, r28, 0) + r34 = cast(tuple, r33) + r35 = __main__.globals :: static + r36 = 'x' + r37 = CPyDict_SetItem(r35, r36, r34) + r38 = r37 >= 0 :: signed + r39 = __main__.globals :: static + r40 = 'List' + r41 = CPyDict_GetItem(r39, r40) + r42 = load_address PyLong_Type + r43 = PyObject_GetItem(r41, r42) + r44 = __main__.globals :: static + r45 = 'Foo' + r46 = CPyDict_SetItem(r44, r45, r43) + r47 = r46 >= 0 :: signed + r48 = 'Bar' + r49 = __main__.globals :: static + r50 = 'Foo' + r51 = CPyDict_GetItem(r49, r50) r52 = __main__.globals :: static - r53 = 'Lol' - r54 = CPyDict_SetItem(r52, r53, r51) - r55 = r54 >= 0 :: signed - r56 = '' - r57 = __main__.globals :: static - r58 = 'Lol' - r59 = CPyDict_GetItem(r57, r58) - r60 = object 1 - r61 = PyObject_CallFunctionObjArgs(r59, r60, r56, 0) - r62 = cast(tuple, r61) - r63 = __main__.globals :: static - r64 = 'x' - r65 = CPyDict_SetItem(r63, r64, r62) - r66 = r65 >= 0 :: signed - r67 = __main__.globals :: static - r68 = 'List' - r69 = CPyDict_GetItem(r67, r68) - r70 = load_address PyLong_Type - r71 = PyObject_GetItem(r69, r70) - r72 = __main__.globals :: static - r73 = 'Foo' - r74 = CPyDict_SetItem(r72, r73, r71) - r75 = r74 >= 0 :: signed - r76 = 'Bar' - r77 = __main__.globals :: static - r78 = 'Foo' - r79 = CPyDict_GetItem(r77, r78) - r80 = __main__.globals :: static - r81 = 'NewType' - r82 = CPyDict_GetItem(r80, r81) - r83 = PyObject_CallFunctionObjArgs(r82, r76, r79, 0) - r84 = __main__.globals :: static - r85 = 'Bar' - r86 = CPyDict_SetItem(r84, r85, r83) - r87 = r86 >= 0 :: signed - r88 = PyList_New(3) - r89 = object 1 - r90 = object 2 - r91 = object 3 - r92 = get_element_ptr r88 ob_item :: PyListObject - r93 = load_mem r92 :: ptr* - set_mem r93, r89 :: builtins.object* - r94 = r93 + WORD_SIZE*1 - set_mem r94, r90 :: builtins.object* - r95 = r93 + WORD_SIZE*2 - set_mem r95, r91 :: builtins.object* - keep_alive r88 - r96 = __main__.globals :: static - r97 = 'y' - r98 = CPyDict_SetItem(r96, r97, r88) - r99 = r98 >= 0 :: signed + r53 = 'NewType' + r54 = CPyDict_GetItem(r52, r53) + r55 = PyObject_CallFunctionObjArgs(r54, r48, r51, 0) + r56 = __main__.globals :: static + r57 = 'Bar' + r58 = CPyDict_SetItem(r56, r57, r55) + r59 = r58 >= 0 :: signed + r60 = PyList_New(3) + r61 = object 1 + r62 = object 2 + r63 = object 3 + r64 = list_items r60 + buf_init_item r64, 0, r61 + buf_init_item r64, 1, r62 + buf_init_item r64, 2, r63 + keep_alive r60 + r65 = __main__.globals :: static + r66 = 'y' + r67 = CPyDict_SetItem(r65, r66, r60) + r68 = r67 >= 0 :: signed return 1 [case testChainedConditional] @@ -2477,60 +2279,24 @@ L0: return x def f(x, y, z): x, y, z, r0, r1 :: int - r2 :: native_int - r3 :: bit - r4 :: native_int - r5, r6, r7 :: bit - r8 :: bool - r9 :: bit - r10 :: bool - r11 :: int - r12 :: native_int - r13 :: bit - r14 :: native_int - r15, r16, r17 :: bit - r18 :: bool - r19 :: bit + r2 :: bit + r3 :: bool + r4 :: int + r5 :: bit L0: r0 = g(x) r1 = g(y) - r2 = r0 & 1 - r3 = r2 == 0 - r4 = r1 & 1 - r5 = r4 == 0 - r6 = r3 & r5 - if r6 goto L1 else goto L2 :: bool + r2 = int_lt r0, r1 + if r2 goto L2 else goto L1 :: bool L1: - r7 = r0 < r1 :: signed - r8 = r7 + r3 = r2 goto L3 L2: - r9 = CPyTagged_IsLt_(r0, r1) - r8 = r9 + r4 = g(z) + r5 = int_gt r1, r4 + r3 = r5 L3: - if r8 goto L5 else goto L4 :: bool -L4: - r10 = r8 - goto L9 -L5: - r11 = g(z) - r12 = r1 & 1 - r13 = r12 == 0 - r14 = r11 & 1 - r15 = r14 == 0 - r16 = r13 & r15 - if r16 goto L6 else goto L7 :: bool -L6: - r17 = r1 > r11 :: signed - r18 = r17 - goto L8 -L7: - r19 = CPyTagged_IsLt_(r11, r1) - r18 = r19 -L8: - r10 = r18 -L9: - return r10 + return r3 [case testEq] class A: @@ -2547,7 +2313,7 @@ def A.__ne__(__mypyc_self__, rhs): __mypyc_self__ :: __main__.A rhs, r0, r1 :: object r2 :: bit - r3 :: int32 + r3 :: i32 r4 :: bit r5 :: bool r6 :: object @@ -2559,7 +2325,7 @@ L0: L1: r3 = PyObject_Not(r0) r4 = r3 >= 0 :: signed - r5 = truncate r3: int32 to builtins.bool + r5 = truncate r3: i32 to builtins.bool r6 = box(bool, r5) return r6 L2: @@ -2609,47 +2375,43 @@ L2: def g_a_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.g_a_obj r0 :: __main__.a_env - r1, g :: object - r2 :: str - r3 :: object - r4 :: str - r5, r6, r7, r8 :: object - r9 :: str - r10 :: object - r11 :: str - r12, r13 :: object + r1 :: str + r2 :: object + r3 :: str + r4, r5, r6, r7 :: object + r8 :: str + r9 :: object + r10 :: str + r11, r12 :: object L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.g - g = r1 - r2 = 'Entering' - r3 = builtins :: module - r4 = 'print' - r5 = CPyObject_GetAttr(r3, r4) - r6 = PyObject_CallFunctionObjArgs(r5, r2, 0) - r7 = r0.f - r8 = PyObject_CallFunctionObjArgs(r7, 0) - r9 = 'Exited' - r10 = builtins :: module - r11 = 'print' - r12 = CPyObject_GetAttr(r10, r11) - r13 = PyObject_CallFunctionObjArgs(r12, r9, 0) + r1 = 'Entering' + r2 = builtins :: module + r3 = 'print' + r4 = CPyObject_GetAttr(r2, r3) + r5 = PyObject_CallFunctionObjArgs(r4, r1, 0) + r6 = r0.f + r7 = PyObject_CallFunctionObjArgs(r6, 0) + r8 = 'Exited' + r9 = builtins :: module + r10 = 'print' + r11 = CPyObject_GetAttr(r9, r10) + r12 = PyObject_CallFunctionObjArgs(r11, r8, 0) return 1 def a(f): f :: object r0 :: __main__.a_env r1 :: bool r2 :: __main__.g_a_obj - r3, r4 :: bool - r5 :: object + r3 :: bool + g :: object L0: r0 = a_env() r0.f = f; r1 = is_error r2 = g_a_obj() r2.__mypyc_env__ = r0; r3 = is_error - r0.g = r2; r4 = is_error - r5 = r0.g - return r5 + g = r2 + return g def g_b_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -2666,47 +2428,43 @@ L2: def g_b_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.g_b_obj r0 :: __main__.b_env - r1, g :: object - r2 :: str - r3 :: object - r4 :: str - r5, r6, r7, r8 :: object - r9 :: str - r10 :: object - r11 :: str - r12, r13 :: object + r1 :: str + r2 :: object + r3 :: str + r4, r5, r6, r7 :: object + r8 :: str + r9 :: object + r10 :: str + r11, r12 :: object L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.g - g = r1 - r2 = '---' - r3 = builtins :: module - r4 = 'print' - r5 = CPyObject_GetAttr(r3, r4) - r6 = PyObject_CallFunctionObjArgs(r5, r2, 0) - r7 = r0.f - r8 = PyObject_CallFunctionObjArgs(r7, 0) - r9 = '---' - r10 = builtins :: module - r11 = 'print' - r12 = CPyObject_GetAttr(r10, r11) - r13 = PyObject_CallFunctionObjArgs(r12, r9, 0) + r1 = '---' + r2 = builtins :: module + r3 = 'print' + r4 = CPyObject_GetAttr(r2, r3) + r5 = PyObject_CallFunctionObjArgs(r4, r1, 0) + r6 = r0.f + r7 = PyObject_CallFunctionObjArgs(r6, 0) + r8 = '---' + r9 = builtins :: module + r10 = 'print' + r11 = CPyObject_GetAttr(r9, r10) + r12 = PyObject_CallFunctionObjArgs(r11, r8, 0) return 1 def b(f): f :: object r0 :: __main__.b_env r1 :: bool r2 :: __main__.g_b_obj - r3, r4 :: bool - r5 :: object + r3 :: bool + g :: object L0: r0 = b_env() r0.f = f; r1 = is_error r2 = g_b_obj() r2.__mypyc_env__ = r0; r3 = is_error - r0.g = r2; r4 = is_error - r5 = r0.g - return r5 + g = r2 + return g def d_c_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -2723,20 +2481,17 @@ L2: def d_c_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.d_c_obj r0 :: __main__.c_env - r1, d :: object - r2 :: str - r3 :: object - r4 :: str - r5, r6 :: object + r1 :: str + r2 :: object + r3 :: str + r4, r5 :: object L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.d - d = r1 - r2 = 'd' - r3 = builtins :: module - r4 = 'print' - r5 = CPyObject_GetAttr(r3, r4) - r6 = PyObject_CallFunctionObjArgs(r5, r2, 0) + r1 = 'd' + r2 = builtins :: module + r3 = 'print' + r4 = CPyObject_GetAttr(r2, r3) + r5 = PyObject_CallFunctionObjArgs(r4, r1, 0) return 1 def c(): r0 :: __main__.c_env @@ -2747,16 +2502,15 @@ def c(): r5, r6 :: object r7 :: dict r8 :: str - r9, r10 :: object - r11 :: bool - r12 :: dict - r13 :: str - r14 :: int32 - r15 :: bit - r16 :: str - r17 :: object - r18 :: str - r19, r20, r21, r22 :: object + r9, r10, d :: object + r11 :: dict + r12 :: str + r13 :: i32 + r14 :: bit + r15 :: str + r16 :: object + r17 :: str + r18, r19, r20 :: object L0: r0 = c_env() r1 = d_c_obj() @@ -2769,48 +2523,39 @@ L0: r8 = 'a' r9 = CPyDict_GetItem(r7, r8) r10 = PyObject_CallFunctionObjArgs(r9, r6, 0) - r0.d = r10; r11 = is_error - r12 = __main__.globals :: static - r13 = 'd' - r14 = CPyDict_SetItem(r12, r13, r10) - r15 = r14 >= 0 :: signed - r16 = 'c' - r17 = builtins :: module - r18 = 'print' - r19 = CPyObject_GetAttr(r17, r18) - r20 = PyObject_CallFunctionObjArgs(r19, r16, 0) - r21 = r0.d - r22 = PyObject_CallFunctionObjArgs(r21, 0) + d = r10 + r11 = __main__.globals :: static + r12 = 'd' + r13 = CPyDict_SetItem(r11, r12, r10) + r14 = r13 >= 0 :: signed + r15 = 'c' + r16 = builtins :: module + r17 = 'print' + r18 = CPyObject_GetAttr(r16, r17) + r19 = PyObject_CallFunctionObjArgs(r18, r15, 0) + r20 = PyObject_CallFunctionObjArgs(d, 0) return 1 def __top_level__(): r0, r1 :: object r2 :: bit r3 :: str - r4 :: object - r5 :: dict + r4, r5 :: object r6 :: str - r7 :: list - r8, r9 :: ptr + r7 :: dict + r8 :: object + r9 :: dict r10 :: str r11 :: object - r12, r13, r14 :: str - r15 :: object - r16 :: str - r17 :: int32 - r18 :: bit - r19 :: dict - r20 :: str - r21 :: object - r22 :: dict - r23 :: str - r24, r25 :: object - r26 :: dict - r27 :: str - r28, r29 :: object - r30 :: dict - r31 :: str - r32 :: int32 - r33 :: bit + r12 :: dict + r13 :: str + r14, r15 :: object + r16 :: dict + r17 :: str + r18, r19 :: object + r20 :: dict + r21 :: str + r22 :: i32 + r23 :: bit L0: r0 = builtins :: module r1 = load_address _Py_NoneStruct @@ -2821,38 +2566,26 @@ L1: r4 = PyImport_Import(r3) builtins = r4 :: module L2: - r5 = __main__.globals :: static - r6 = 'Callable' - r7 = PyList_New(1) - r8 = get_element_ptr r7 ob_item :: PyListObject - r9 = load_mem r8 :: ptr* - set_mem r9, r6 :: builtins.object* - keep_alive r7 - r10 = 'typing' - r11 = PyImport_ImportModuleLevelObject(r10, r5, 0, r7, 0) - typing = r11 :: module - r12 = 'typing' - r13 = 'Callable' - r14 = 'Callable' - r15 = CPyImport_ImportFrom(r11, r12, r13, r14) - r16 = 'Callable' - r17 = CPyDict_SetItem(r5, r16, r15) - r18 = r17 >= 0 :: signed - r19 = __main__.globals :: static - r20 = 'c' - r21 = CPyDict_GetItem(r19, r20) - r22 = __main__.globals :: static - r23 = 'b' - r24 = CPyDict_GetItem(r22, r23) - r25 = PyObject_CallFunctionObjArgs(r24, r21, 0) - r26 = __main__.globals :: static - r27 = 'a' - r28 = CPyDict_GetItem(r26, r27) - r29 = PyObject_CallFunctionObjArgs(r28, r25, 0) - r30 = __main__.globals :: static - r31 = 'c' - r32 = CPyDict_SetItem(r30, r31, r29) - r33 = r32 >= 0 :: signed + r5 = ('Callable',) + r6 = 'typing' + r7 = __main__.globals :: static + r8 = CPyImport_ImportFromMany(r6, r5, r5, r7) + typing = r8 :: module + r9 = __main__.globals :: static + r10 = 'c' + r11 = CPyDict_GetItem(r9, r10) + r12 = __main__.globals :: static + r13 = 'b' + r14 = CPyDict_GetItem(r12, r13) + r15 = PyObject_CallFunctionObjArgs(r14, r11, 0) + r16 = __main__.globals :: static + r17 = 'a' + r18 = CPyDict_GetItem(r16, r17) + r19 = PyObject_CallFunctionObjArgs(r18, r15, 0) + r20 = __main__.globals :: static + r21 = 'c' + r22 = CPyDict_SetItem(r20, r21, r19) + r23 = r22 >= 0 :: signed return 1 [case testDecoratorsSimple_toplevel] @@ -2882,63 +2615,51 @@ L2: def g_a_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.g_a_obj r0 :: __main__.a_env - r1, g :: object - r2 :: str - r3 :: object - r4 :: str - r5, r6, r7, r8 :: object - r9 :: str - r10 :: object - r11 :: str - r12, r13 :: object + r1 :: str + r2 :: object + r3 :: str + r4, r5, r6, r7 :: object + r8 :: str + r9 :: object + r10 :: str + r11, r12 :: object L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.g - g = r1 - r2 = 'Entering' - r3 = builtins :: module - r4 = 'print' - r5 = CPyObject_GetAttr(r3, r4) - r6 = PyObject_CallFunctionObjArgs(r5, r2, 0) - r7 = r0.f - r8 = PyObject_CallFunctionObjArgs(r7, 0) - r9 = 'Exited' - r10 = builtins :: module - r11 = 'print' - r12 = CPyObject_GetAttr(r10, r11) - r13 = PyObject_CallFunctionObjArgs(r12, r9, 0) + r1 = 'Entering' + r2 = builtins :: module + r3 = 'print' + r4 = CPyObject_GetAttr(r2, r3) + r5 = PyObject_CallFunctionObjArgs(r4, r1, 0) + r6 = r0.f + r7 = PyObject_CallFunctionObjArgs(r6, 0) + r8 = 'Exited' + r9 = builtins :: module + r10 = 'print' + r11 = CPyObject_GetAttr(r9, r10) + r12 = PyObject_CallFunctionObjArgs(r11, r8, 0) return 1 def a(f): f :: object r0 :: __main__.a_env r1 :: bool r2 :: __main__.g_a_obj - r3, r4 :: bool - r5 :: object + r3 :: bool + g :: object L0: r0 = a_env() r0.f = f; r1 = is_error r2 = g_a_obj() r2.__mypyc_env__ = r0; r3 = is_error - r0.g = r2; r4 = is_error - r5 = r0.g - return r5 + g = r2 + return g def __top_level__(): r0, r1 :: object r2 :: bit r3 :: str - r4 :: object - r5 :: dict + r4, r5 :: object r6 :: str - r7 :: list - r8, r9 :: ptr - r10 :: str - r11 :: object - r12, r13, r14 :: str - r15 :: object - r16 :: str - r17 :: int32 - r18 :: bit + r7 :: dict + r8 :: object L0: r0 = builtins :: module r1 = load_address _Py_NoneStruct @@ -2949,23 +2670,11 @@ L1: r4 = PyImport_Import(r3) builtins = r4 :: module L2: - r5 = __main__.globals :: static - r6 = 'Callable' - r7 = PyList_New(1) - r8 = get_element_ptr r7 ob_item :: PyListObject - r9 = load_mem r8 :: ptr* - set_mem r9, r6 :: builtins.object* - keep_alive r7 - r10 = 'typing' - r11 = PyImport_ImportModuleLevelObject(r10, r5, 0, r7, 0) - typing = r11 :: module - r12 = 'typing' - r13 = 'Callable' - r14 = 'Callable' - r15 = CPyImport_ImportFrom(r11, r12, r13, r14) - r16 = 'Callable' - r17 = CPyDict_SetItem(r5, r16, r15) - r18 = r17 >= 0 :: signed + r5 = ('Callable',) + r6 = 'typing' + r7 = __main__.globals :: static + r8 = CPyImport_ImportFromMany(r6, r5, r5, r7) + typing = r8 :: module return 1 [case testAnyAllG] @@ -2983,85 +2692,57 @@ def call_any(l): r0 :: bool r1, r2 :: object r3, i :: int - r4 :: native_int - r5, r6 :: bit - r7 :: bool - r8, r9 :: bit + r4, r5 :: bit L0: r0 = 0 r1 = PyObject_GetIter(l) L1: r2 = PyIter_Next(r1) - if is_error(r2) goto L9 else goto L2 + if is_error(r2) goto L6 else goto L2 L2: r3 = unbox(int, r2) i = r3 - r4 = i & 1 - r5 = r4 == 0 - if r5 goto L3 else goto L4 :: bool + r4 = int_eq i, 0 + if r4 goto L3 else goto L4 :: bool L3: - r6 = i == 0 - r7 = r6 - goto L5 + r0 = 1 + goto L8 L4: - r8 = CPyTagged_IsEq_(i, 0) - r7 = r8 L5: - if r7 goto L6 else goto L7 :: bool + goto L1 L6: - r0 = 1 - goto L11 + r5 = CPy_NoErrOccured() L7: L8: - goto L1 -L9: - r9 = CPy_NoErrOccured() -L10: -L11: return r0 def call_all(l): l :: object r0 :: bool r1, r2 :: object r3, i :: int - r4 :: native_int - r5, r6 :: bit - r7 :: bool - r8 :: bit - r9 :: bool - r10 :: bit + r4, r5, r6 :: bit L0: r0 = 1 r1 = PyObject_GetIter(l) L1: r2 = PyIter_Next(r1) - if is_error(r2) goto L9 else goto L2 + if is_error(r2) goto L6 else goto L2 L2: r3 = unbox(int, r2) i = r3 - r4 = i & 1 - r5 = r4 == 0 + r4 = int_eq i, 0 + r5 = r4 ^ 1 if r5 goto L3 else goto L4 :: bool L3: - r6 = i == 0 - r7 = r6 - goto L5 + r0 = 0 + goto L8 L4: - r8 = CPyTagged_IsEq_(i, 0) - r7 = r8 L5: - r9 = r7 ^ 1 - if r9 goto L6 else goto L7 :: bool + goto L1 L6: - r0 = 0 - goto L11 + r6 = CPy_NoErrOccured() L7: L8: - goto L1 -L9: - r10 = CPy_NoErrOccured() -L10: -L11: return r0 [case testSum] @@ -3112,7 +2793,7 @@ def lol(x: Any): def lol(x): x :: object r0, r1 :: str - r2 :: int32 + r2 :: i32 r3 :: bit r4 :: object L0: @@ -3309,6 +2990,32 @@ def foo(z): L0: return 1 +[case testFinalLocals] +from typing import Final + +def inlined() -> str: + # XXX: the final type must be declared explicitly for Var.final_value to be set. + const: Final[str] = "Oppenheimer" + return const + +def local() -> str: + const: Final[str] = inlined() + return const +[out] +def inlined(): + r0, const, r1 :: str +L0: + r0 = 'Oppenheimer' + const = r0 + r1 = 'Oppenheimer' + return r1 +def local(): + r0, const :: str +L0: + r0 = inlined() + const = r0 + return const + [case testDirectlyCall__bool__] class A: def __bool__(self) -> bool: @@ -3417,34 +3124,95 @@ def f(x: object) -> bool: [out] def f(x): x :: object - r0 :: int32 + r0 :: i32 r1 :: bit r2 :: bool L0: r0 = PyObject_IsTrue(x) r1 = r0 >= 0 :: signed - r2 = truncate r0: int32 to builtins.bool + r2 = truncate r0: i32 to builtins.bool return r2 -[case testLocalImportSubmodule] -def f() -> int: +[case testLocalImports] +def root() -> None: + import dataclasses + import enum + +def submodule() -> int: import p.m return p.x [file p/__init__.py] x = 1 [file p/m.py] [out] -def f(): +def root(): r0 :: dict r1, r2 :: object r3 :: bit r4 :: str r5 :: object - r6 :: dict - r7 :: str - r8 :: object - r9 :: str - r10 :: int32 + r6 :: str + r7 :: dict + r8 :: str + r9 :: object + r10 :: i32 + r11 :: bit + r12 :: dict + r13, r14 :: object + r15 :: bit + r16 :: str + r17 :: object + r18 :: str + r19 :: dict + r20 :: str + r21 :: object + r22 :: i32 + r23 :: bit +L0: + r0 = __main__.globals :: static + r1 = dataclasses :: module + r2 = load_address _Py_NoneStruct + r3 = r1 != r2 + if r3 goto L2 else goto L1 :: bool +L1: + r4 = 'dataclasses' + r5 = PyImport_Import(r4) + dataclasses = r5 :: module +L2: + r6 = 'dataclasses' + r7 = PyImport_GetModuleDict() + r8 = 'dataclasses' + r9 = CPyDict_GetItem(r7, r8) + r10 = CPyDict_SetItem(r0, r6, r9) + r11 = r10 >= 0 :: signed + r12 = __main__.globals :: static + r13 = enum :: module + r14 = load_address _Py_NoneStruct + r15 = r13 != r14 + if r15 goto L4 else goto L3 :: bool +L3: + r16 = 'enum' + r17 = PyImport_Import(r16) + enum = r17 :: module +L4: + r18 = 'enum' + r19 = PyImport_GetModuleDict() + r20 = 'enum' + r21 = CPyDict_GetItem(r19, r20) + r22 = CPyDict_SetItem(r12, r18, r21) + r23 = r22 >= 0 :: signed + return 1 +def submodule(): + r0 :: dict + r1, r2 :: object + r3 :: bit + r4 :: str + r5 :: object + r6 :: str + r7 :: dict + r8 :: str + r9 :: object + r10 :: i32 r11 :: bit r12 :: dict r13 :: str @@ -3463,11 +3231,11 @@ L1: r5 = PyImport_Import(r4) p.m = r5 :: module L2: - r6 = PyImport_GetModuleDict() - r7 = 'p' - r8 = CPyDict_GetItem(r6, r7) - r9 = 'p' - r10 = CPyDict_SetItem(r0, r9, r8) + r6 = 'p' + r7 = PyImport_GetModuleDict() + r8 = 'p' + r9 = CPyDict_GetItem(r7, r8) + r10 = CPyDict_SetItem(r0, r6, r9) r11 = r10 >= 0 :: signed r12 = PyImport_GetModuleDict() r13 = 'p' @@ -3483,14 +3251,14 @@ def f(x: object) -> bool: [out] def f(x): x, r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: bool L0: r0 = load_address PyBool_Type r1 = PyObject_IsInstance(x, r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool return r3 [case testRangeObject] @@ -3548,7 +3316,7 @@ L0: r0 = 8 i = r0 L1: - r1 = r0 < 24 :: signed + r1 = int_lt r0, 24 if r1 goto L2 else goto L4 :: bool L2: r2 = CPyTagged_Add(sum, i) @@ -3573,7 +3341,7 @@ def f() -> None: def f(): i, r0 :: int r1, i__redef__, r2 :: str - r3, i__redef____redef__ :: float + i__redef____redef__ :: float L0: i = 0 r0 = CPyTagged_Add(i, 2) @@ -3582,8 +3350,7 @@ L0: i__redef__ = r1 r2 = CPyStr_Append(i__redef__, i__redef__) i__redef__ = r2 - r3 = 0.0 - i__redef____redef__ = r3 + i__redef____redef__ = 0.0 return 1 [case testNewType] @@ -3600,3 +3367,33 @@ def f(arg): arg :: __main__.A L0: return arg + +[case testTypeCheckingFlag] +from typing import TYPE_CHECKING, List + +def f(arg: List[int]) -> int: + if TYPE_CHECKING: + from collections.abc import Sized + s: Sized = arg + return len(s) + +[out] +def f(arg): + arg :: list + r0 :: bool + r1 :: int + r2 :: bit + s :: object + r3 :: int +L0: + r0 = 0 << 1 + r1 = extend r0: builtins.bool to builtins.int + r2 = r1 != 0 + if r2 goto L1 else goto L2 :: bool +L1: + goto L3 +L2: +L3: + s = arg + r3 = CPyObject_Size(s) + return r3 diff --git a/mypyc/test-data/irbuild-bool.test b/mypyc/test-data/irbuild-bool.test index 9257d8d63f7e..128266e6b1d7 100644 --- a/mypyc/test-data/irbuild-bool.test +++ b/mypyc/test-data/irbuild-bool.test @@ -29,18 +29,18 @@ L0: return r0 def bool_to_i64(b): b :: bool - r0 :: int64 + r0 :: i64 L0: - r0 = extend b: builtins.bool to int64 + r0 = extend b: builtins.bool to i64 return r0 def i64_to_bool(n): - n :: int64 + n :: i64 r0 :: bit L0: r0 = n != 0 return r0 def bit_to_int(n1, n2): - n1, n2 :: int64 + n1, n2 :: i64 r0 :: bit r1 :: bool r2 :: int @@ -50,12 +50,12 @@ L0: r2 = extend r1: builtins.bool to builtins.int return r2 def bit_to_i64(n1, n2): - n1, n2 :: int64 + n1, n2 :: i64 r0 :: bit - r1 :: int64 + r1 :: i64 L0: r0 = n1 == n2 - r1 = extend r0: bit to int64 + r1 = extend r0: bit to i64 return r1 [case testConversionToBool] @@ -87,26 +87,23 @@ L0: return 1 def list_to_bool(l): l :: list - r0 :: ptr - r1 :: native_int - r2 :: short_int - r3 :: bit -L0: - r0 = get_element_ptr l ob_size :: PyVarObject - r1 = load_mem r0 :: native_int* - keep_alive l - r2 = r1 << 1 - r3 = r2 != 0 - return r3 + r0 :: native_int + r1 :: short_int + r2 :: bit +L0: + r0 = var_object_size l + r1 = r0 << 1 + r2 = int_ne r1, 0 + return r2 def always_truthy_instance_to_bool(o): o :: __main__.C - r0 :: int32 + r0 :: i32 r1 :: bit r2 :: bool L0: r0 = PyObject_IsTrue(o) r1 = r0 >= 0 :: signed - r2 = truncate r0: int32 to builtins.bool + r2 = truncate r0: i32 to builtins.bool return r2 def instance_to_bool(o): o :: __main__.D @@ -222,7 +219,7 @@ def eq1(x, y): L0: r0 = y << 1 r1 = extend r0: builtins.bool to builtins.int - r2 = x == r1 + r2 = int_eq x, r1 return r2 def eq2(x, y): x :: bool @@ -233,23 +230,23 @@ def eq2(x, y): L0: r0 = x << 1 r1 = extend r0: builtins.bool to builtins.int - r2 = r1 == y + r2 = int_eq r1, y return r2 def neq1(x, y): - x :: int64 + x :: i64 y :: bool - r0 :: int64 + r0 :: i64 r1 :: bit L0: - r0 = extend y: builtins.bool to int64 + r0 = extend y: builtins.bool to i64 r1 = x != r0 return r1 def neq2(x, y): x :: bool - y, r0 :: int64 + y, r0 :: i64 r1 :: bit L0: - r0 = extend x: builtins.bool to int64 + r0 = extend x: builtins.bool to i64 r1 = r0 != y return r1 @@ -272,74 +269,38 @@ def lt1(x, y): x :: bool y :: int r0 :: bool - r1 :: short_int - r2 :: native_int - r3 :: bit - r4 :: native_int - r5, r6, r7 :: bit - r8 :: bool - r9 :: bit + r1 :: int + r2 :: bit L0: r0 = x << 1 - r1 = extend r0: builtins.bool to short_int - r2 = r1 & 1 - r3 = r2 == 0 - r4 = y & 1 - r5 = r4 == 0 - r6 = r3 & r5 - if r6 goto L1 else goto L2 :: bool -L1: - r7 = r1 < y :: signed - r8 = r7 - goto L3 -L2: - r9 = CPyTagged_IsLt_(r1, y) - r8 = r9 -L3: - return r8 + r1 = extend r0: builtins.bool to builtins.int + r2 = int_lt r1, y + return r2 def lt2(x, y): x :: int y, r0 :: bool - r1 :: short_int - r2 :: native_int - r3 :: bit - r4 :: native_int - r5, r6, r7 :: bit - r8 :: bool - r9 :: bit + r1 :: int + r2 :: bit L0: r0 = y << 1 - r1 = extend r0: builtins.bool to short_int - r2 = x & 1 - r3 = r2 == 0 - r4 = r1 & 1 - r5 = r4 == 0 - r6 = r3 & r5 - if r6 goto L1 else goto L2 :: bool -L1: - r7 = x < r1 :: signed - r8 = r7 - goto L3 -L2: - r9 = CPyTagged_IsLt_(x, r1) - r8 = r9 -L3: - return r8 + r1 = extend r0: builtins.bool to builtins.int + r2 = int_lt x, r1 + return r2 def gt1(x, y): x :: bool - y, r0 :: int64 + y, r0 :: i64 r1 :: bit L0: - r0 = extend x: builtins.bool to int64 + r0 = extend x: builtins.bool to i64 r1 = r0 < y :: signed return r1 def gt2(x, y): - x :: int64 + x :: i64 y :: bool - r0 :: int64 + r0 :: i64 r1 :: bit L0: - r0 = extend y: builtins.bool to int64 + r0 = extend y: builtins.bool to i64 r1 = x < r0 :: signed return r1 @@ -386,11 +347,11 @@ L0: r2 = CPyTagged_Invert(r1) return r2 def mixed_bitand(x, y): - x :: int64 + x :: i64 y :: bool - r0, r1 :: int64 + r0, r1 :: i64 L0: - r0 = extend y: builtins.bool to int64 + r0 = extend y: builtins.bool to i64 r1 = x & r0 return r1 diff --git a/mypyc/test-data/irbuild-bytes.test b/mypyc/test-data/irbuild-bytes.test index f13a1a956580..b41836d8829f 100644 --- a/mypyc/test-data/irbuild-bytes.test +++ b/mypyc/test-data/irbuild-bytes.test @@ -71,7 +71,7 @@ def neq(x: bytes, y: bytes) -> bool: [out] def eq(x, y): x, y :: bytes - r0 :: int32 + r0 :: i32 r1, r2 :: bit L0: r0 = CPyBytes_Compare(x, y) @@ -80,7 +80,7 @@ L0: return r2 def neq(x, y): x, y :: bytes - r0 :: int32 + r0 :: i32 r1, r2 :: bit L0: r0 = CPyBytes_Compare(x, y) @@ -140,15 +140,12 @@ def f(b: bytes) -> int: [out] def f(b): b :: bytes - r0 :: ptr - r1 :: native_int - r2 :: short_int + r0 :: native_int + r1 :: short_int L0: - r0 = get_element_ptr b ob_size :: PyVarObject - r1 = load_mem r0 :: native_int* - keep_alive b - r2 = r1 << 1 - return r2 + r0 = var_object_size b + r1 = r0 << 1 + return r1 [case testBytesFormatting] def f(var: bytes, num: int) -> None: diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index b9501c32180d..2c15f09c9c34 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -41,28 +41,27 @@ def f(): r0, c :: __main__.C r1 :: bool r2 :: list - r3, r4 :: ptr + r3 :: ptr a :: list - r5 :: object - r6, d :: __main__.C - r7, r8 :: int + r4 :: object + r5, d :: __main__.C + r6, r7 :: int L0: r0 = C() c = r0 c.x = 10; r1 = is_error r2 = PyList_New(1) - r3 = get_element_ptr r2 ob_item :: PyListObject - r4 = load_mem r3 :: ptr* - set_mem r4, c :: builtins.object* + r3 = list_items r2 + buf_init_item r3, 0, c keep_alive r2 a = r2 - r5 = CPyList_GetItemShort(a, 0) - r6 = cast(__main__.C, r5) - d = r6 - r7 = borrow d.x - r8 = CPyTagged_Add(r7, 2) + r4 = CPyList_GetItemShort(a, 0) + r5 = cast(__main__.C, r4) + d = r5 + r6 = borrow d.x + r7 = CPyTagged_Add(r6, 2) keep_alive d - return r8 + return r7 [case testMethodCall] class A: @@ -200,84 +199,63 @@ def __top_level__(): r0, r1 :: object r2 :: bit r3 :: str - r4 :: object - r5 :: dict - r6, r7 :: str - r8 :: list - r9, r10, r11 :: ptr - r12 :: str - r13 :: object - r14, r15, r16 :: str - r17 :: object - r18 :: str - r19 :: int32 - r20 :: bit - r21, r22, r23 :: str - r24 :: object - r25 :: str - r26 :: int32 - r27 :: bit - r28 :: dict - r29 :: str - r30 :: list - r31, r32 :: ptr - r33 :: str - r34 :: object - r35, r36, r37 :: str - r38 :: object + r4, r5 :: object + r6 :: str + r7 :: dict + r8, r9 :: object + r10 :: str + r11 :: dict + r12 :: object + r13 :: str + r14 :: dict + r15 :: str + r16, r17 :: object + r18 :: dict + r19 :: str + r20 :: i32 + r21 :: bit + r22 :: object + r23 :: str + r24, r25 :: object + r26 :: bool + r27 :: str + r28 :: tuple + r29 :: i32 + r30 :: bit + r31 :: dict + r32 :: str + r33 :: i32 + r34 :: bit + r35 :: object + r36 :: str + r37, r38 :: object r39 :: str - r40 :: int32 - r41 :: bit - r42 :: str + r40 :: tuple + r41 :: i32 + r42 :: bit r43 :: dict r44 :: str - r45, r46 :: object - r47 :: dict - r48 :: str - r49 :: int32 - r50 :: bit + r45 :: i32 + r46 :: bit + r47, r48 :: object + r49 :: dict + r50 :: str r51 :: object - r52 :: str - r53, r54 :: object - r55 :: bool - r56 :: str - r57 :: tuple - r58 :: int32 - r59 :: bit - r60 :: dict - r61 :: str - r62 :: int32 - r63 :: bit - r64 :: object - r65 :: str - r66, r67 :: object - r68 :: str - r69 :: tuple - r70 :: int32 - r71 :: bit - r72 :: dict - r73 :: str - r74 :: int32 - r75 :: bit - r76, r77 :: object - r78 :: dict - r79 :: str - r80 :: object - r81 :: dict - r82 :: str - r83, r84 :: object - r85 :: tuple - r86 :: str - r87, r88 :: object - r89 :: bool - r90, r91 :: str - r92 :: tuple - r93 :: int32 - r94 :: bit - r95 :: dict - r96 :: str - r97 :: int32 - r98 :: bit + r52 :: dict + r53 :: str + r54, r55 :: object + r56 :: tuple + r57 :: str + r58, r59 :: object + r60 :: bool + r61, r62 :: str + r63 :: tuple + r64 :: i32 + r65 :: bit + r66 :: dict + r67 :: str + r68 :: i32 + r69 :: bit L0: r0 = builtins :: module r1 = load_address _Py_NoneStruct @@ -288,110 +266,76 @@ L1: r4 = PyImport_Import(r3) builtins = r4 :: module L2: - r5 = __main__.globals :: static - r6 = 'TypeVar' - r7 = 'Generic' - r8 = PyList_New(2) - r9 = get_element_ptr r8 ob_item :: PyListObject - r10 = load_mem r9 :: ptr* - set_mem r10, r6 :: builtins.object* - r11 = r10 + WORD_SIZE*1 - set_mem r11, r7 :: builtins.object* - keep_alive r8 - r12 = 'typing' - r13 = PyImport_ImportModuleLevelObject(r12, r5, 0, r8, 0) - typing = r13 :: module - r14 = 'typing' + r5 = ('TypeVar', 'Generic') + r6 = 'typing' + r7 = __main__.globals :: static + r8 = CPyImport_ImportFromMany(r6, r5, r5, r7) + typing = r8 :: module + r9 = ('trait',) + r10 = 'mypy_extensions' + r11 = __main__.globals :: static + r12 = CPyImport_ImportFromMany(r10, r9, r9, r11) + mypy_extensions = r12 :: module + r13 = 'T' + r14 = __main__.globals :: static r15 = 'TypeVar' - r16 = 'TypeVar' - r17 = CPyImport_ImportFrom(r13, r14, r15, r16) - r18 = 'TypeVar' - r19 = CPyDict_SetItem(r5, r18, r17) - r20 = r19 >= 0 :: signed - r21 = 'typing' - r22 = 'Generic' - r23 = 'Generic' - r24 = CPyImport_ImportFrom(r13, r21, r22, r23) - r25 = 'Generic' - r26 = CPyDict_SetItem(r5, r25, r24) - r27 = r26 >= 0 :: signed - r28 = __main__.globals :: static - r29 = 'trait' - r30 = PyList_New(1) - r31 = get_element_ptr r30 ob_item :: PyListObject - r32 = load_mem r31 :: ptr* - set_mem r32, r29 :: builtins.object* - keep_alive r30 - r33 = 'mypy_extensions' - r34 = PyImport_ImportModuleLevelObject(r33, r28, 0, r30, 0) - mypy_extensions = r34 :: module - r35 = 'mypy_extensions' - r36 = 'trait' - r37 = 'trait' - r38 = CPyImport_ImportFrom(r34, r35, r36, r37) - r39 = 'trait' - r40 = CPyDict_SetItem(r28, r39, r38) - r41 = r40 >= 0 :: signed - r42 = 'T' + r16 = CPyDict_GetItem(r14, r15) + r17 = PyObject_CallFunctionObjArgs(r16, r13, 0) + r18 = __main__.globals :: static + r19 = 'T' + r20 = CPyDict_SetItem(r18, r19, r17) + r21 = r20 >= 0 :: signed + r22 = :: object + r23 = '__main__' + r24 = __main__.C_template :: type + r25 = CPyType_FromTemplate(r24, r22, r23) + r26 = C_trait_vtable_setup() + r27 = '__mypyc_attrs__' + r28 = PyTuple_Pack(0) + r29 = PyObject_SetAttr(r25, r27, r28) + r30 = r29 >= 0 :: signed + __main__.C = r25 :: type + r31 = __main__.globals :: static + r32 = 'C' + r33 = CPyDict_SetItem(r31, r32, r25) + r34 = r33 >= 0 :: signed + r35 = :: object + r36 = '__main__' + r37 = __main__.S_template :: type + r38 = CPyType_FromTemplate(r37, r35, r36) + r39 = '__mypyc_attrs__' + r40 = PyTuple_Pack(0) + r41 = PyObject_SetAttr(r38, r39, r40) + r42 = r41 >= 0 :: signed + __main__.S = r38 :: type r43 = __main__.globals :: static - r44 = 'TypeVar' - r45 = CPyDict_GetItem(r43, r44) - r46 = PyObject_CallFunctionObjArgs(r45, r42, 0) - r47 = __main__.globals :: static - r48 = 'T' - r49 = CPyDict_SetItem(r47, r48, r46) - r50 = r49 >= 0 :: signed - r51 = :: object - r52 = '__main__' - r53 = __main__.C_template :: type - r54 = CPyType_FromTemplate(r53, r51, r52) - r55 = C_trait_vtable_setup() - r56 = '__mypyc_attrs__' - r57 = PyTuple_Pack(0) - r58 = PyObject_SetAttr(r54, r56, r57) - r59 = r58 >= 0 :: signed - __main__.C = r54 :: type - r60 = __main__.globals :: static - r61 = 'C' - r62 = CPyDict_SetItem(r60, r61, r54) - r63 = r62 >= 0 :: signed - r64 = :: object - r65 = '__main__' - r66 = __main__.S_template :: type - r67 = CPyType_FromTemplate(r66, r64, r65) - r68 = '__mypyc_attrs__' - r69 = PyTuple_Pack(0) - r70 = PyObject_SetAttr(r67, r68, r69) - r71 = r70 >= 0 :: signed - __main__.S = r67 :: type - r72 = __main__.globals :: static - r73 = 'S' - r74 = CPyDict_SetItem(r72, r73, r67) - r75 = r74 >= 0 :: signed - r76 = __main__.C :: type - r77 = __main__.S :: type - r78 = __main__.globals :: static - r79 = 'Generic' - r80 = CPyDict_GetItem(r78, r79) - r81 = __main__.globals :: static - r82 = 'T' - r83 = CPyDict_GetItem(r81, r82) - r84 = PyObject_GetItem(r80, r83) - r85 = PyTuple_Pack(3, r76, r77, r84) - r86 = '__main__' - r87 = __main__.D_template :: type - r88 = CPyType_FromTemplate(r87, r85, r86) - r89 = D_trait_vtable_setup() - r90 = '__mypyc_attrs__' - r91 = '__dict__' - r92 = PyTuple_Pack(1, r91) - r93 = PyObject_SetAttr(r88, r90, r92) - r94 = r93 >= 0 :: signed - __main__.D = r88 :: type - r95 = __main__.globals :: static - r96 = 'D' - r97 = CPyDict_SetItem(r95, r96, r88) - r98 = r97 >= 0 :: signed + r44 = 'S' + r45 = CPyDict_SetItem(r43, r44, r38) + r46 = r45 >= 0 :: signed + r47 = __main__.C :: type + r48 = __main__.S :: type + r49 = __main__.globals :: static + r50 = 'Generic' + r51 = CPyDict_GetItem(r49, r50) + r52 = __main__.globals :: static + r53 = 'T' + r54 = CPyDict_GetItem(r52, r53) + r55 = PyObject_GetItem(r51, r54) + r56 = PyTuple_Pack(3, r47, r48, r55) + r57 = '__main__' + r58 = __main__.D_template :: type + r59 = CPyType_FromTemplate(r58, r56, r57) + r60 = D_trait_vtable_setup() + r61 = '__mypyc_attrs__' + r62 = '__dict__' + r63 = PyTuple_Pack(1, r62) + r64 = PyObject_SetAttr(r59, r61, r63) + r65 = r64 >= 0 :: signed + __main__.D = r59 :: type + r66 = __main__.globals :: static + r67 = 'D' + r68 = CPyDict_SetItem(r66, r67, r59) + r69 = r68 >= 0 :: signed return 1 [case testIsInstance] @@ -656,6 +600,75 @@ L0: r3 = CPyTagged_Add(r0, r2) return r3 +[case testCallClassMethodViaCls] +class C: + @classmethod + def f(cls, x: int) -> int: + return cls.g(x) + + @classmethod + def g(cls, x: int) -> int: + return x + +class D: + @classmethod + def f(cls, x: int) -> int: + # TODO: This could aso be optimized, since g is not ever overridden + return cls.g(x) + + @classmethod + def g(cls, x: int) -> int: + return x + +class DD(D): + pass +[out] +def C.f(cls, x): + cls :: object + x :: int + r0 :: object + r1 :: int +L0: + r0 = __main__.C :: type + r1 = C.g(r0, x) + return r1 +def C.g(cls, x): + cls :: object + x :: int +L0: + return x +def D.f(cls, x): + cls :: object + x :: int + r0 :: str + r1, r2 :: object + r3 :: int +L0: + r0 = 'g' + r1 = box(int, x) + r2 = CPyObject_CallMethodObjArgs(cls, r0, r1, 0) + r3 = unbox(int, r2) + return r3 +def D.g(cls, x): + cls :: object + x :: int +L0: + return x + +[case testCannotAssignToClsArgument] +from typing import Any, cast + +class C: + @classmethod + def m(cls) -> None: + cls = cast(Any, D) # E: Cannot assign to the first argument of classmethod + cls, x = cast(Any, D), 1 # E: Cannot assign to the first argument of classmethod + cls, x = cast(Any, [1, 2]) # E: Cannot assign to the first argument of classmethod + cls.m() + +class D: + pass + [case testSuper1] class A: def __init__(self, x: int) -> None: @@ -823,7 +836,7 @@ def Base.__ne__(__mypyc_self__, rhs): __mypyc_self__ :: __main__.Base rhs, r0, r1 :: object r2 :: bit - r3 :: int32 + r3 :: i32 r4 :: bit r5 :: bool r6 :: object @@ -835,7 +848,7 @@ L0: L1: r3 = PyObject_Not(r0) r4 = r3 >= 0 :: signed - r5 = truncate r3: int32 to builtins.bool + r5 = truncate r3: i32 to builtins.bool r6 = box(bool, r5) return r6 L2: @@ -943,7 +956,7 @@ def Derived.__ne__(__mypyc_self__, rhs): __mypyc_self__ :: __main__.Derived rhs, r0, r1 :: object r2 :: bit - r3 :: int32 + r3 :: i32 r4 :: bit r5 :: bool r6 :: object @@ -955,7 +968,7 @@ L0: L1: r3 = PyObject_Not(r0) r4 = r3 >= 0 :: signed - r5 = truncate r3: int32 to builtins.bool + r5 = truncate r3: i32 to builtins.bool r6 = box(bool, r5) return r6 L2: @@ -1015,7 +1028,7 @@ def foo(x: WelpDict) -> None: [out] def foo(x): x :: dict - r0 :: int32 + r0 :: i32 r1 :: bit L0: r0 = CPyDict_Update(x, x) @@ -1235,7 +1248,7 @@ L0: r0 = x.__getitem__(2) r1 = CPyList_GetItemShortBorrow(r0, 0) r2 = unbox(int, r1) - r3 = r2 == 4 + r3 = int_eq r2, 4 keep_alive r0 if r3 goto L1 else goto L2 :: bool L1: diff --git a/mypyc/test-data/irbuild-constant-fold.test b/mypyc/test-data/irbuild-constant-fold.test index 7d9127887aa6..97b13ab337c7 100644 --- a/mypyc/test-data/irbuild-constant-fold.test +++ b/mypyc/test-data/irbuild-constant-fold.test @@ -3,6 +3,7 @@ def bin_ops() -> None: add = 15 + 47 add_mul = (2 + 3) * 5 sub = 7 - 11 + div = 3 / 2 bit_and = 6 & 10 bit_or = 6 | 10 bit_xor = 6 ^ 10 @@ -25,11 +26,14 @@ def pow() -> None: p3 = 0**0 [out] def bin_ops(): - add, add_mul, sub, bit_and, bit_or, bit_xor, lshift, rshift, lshift0, rshift0 :: int + add, add_mul, sub :: int + div :: float + bit_and, bit_or, bit_xor, lshift, rshift, lshift0, rshift0 :: int L0: add = 124 add_mul = 50 sub = -8 + div = 1.5 bit_and = 4 bit_or = 28 bit_xor = 24 @@ -117,44 +121,28 @@ L0: [case testIntConstantFoldingUnsupportedCases] def error_cases() -> None: - div_by_zero = 5 // 0 + div_by_zero = 5 / 0 + floor_div_by_zero = 5 // 0 mod_by_zero = 5 % 0 lshift_neg = 6 << -1 rshift_neg = 7 >> -1 -def unsupported_div() -> None: - x = 4 / 6 - y = 10 / 5 def unsupported_pow() -> None: p = 3 ** (-1) [out] def error_cases(): - r0, div_by_zero, r1, mod_by_zero, r2, lshift_neg, r3, rshift_neg :: int + r0, div_by_zero :: float + r1, floor_div_by_zero, r2, mod_by_zero, r3, lshift_neg, r4, rshift_neg :: int L0: - r0 = CPyTagged_FloorDivide(10, 0) + r0 = CPyTagged_TrueDivide(10, 0) div_by_zero = r0 - r1 = CPyTagged_Remainder(10, 0) - mod_by_zero = r1 - r2 = CPyTagged_Lshift(12, -2) - lshift_neg = r2 - r3 = CPyTagged_Rshift(14, -2) - rshift_neg = r3 - return 1 -def unsupported_div(): - r0, r1, r2 :: object - r3, x :: float - r4, r5, r6 :: object - r7, y :: float -L0: - r0 = object 4 - r1 = object 6 - r2 = PyNumber_TrueDivide(r0, r1) - r3 = cast(float, r2) - x = r3 - r4 = object 10 - r5 = object 5 - r6 = PyNumber_TrueDivide(r4, r5) - r7 = cast(float, r6) - y = r7 + r1 = CPyTagged_FloorDivide(10, 0) + floor_div_by_zero = r1 + r2 = CPyTagged_Remainder(10, 0) + mod_by_zero = r2 + r3 = CPyTagged_Lshift(12, -2) + lshift_neg = r3 + r4 = CPyTagged_Rshift(14, -2) + rshift_neg = r4 return 1 def unsupported_pow(): r0, r1, r2 :: object @@ -163,7 +151,7 @@ L0: r0 = object 3 r1 = object -1 r2 = CPyNumber_Power(r0, r1) - r3 = cast(float, r2) + r3 = unbox(float, r2) p = r3 return 1 @@ -233,20 +221,260 @@ L0: a = 12 return 1 +[case testFloatConstantFolding] +from typing_extensions import Final + +N: Final = 1.5 +N2: Final = 1.5 * 2 + +def bin_ops() -> None: + add = 0.5 + 0.5 + add_mul = (1.5 + 3.5) * 5.0 + sub = 7.0 - 7.5 + div = 3.0 / 2.0 + floor_div = 3.0 // 2.0 +def bin_ops_neg() -> None: + add = 0.5 + -0.5 + add_mul = (-1.5 + 3.5) * -5.0 + add_mul2 = (1.5 + -3.5) * -5.0 + sub = 7.0 - -7.5 + div = 3.0 / -2.0 + floor_div = 3.0 // -2.0 +def unary_ops() -> None: + neg1 = -5.5 + neg2 = --1.5 + neg3 = -0.0 + pos = +5.5 +def pow() -> None: + p0 = 16.0**0 + p1 = 16.0**0.5 + p2 = (-5.0)**3 + p3 = 16.0**(-0) + p4 = 16.0**(-0.5) + p5 = (-2.0)**(-1) +def error_cases() -> None: + div = 2.0 / 0.0 + floor_div = 2.0 // 0.0 + power_imag = (-2.0)**0.5 + power_imag2 = (-2.0)**(-0.5) + power_overflow = 2.0**10000.0 +def final_floats() -> None: + add1 = N + 1.2 + add2 = N + N2 + add3 = -1.2 + N2 +[out] +def bin_ops(): + add, add_mul, sub, div, floor_div :: float +L0: + add = 1.0 + add_mul = 25.0 + sub = -0.5 + div = 1.5 + floor_div = 1.0 + return 1 +def bin_ops_neg(): + add, add_mul, add_mul2, sub, div, floor_div :: float +L0: + add = 0.0 + add_mul = -10.0 + add_mul2 = 10.0 + sub = 14.5 + div = -1.5 + floor_div = -2.0 + return 1 +def unary_ops(): + neg1, neg2, neg3, pos :: float +L0: + neg1 = -5.5 + neg2 = 1.5 + neg3 = -0.0 + pos = 5.5 + return 1 +def pow(): + p0, p1, p2, p3, p4, p5 :: float +L0: + p0 = 1.0 + p1 = 4.0 + p2 = -125.0 + p3 = 1.0 + p4 = 0.25 + p5 = -0.5 + return 1 +def error_cases(): + r0 :: bit + r1 :: bool + r2, div, r3, floor_div :: float + r4, r5, r6 :: object + r7, power_imag :: float + r8, r9, r10 :: object + r11, power_imag2 :: float + r12, r13, r14 :: object + r15, power_overflow :: float +L0: + r0 = 0.0 == 0.0 + if r0 goto L1 else goto L2 :: bool +L1: + r1 = raise ZeroDivisionError('float division by zero') + unreachable +L2: + r2 = 2.0 / 0.0 + div = r2 + r3 = CPyFloat_FloorDivide(2.0, 0.0) + floor_div = r3 + r4 = box(float, -2.0) + r5 = box(float, 0.5) + r6 = CPyNumber_Power(r4, r5) + r7 = unbox(float, r6) + power_imag = r7 + r8 = box(float, -2.0) + r9 = box(float, -0.5) + r10 = CPyNumber_Power(r8, r9) + r11 = unbox(float, r10) + power_imag2 = r11 + r12 = box(float, 2.0) + r13 = box(float, 10000.0) + r14 = CPyNumber_Power(r12, r13) + r15 = unbox(float, r14) + power_overflow = r15 + return 1 +def final_floats(): + add1, add2, add3 :: float +L0: + add1 = 2.7 + add2 = 4.5 + add3 = 1.8 + return 1 + +[case testMixedFloatIntConstantFolding] +def bin_ops() -> None: + add = 1 + 0.5 + sub = 1 - 0.5 + mul = 0.5 * 5 + div = 5 / 0.5 + floor_div = 9.5 // 5 +def error_cases() -> None: + div = 2.0 / 0 + floor_div = 2.0 // 0 + power_overflow = 2.0**10000 +[out] +def bin_ops(): + add, sub, mul, div, floor_div :: float +L0: + add = 1.5 + sub = 0.5 + mul = 2.5 + div = 10.0 + floor_div = 1.0 + return 1 +def error_cases(): + r0 :: bit + r1 :: bool + r2, div, r3, floor_div :: float + r4, r5, r6 :: object + r7, power_overflow :: float +L0: + r0 = 0.0 == 0.0 + if r0 goto L1 else goto L2 :: bool +L1: + r1 = raise ZeroDivisionError('float division by zero') + unreachable +L2: + r2 = 2.0 / 0.0 + div = r2 + r3 = CPyFloat_FloorDivide(2.0, 0.0) + floor_div = r3 + r4 = box(float, 2.0) + r5 = box(float, 10000.0) + r6 = CPyNumber_Power(r4, r5) + r7 = unbox(float, r6) + power_overflow = r7 + return 1 + [case testStrConstantFolding] from typing_extensions import Final S: Final = 'z' +N: Final = 2 def f() -> None: x = 'foo' + 'bar' y = 'x' + 'y' + S + mul = "foobar" * 2 + mul2 = N * "foobar" [out] def f(): - r0, x, r1, y :: str + r0, x, r1, y, r2, mul, r3, mul2 :: str L0: r0 = 'foobar' x = r0 r1 = 'xyz' y = r1 + r2 = 'foobarfoobar' + mul = r2 + r3 = 'foobarfoobar' + mul2 = r3 + return 1 + +[case testBytesConstantFolding] +from typing_extensions import Final + +N: Final = 2 + +def f() -> None: + # Unfortunately, mypy doesn't store the bytes value of final refs. + x = b'foo' + b'bar' + mul = b"foobar" * 2 + mul2 = N * b"foobar" +[out] +def f(): + r0, x, r1, mul, r2, mul2 :: bytes +L0: + r0 = b'foobar' + x = r0 + r1 = b'foobarfoobar' + mul = r1 + r2 = b'foobarfoobar' + mul2 = r2 + return 1 + +[case testComplexConstantFolding] +from typing_extensions import Final + +N: Final = 1 +FLOAT_N: Final = 1.5 + +def integral() -> None: + pos = 1+2j + pos_2 = 2j+N + neg = 1-2j + neg_2 = 2j-N +def floating() -> None: + pos = 1.5+2j + pos_2 = 2j+FLOAT_N + neg = 1.5-2j + neg_2 = 2j-FLOAT_N +[out] +def integral(): + r0, pos, r1, pos_2, r2, neg, r3, neg_2 :: object +L0: + r0 = (1+2j) + pos = r0 + r1 = (1+2j) + pos_2 = r1 + r2 = (1-2j) + neg = r2 + r3 = (-1+2j) + neg_2 = r3 + return 1 +def floating(): + r0, pos, r1, pos_2, r2, neg, r3, neg_2 :: object +L0: + r0 = (1.5+2j) + pos = r0 + r1 = (1.5+2j) + pos_2 = r1 + r2 = (1.5-2j) + neg = r2 + r3 = (-1.5+2j) + neg_2 = r3 return 1 diff --git a/mypyc/test-data/irbuild-dict.test b/mypyc/test-data/irbuild-dict.test index 99643b9451f0..6139a02029b9 100644 --- a/mypyc/test-data/irbuild-dict.test +++ b/mypyc/test-data/irbuild-dict.test @@ -21,7 +21,7 @@ def f(d: Dict[int, bool]) -> None: def f(d): d :: dict r0, r1 :: object - r2 :: int32 + r2 :: i32 r3 :: bit L0: r0 = object 0 @@ -83,14 +83,14 @@ def f(d: Dict[int, int]) -> bool: def f(d): d :: dict r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: bool L0: r0 = object 4 r1 = PyDict_Contains(d, r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool if r3 goto L1 else goto L2 :: bool L1: return 1 @@ -110,14 +110,14 @@ def f(d: Dict[int, int]) -> bool: def f(d): d :: dict r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3, r4 :: bool L0: r0 = object 4 r1 = PyDict_Contains(d, r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool r4 = r3 ^ 1 if r4 goto L1 else goto L2 :: bool L1: @@ -134,7 +134,7 @@ def f(a: Dict[int, int], b: Dict[int, int]) -> None: [out] def f(a, b): a, b :: dict - r0 :: int32 + r0 :: i32 r1 :: bit L0: r0 = CPyDict_Update(a, b) @@ -160,7 +160,7 @@ def increment(d): r7 :: object r8, k :: str r9, r10, r11 :: object - r12 :: int32 + r12 :: i32 r13, r14, r15 :: bit L0: r0 = 0 @@ -201,10 +201,10 @@ def f(x, y): r0 :: str r1 :: object r2 :: dict - r3 :: int32 + r3 :: i32 r4 :: bit r5 :: object - r6 :: int32 + r6 :: i32 r7 :: bit L0: r0 = 'z' @@ -219,6 +219,12 @@ L0: [case testDictIterationMethods] from typing import Dict, Union +from typing_extensions import TypedDict + +class Person(TypedDict): + name: str + age: int + def print_dict_methods(d1: Dict[int, int], d2: Dict[int, int]) -> None: for v in d1.values(): if v in d2: @@ -229,6 +235,10 @@ def union_of_dicts(d: Union[Dict[str, int], Dict[str, str]]) -> None: new = {} for k, v in d.items(): new[k] = int(v) +def typeddict(d: Person) -> None: + for k, v in d.items(): + if k == "name": + name = v [out] def print_dict_methods(d1, d2): d1, d2 :: dict @@ -242,7 +252,7 @@ def print_dict_methods(d1, d2): r7 :: object r8, v :: int r9 :: object - r10 :: int32 + r10 :: i32 r11 :: bit r12 :: bool r13, r14 :: bit @@ -256,7 +266,7 @@ def print_dict_methods(d1, d2): r22, r23 :: object r24, r25, k :: int r26, r27, r28, r29, r30 :: object - r31 :: int32 + r31 :: i32 r32, r33, r34 :: bit L0: r0 = 0 @@ -276,7 +286,7 @@ L2: r9 = box(int, v) r10 = PyDict_Contains(d2, r9) r11 = r10 >= 0 :: signed - r12 = truncate r10: int32 to builtins.bool + r12 = truncate r10: i32 to builtins.bool if r12 goto L3 else goto L4 :: bool L3: return 1 @@ -335,7 +345,7 @@ def union_of_dicts(d): r12, r13 :: object r14 :: int r15 :: object - r16 :: int32 + r16 :: i32 r17, r18, r19 :: bit L0: r0 = PyDict_New() @@ -370,6 +380,65 @@ L4: r19 = CPy_NoErrOccured() L5: return 1 +def typeddict(d): + d :: dict + r0 :: short_int + r1 :: native_int + r2 :: short_int + r3 :: object + r4 :: tuple[bool, short_int, object, object] + r5 :: short_int + r6 :: bool + r7, r8 :: object + r9, k :: str + v :: object + r10 :: str + r11 :: i32 + r12 :: bit + r13 :: object + r14, r15, r16 :: bit + name :: object + r17, r18 :: bit +L0: + r0 = 0 + r1 = PyDict_Size(d) + r2 = r1 << 1 + r3 = CPyDict_GetItemsIter(d) +L1: + r4 = CPyDict_NextItem(r3, r0) + r5 = r4[1] + r0 = r5 + r6 = r4[0] + if r6 goto L2 else goto L9 :: bool +L2: + r7 = r4[2] + r8 = r4[3] + r9 = cast(str, r7) + k = r9 + v = r8 + r10 = 'name' + r11 = PyUnicode_Compare(k, r10) + r12 = r11 == -1 + if r12 goto L3 else goto L5 :: bool +L3: + r13 = PyErr_Occurred() + r14 = r13 != 0 + if r14 goto L4 else goto L5 :: bool +L4: + r15 = CPy_KeepPropagating() +L5: + r16 = r11 == 0 + if r16 goto L6 else goto L7 :: bool +L6: + name = v +L7: +L8: + r17 = CPyDict_CheckSize(d, r2) + goto L1 +L9: + r18 = CPy_NoErrOccured() +L10: + return 1 [case testDictLoadAddress] def f() -> None: @@ -468,8 +537,8 @@ def f3(d, flag): r2 :: str r3 :: list r4 :: object - r5, r6 :: ptr - r7, r8 :: object + r5 :: ptr + r6, r7 :: object L0: if flag goto L1 else goto L2 :: bool L1: @@ -480,15 +549,14 @@ L2: r2 = 'a' r3 = PyList_New(1) r4 = object 1 - r5 = get_element_ptr r3 ob_item :: PyListObject - r6 = load_mem r5 :: ptr* - set_mem r6, r4 :: builtins.object* + r5 = list_items r3 + buf_init_item r5, 0, r4 keep_alive r3 - r7 = CPyDict_SetDefault(d, r2, r3) - return r7 + r6 = CPyDict_SetDefault(d, r2, r3) + return r6 L3: - r8 = box(None, 1) - return r8 + r7 = box(None, 1) + return r7 def f4(d, flag): d :: dict flag :: bool @@ -514,4 +582,3 @@ L2: L3: r7 = box(None, 1) return r7 - diff --git a/mypyc/test-data/irbuild-dunders.test b/mypyc/test-data/irbuild-dunders.test index 82f04dcdf687..1796a7e2160e 100644 --- a/mypyc/test-data/irbuild-dunders.test +++ b/mypyc/test-data/irbuild-dunders.test @@ -15,24 +15,16 @@ L0: def f(c): c :: __main__.C r0 :: int - r1 :: native_int - r2, r3, r4 :: bit - r5 :: bool + r1 :: bit + r2 :: bool L0: r0 = c.__len__() - r1 = r0 & 1 - r2 = r1 != 0 - if r2 goto L1 else goto L2 :: bool + r1 = int_ge r0, 0 + if r1 goto L2 else goto L1 :: bool L1: - r3 = CPyTagged_IsLt_(r0, 0) - if r3 goto L3 else goto L4 :: bool -L2: - r4 = r0 >= 0 :: signed - if r4 goto L4 else goto L3 :: bool -L3: - r5 = raise ValueError('__len__() should return >= 0') + r2 = raise ValueError('__len__() should return >= 0') unreachable -L4: +L2: return r0 [case testDundersSetItem] @@ -103,14 +95,14 @@ L0: def h(d): d :: __main__.D r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3, r4 :: bool L0: r0 = d.__contains__(14) r1 = PyObject_IsTrue(r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool r4 = r3 ^ 1 return r4 @@ -184,10 +176,8 @@ L0: return 6 def C.__float__(self): self :: __main__.C - r0 :: float L0: - r0 = 4.0 - return r0 + return 4.0 def C.__pos__(self): self :: __main__.C L0: @@ -223,4 +213,3 @@ L0: r6 = c.__bool__() r7 = c.__complex__() return 1 - diff --git a/mypyc/test-data/irbuild-float.test b/mypyc/test-data/irbuild-float.test new file mode 100644 index 000000000000..35e2eff62b86 --- /dev/null +++ b/mypyc/test-data/irbuild-float.test @@ -0,0 +1,497 @@ +[case testFloatAdd] +def f(x: float, y: float) -> float: + return x + y +def g(x: float) -> float: + z = x - 1.5 + return 2.5 * z +[out] +def f(x, y): + x, y, r0 :: float +L0: + r0 = x + y + return r0 +def g(x): + x, r0, z, r1 :: float +L0: + r0 = x - 1.5 + z = r0 + r1 = 2.5 * z + return r1 + +[case testFloatBoxAndUnbox] +from typing import Any +def f(x: float) -> object: + return x +def g(x: Any) -> float: + return x +[out] +def f(x): + x :: float + r0 :: object +L0: + r0 = box(float, x) + return r0 +def g(x): + x :: object + r0 :: float +L0: + r0 = unbox(float, x) + return r0 + +[case testFloatNegAndPos] +def f(x: float) -> float: + y = +x * -0.5 + return -y +[out] +def f(x): + x, r0, y, r1 :: float +L0: + r0 = x * -0.5 + y = r0 + r1 = -y + return r1 + +[case testFloatCoerceFromInt] +def from_int(x: int) -> float: + return x + +def from_literal() -> float: + return 5 + +def from_literal_neg() -> float: + return -2 +[out] +def from_int(x): + x :: int + r0 :: float +L0: + r0 = CPyFloat_FromTagged(x) + return r0 +def from_literal(): +L0: + return 5.0 +def from_literal_neg(): +L0: + return -2.0 + +[case testConvertBetweenFloatAndInt] +def to_int(x: float) -> int: + return int(x) +def from_int(x: int) -> float: + return float(x) +[out] +def to_int(x): + x :: float + r0 :: int +L0: + r0 = CPyTagged_FromFloat(x) + return r0 +def from_int(x): + x :: int + r0 :: float +L0: + r0 = CPyFloat_FromTagged(x) + return r0 + +[case testFloatOperatorAssignment] +def f(x: float, y: float) -> float: + x += y + x -= 5.0 + return x +[out] +def f(x, y): + x, y, r0, r1 :: float +L0: + r0 = x + y + x = r0 + r1 = x - 5.0 + x = r1 + return x + +[case testFloatOperatorAssignmentWithInt] +def f(x: float, y: int) -> None: + x += y + x -= 5 +[out] +def f(x, y): + x :: float + y :: int + r0, r1, r2 :: float +L0: + r0 = CPyFloat_FromTagged(y) + r1 = x + r0 + x = r1 + r2 = x - 5.0 + x = r2 + return 1 + +[case testFloatComparison] +def lt(x: float, y: float) -> bool: + return x < y +def eq(x: float, y: float) -> bool: + return x == y +[out] +def lt(x, y): + x, y :: float + r0 :: bit +L0: + r0 = x < y + return r0 +def eq(x, y): + x, y :: float + r0 :: bit +L0: + r0 = x == y + return r0 + +[case testFloatOpWithLiteralInt] +def f(x: float) -> None: + y = x * 2 + z = 1 - y + b = z < 3 + c = 0 == z +[out] +def f(x): + x, r0, y, r1, z :: float + r2 :: bit + b :: bool + r3 :: bit + c :: bool +L0: + r0 = x * 2.0 + y = r0 + r1 = 1.0 - y + z = r1 + r2 = z < 3.0 + b = r2 + r3 = 0.0 == z + c = r3 + return 1 + +[case testFloatCallFunctionWithLiteralInt] +def f(x: float) -> None: pass + +def g() -> None: + f(3) + f(-2) +[out] +def f(x): + x :: float +L0: + return 1 +def g(): + r0, r1 :: None +L0: + r0 = f(3.0) + r1 = f(-2.0) + return 1 + +[case testFloatAsBool] +def f(x: float) -> int: + if x: + return 2 + else: + return 5 +[out] +def f(x): + x :: float + r0 :: bit +L0: + r0 = x != 0.0 + if r0 goto L1 else goto L2 :: bool +L1: + return 4 +L2: + return 10 +L3: + unreachable + +[case testCallSqrtViaMathModule] +import math + +def f(x: float) -> float: + return math.sqrt(x) +[out] +def f(x): + x, r0 :: float +L0: + r0 = CPyFloat_Sqrt(x) + return r0 + +[case testFloatFinalConstant] +from typing_extensions import Final + +X: Final = 123.0 +Y: Final = -1.0 + +def f() -> float: + a = X + return a + Y +[out] +def f(): + a, r0 :: float +L0: + a = 123.0 + r0 = a + -1.0 + return r0 + +[case testFloatDefaultArg] +def f(x: float = 1.5) -> float: + return x +[out] +def f(x, __bitmap): + x :: float + __bitmap, r0 :: u32 + r1 :: bit +L0: + r0 = __bitmap & 1 + r1 = r0 == 0 + if r1 goto L1 else goto L2 :: bool +L1: + x = 1.5 +L2: + return x + +[case testFloatMixedOperations] +def f(x: float, y: int) -> None: + if x < y: + z = x + y + x -= y + z = y + z + if y == x: + x -= 1 +[out] +def f(x, y): + x :: float + y :: int + r0 :: float + r1 :: bit + r2, r3, z, r4, r5, r6, r7, r8 :: float + r9 :: bit + r10 :: float +L0: + r0 = CPyFloat_FromTagged(y) + r1 = x < r0 + if r1 goto L1 else goto L2 :: bool +L1: + r2 = CPyFloat_FromTagged(y) + r3 = x + r2 + z = r3 + r4 = CPyFloat_FromTagged(y) + r5 = x - r4 + x = r5 + r6 = CPyFloat_FromTagged(y) + r7 = r6 + z + z = r7 +L2: + r8 = CPyFloat_FromTagged(y) + r9 = r8 == x + if r9 goto L3 else goto L4 :: bool +L3: + r10 = x - 1.0 + x = r10 +L4: + return 1 + +[case testFloatDivideSimple] +def f(x: float, y: float) -> float: + z = x / y + z = z / 2.0 + return z / 3 +[out] +def f(x, y): + x, y :: float + r0 :: bit + r1 :: bool + r2, z, r3, r4 :: float +L0: + r0 = y == 0.0 + if r0 goto L1 else goto L2 :: bool +L1: + r1 = raise ZeroDivisionError('float division by zero') + unreachable +L2: + r2 = x / y + z = r2 + r3 = z / 2.0 + z = r3 + r4 = z / 3.0 + return r4 + +[case testFloatDivideIntOperand] +def f(n: int, m: int) -> float: + return n / m +[out] +def f(n, m): + n, m :: int + r0 :: float +L0: + r0 = CPyTagged_TrueDivide(n, m) + return r0 + +[case testFloatResultOfIntDivide] +def f(f: float, n: int) -> float: + x = f / n + return n / x +[out] +def f(f, n): + f :: float + n :: int + r0 :: float + r1 :: bit + r2 :: bool + r3, x, r4 :: float + r5 :: bit + r6 :: bool + r7 :: float +L0: + r0 = CPyFloat_FromTagged(n) + r1 = r0 == 0.0 + if r1 goto L1 else goto L2 :: bool +L1: + r2 = raise ZeroDivisionError('float division by zero') + unreachable +L2: + r3 = f / r0 + x = r3 + r4 = CPyFloat_FromTagged(n) + r5 = x == 0.0 + if r5 goto L3 else goto L4 :: bool +L3: + r6 = raise ZeroDivisionError('float division by zero') + unreachable +L4: + r7 = r4 / x + return r7 + +[case testFloatExplicitConversions] +def f(f: float, n: int) -> int: + x = float(n) + y = float(x) # no-op + return int(y) +[out] +def f(f, n): + f :: float + n :: int + r0, x, y :: float + r1 :: int +L0: + r0 = CPyFloat_FromTagged(n) + x = r0 + y = x + r1 = CPyTagged_FromFloat(y) + return r1 + +[case testFloatModulo] +def f(x: float, y: float) -> float: + return x % y +[out] +def f(x, y): + x, y :: float + r0 :: bit + r1 :: bool + r2, r3 :: float + r4, r5, r6, r7 :: bit + r8, r9 :: float +L0: + r0 = y == 0.0 + if r0 goto L1 else goto L2 :: bool +L1: + r1 = raise ZeroDivisionError('float modulo') + unreachable +L2: + r2 = x % y + r3 = r2 + r4 = r3 == 0.0 + if r4 goto L5 else goto L3 :: bool +L3: + r5 = x < 0.0 + r6 = y < 0.0 + r7 = r5 == r6 + if r7 goto L6 else goto L4 :: bool +L4: + r8 = r3 + y + r3 = r8 + goto L6 +L5: + r9 = copysign(0.0, y) + r3 = r9 +L6: + return r3 + +[case testFloatFloorDivide] +def f(x: float, y: float) -> float: + return x // y +def g(x: float, y: int) -> float: + return x // y +[out] +def f(x, y): + x, y, r0 :: float +L0: + r0 = CPyFloat_FloorDivide(x, y) + return r0 +def g(x, y): + x :: float + y :: int + r0, r1 :: float +L0: + r0 = CPyFloat_FromTagged(y) + r1 = CPyFloat_FloorDivide(x, r0) + return r1 + +[case testFloatNarrowToIntDisallowed] +class C: + x: float + +def narrow_local(x: float, n: int) -> int: + x = n # E: Incompatible value representations in assignment (expression has type "int", variable has type "float") + return x + +def narrow_tuple_lvalue(x: float, y: float, n: int) -> int: + x, y = 1.0, n # E: Incompatible value representations in assignment (expression has type "int", variable has type "float") + return y + +def narrow_multiple_lvalues(x: float, y: float, n: int) -> int: + x = a = n # E: Incompatible value representations in assignment (expression has type "int", variable has type "float") + a = y = n # E: Incompatible value representations in assignment (expression has type "int", variable has type "float") + return x + y + +def narrow_attribute(c: C, n: int) -> int: + c.x = n # E: Incompatible value representations in assignment (expression has type "int", variable has type "float") + return c.x + +def narrow_using_int_literal(x: float) -> int: + x = 1 # E: Incompatible value representations in assignment (expression has type "int", variable has type "float") + return x + +def narrow_using_declaration(n: int) -> int: + x: float + x = n # E: Incompatible value representations in assignment (expression has type "int", variable has type "float") + return x + +[case testFloatInitializeFromInt] +def init(n: int) -> None: + # These are strictly speaking safe, since these don't narrow, but for consistency with + # narrowing assignments, generate errors here + x: float = n # E: Incompatible value representations in assignment (expression has type "int", variable has type "float") + y: float = 5 # E: Incompatible value representations in assignment (expression has type "int", variable has type "float") + +[case testFloatCoerceTupleFromIntValues] +from __future__ import annotations + +def f(x: int) -> None: + t: tuple[float, float, float] = (x, 2.5, -7) +[out] +def f(x): + x :: int + r0 :: tuple[int, float, int] + r1 :: int + r2 :: float + r3, t :: tuple[float, float, float] +L0: + r0 = (x, 2.5, -14) + r1 = r0[0] + r2 = CPyFloat_FromTagged(r1) + r3 = (r2, 2.5, -7.0) + t = r3 + return 1 diff --git a/mypyc/test-data/irbuild-generics.test b/mypyc/test-data/irbuild-generics.test index fe4a94992717..4f9d0ab83a16 100644 --- a/mypyc/test-data/irbuild-generics.test +++ b/mypyc/test-data/irbuild-generics.test @@ -17,13 +17,12 @@ def g(x): x :: list r0 :: object r1 :: list - r2, r3 :: ptr + r2 :: ptr L0: r0 = CPyList_GetItemShort(x, 0) r1 = PyList_New(1) - r2 = get_element_ptr r1 ob_item :: PyListObject - r3 = load_mem r2 :: ptr* - set_mem r3, r0 :: builtins.object* + r2 = list_items r1 + buf_init_item r2, 0, r0 keep_alive r1 return r1 def h(x, y): @@ -132,7 +131,7 @@ def f(x: T, y: T) -> T: [out] def f(x, y): x, y, r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: bool r4 :: object @@ -140,7 +139,7 @@ L0: r0 = PyObject_RichCompare(y, x, 4) r1 = PyObject_IsTrue(r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool if r3 goto L1 else goto L2 :: bool L1: r4 = y diff --git a/mypyc/test-data/irbuild-glue-methods.test b/mypyc/test-data/irbuild-glue-methods.test index 6d749bf5dd84..3012c79586f2 100644 --- a/mypyc/test-data/irbuild-glue-methods.test +++ b/mypyc/test-data/irbuild-glue-methods.test @@ -343,8 +343,8 @@ L0: return 1 def D.f(self, x, __bitmap): self :: __main__.D - x :: int64 - __bitmap, r0 :: uint32 + x :: i64 + __bitmap, r0 :: u32 r1 :: bit L0: r0 = __bitmap & 1 @@ -371,8 +371,8 @@ class D(C): [out] def C.f(self, x, __bitmap): self :: __main__.C - x :: int64 - __bitmap, r0 :: uint32 + x :: i64 + __bitmap, r0 :: u32 r1 :: bit L0: r0 = __bitmap & 1 @@ -384,10 +384,10 @@ L2: return 1 def D.f(self, x, y, __bitmap): self :: __main__.D - x, y :: int64 - __bitmap, r0 :: uint32 + x, y :: i64 + __bitmap, r0 :: u32 r1 :: bit - r2 :: uint32 + r2 :: u32 r3 :: bit L0: r0 = __bitmap & 1 @@ -405,8 +405,8 @@ L4: return 1 def D.f__C_glue(self, x, __bitmap): self :: __main__.D - x :: int64 - __bitmap :: uint32 + x :: i64 + __bitmap :: u32 r0 :: None L0: r0 = D.f(self, x, 0, __bitmap) @@ -432,6 +432,6 @@ class E: class EE(E): def f(self, x: int) -> None: pass # Line 18 [out] -main:7: error: An argument with type "int64" cannot be given a default value in a method override -main:13: error: Incompatible argument type "int64" (base class has type "int") -main:18: error: Incompatible argument type "int" (base class has type "int64") +main:7: error: An argument with type "i64" cannot be given a default value in a method override +main:13: error: Incompatible argument type "i64" (base class has type "int") +main:18: error: Incompatible argument type "int" (base class has type "i64") diff --git a/mypyc/test-data/irbuild-i16.test b/mypyc/test-data/irbuild-i16.test new file mode 100644 index 000000000000..a03c9df2c6ac --- /dev/null +++ b/mypyc/test-data/irbuild-i16.test @@ -0,0 +1,526 @@ +# Test cases for i16 native ints. Focus on things that are different from i64; no need to +# duplicate all i64 test cases here. + +[case testI16BinaryOp] +from mypy_extensions import i16 + +def add_op(x: i16, y: i16) -> i16: + x = y + x + y = x + 5 + y += x + y += 7 + x = 5 + y + return x +def compare(x: i16, y: i16) -> None: + a = x == y + b = x == -5 + c = x < y + d = x < -5 + e = -5 == x + f = -5 < x +[out] +def add_op(x, y): + x, y, r0, r1, r2, r3, r4 :: i16 +L0: + r0 = y + x + x = r0 + r1 = x + 5 + y = r1 + r2 = y + x + y = r2 + r3 = y + 7 + y = r3 + r4 = 5 + y + x = r4 + return x +def compare(x, y): + x, y :: i16 + r0 :: bit + a :: bool + r1 :: bit + b :: bool + r2 :: bit + c :: bool + r3 :: bit + d :: bool + r4 :: bit + e :: bool + r5 :: bit + f :: bool +L0: + r0 = x == y + a = r0 + r1 = x == -5 + b = r1 + r2 = x < y :: signed + c = r2 + r3 = x < -5 :: signed + d = r3 + r4 = -5 == x + e = r4 + r5 = -5 < x :: signed + f = r5 + return 1 + +[case testI16UnaryOp] +from mypy_extensions import i16 + +def unary(x: i16) -> i16: + y = -x + x = ~y + y = +x + return y +[out] +def unary(x): + x, r0, y, r1 :: i16 +L0: + r0 = 0 - x + y = r0 + r1 = y ^ -1 + x = r1 + y = x + return y + +[case testI16DivisionByConstant] +from mypy_extensions import i16 + +def div_by_constant(x: i16) -> i16: + x = x // 5 + x //= 17 + return x +[out] +def div_by_constant(x): + x, r0, r1 :: i16 + r2, r3, r4 :: bit + r5 :: i16 + r6 :: bit + r7, r8, r9 :: i16 + r10, r11, r12 :: bit + r13 :: i16 + r14 :: bit + r15 :: i16 +L0: + r0 = x / 5 + r1 = r0 + r2 = x < 0 :: signed + r3 = 5 < 0 :: signed + r4 = r2 == r3 + if r4 goto L3 else goto L1 :: bool +L1: + r5 = r1 * 5 + r6 = r5 == x + if r6 goto L3 else goto L2 :: bool +L2: + r7 = r1 - 1 + r1 = r7 +L3: + x = r1 + r8 = x / 17 + r9 = r8 + r10 = x < 0 :: signed + r11 = 17 < 0 :: signed + r12 = r10 == r11 + if r12 goto L6 else goto L4 :: bool +L4: + r13 = r9 * 17 + r14 = r13 == x + if r14 goto L6 else goto L5 :: bool +L5: + r15 = r9 - 1 + r9 = r15 +L6: + x = r9 + return x + +[case testI16ModByConstant] +from mypy_extensions import i16 + +def mod_by_constant(x: i16) -> i16: + x = x % 5 + x %= 17 + return x +[out] +def mod_by_constant(x): + x, r0, r1 :: i16 + r2, r3, r4, r5 :: bit + r6, r7, r8 :: i16 + r9, r10, r11, r12 :: bit + r13 :: i16 +L0: + r0 = x % 5 + r1 = r0 + r2 = x < 0 :: signed + r3 = 5 < 0 :: signed + r4 = r2 == r3 + if r4 goto L3 else goto L1 :: bool +L1: + r5 = r1 == 0 + if r5 goto L3 else goto L2 :: bool +L2: + r6 = r1 + 5 + r1 = r6 +L3: + x = r1 + r7 = x % 17 + r8 = r7 + r9 = x < 0 :: signed + r10 = 17 < 0 :: signed + r11 = r9 == r10 + if r11 goto L6 else goto L4 :: bool +L4: + r12 = r8 == 0 + if r12 goto L6 else goto L5 :: bool +L5: + r13 = r8 + 17 + r8 = r13 +L6: + x = r8 + return x + +[case testI16DivModByVariable] +from mypy_extensions import i16 + +def divmod(x: i16, y: i16) -> i16: + a = x // y + return a % y +[out] +def divmod(x, y): + x, y, r0, a, r1 :: i16 +L0: + r0 = CPyInt16_Divide(x, y) + a = r0 + r1 = CPyInt16_Remainder(a, y) + return r1 + +[case testI16BinaryOperationWithOutOfRangeOperand] +from mypy_extensions import i16 + +def out_of_range(x: i16) -> None: + x + (-32769) + (-32770) + x + x * 32768 + x + 32767 # OK + (-32768) + x # OK +[out] +main:4: error: Value -32769 is out of range for "i16" +main:5: error: Value -32770 is out of range for "i16" +main:6: error: Value 32768 is out of range for "i16" + +[case testI16BoxAndUnbox] +from typing import Any +from mypy_extensions import i16 + +def f(x: Any) -> Any: + y: i16 = x + return y +[out] +def f(x): + x :: object + r0, y :: i16 + r1 :: object +L0: + r0 = unbox(i16, x) + y = r0 + r1 = box(i16, y) + return r1 + +[case testI16MixedCompare1] +from mypy_extensions import i16 +def f(x: int, y: i16) -> bool: + return x == y +[out] +def f(x, y): + x :: int + y :: i16 + r0 :: native_int + r1, r2, r3 :: bit + r4 :: native_int + r5, r6 :: i16 + r7 :: bit +L0: + r0 = x & 1 + r1 = r0 == 0 + if r1 goto L1 else goto L4 :: bool +L1: + r2 = x < 65536 :: signed + if r2 goto L2 else goto L4 :: bool +L2: + r3 = x >= -65536 :: signed + if r3 goto L3 else goto L4 :: bool +L3: + r4 = x >> 1 + r5 = truncate r4: native_int to i16 + r6 = r5 + goto L5 +L4: + CPyInt16_Overflow() + unreachable +L5: + r7 = r6 == y + return r7 + +[case testI16MixedCompare2] +from mypy_extensions import i16 +def f(x: i16, y: int) -> bool: + return x == y +[out] +def f(x, y): + x :: i16 + y :: int + r0 :: native_int + r1, r2, r3 :: bit + r4 :: native_int + r5, r6 :: i16 + r7 :: bit +L0: + r0 = y & 1 + r1 = r0 == 0 + if r1 goto L1 else goto L4 :: bool +L1: + r2 = y < 65536 :: signed + if r2 goto L2 else goto L4 :: bool +L2: + r3 = y >= -65536 :: signed + if r3 goto L3 else goto L4 :: bool +L3: + r4 = y >> 1 + r5 = truncate r4: native_int to i16 + r6 = r5 + goto L5 +L4: + CPyInt16_Overflow() + unreachable +L5: + r7 = x == r6 + return r7 + +[case testI16ConvertToInt] +from mypy_extensions import i16 + +def i16_to_int(a: i16) -> int: + return a +[out] +def i16_to_int(a): + a :: i16 + r0 :: native_int + r1 :: int +L0: + r0 = extend signed a: i16 to native_int + r1 = r0 << 1 + return r1 + +[case testI16OperatorAssignmentMixed] +from mypy_extensions import i16 + +def f(a: i16) -> None: + x = 0 + x += a +[out] +def f(a): + a :: i16 + x :: int + r0 :: native_int + r1, r2, r3 :: bit + r4 :: native_int + r5, r6, r7 :: i16 + r8 :: native_int + r9 :: int +L0: + x = 0 + r0 = x & 1 + r1 = r0 == 0 + if r1 goto L1 else goto L4 :: bool +L1: + r2 = x < 65536 :: signed + if r2 goto L2 else goto L4 :: bool +L2: + r3 = x >= -65536 :: signed + if r3 goto L3 else goto L4 :: bool +L3: + r4 = x >> 1 + r5 = truncate r4: native_int to i16 + r6 = r5 + goto L5 +L4: + CPyInt16_Overflow() + unreachable +L5: + r7 = r6 + a + r8 = extend signed r7: i16 to native_int + r9 = r8 << 1 + x = r9 + return 1 + +[case testI16InitializeFromLiteral] +from mypy_extensions import i16, i64 + +def f() -> None: + x: i16 = 0 + y: i16 = -127 + z: i16 = 5 + 7 +[out] +def f(): + x, y, z :: i16 +L0: + x = 0 + y = -127 + z = 12 + return 1 + +[case testI16ExplicitConversionFromNativeInt] +from mypy_extensions import i64, i32, i16 + +def from_i16(x: i16) -> i16: + return i16(x) + +def from_i32(x: i32) -> i16: + return i16(x) + +def from_i64(x: i64) -> i16: + return i16(x) +[out] +def from_i16(x): + x :: i16 +L0: + return x +def from_i32(x): + x :: i32 + r0 :: i16 +L0: + r0 = truncate x: i32 to i16 + return r0 +def from_i64(x): + x :: i64 + r0 :: i16 +L0: + r0 = truncate x: i64 to i16 + return r0 + +[case testI16ExplicitConversionFromInt] +from mypy_extensions import i16 + +def f(x: int) -> i16: + return i16(x) +[out] +def f(x): + x :: int + r0 :: native_int + r1, r2, r3 :: bit + r4 :: native_int + r5, r6 :: i16 +L0: + r0 = x & 1 + r1 = r0 == 0 + if r1 goto L1 else goto L4 :: bool +L1: + r2 = x < 65536 :: signed + if r2 goto L2 else goto L4 :: bool +L2: + r3 = x >= -65536 :: signed + if r3 goto L3 else goto L4 :: bool +L3: + r4 = x >> 1 + r5 = truncate r4: native_int to i16 + r6 = r5 + goto L5 +L4: + CPyInt16_Overflow() + unreachable +L5: + return r6 + +[case testI16ExplicitConversionFromLiteral] +from mypy_extensions import i16 + +def f() -> None: + x = i16(0) + y = i16(11) + z = i16(-3) + a = i16(32767) + b = i16(32768) # Truncate + c = i16(-32768) + d = i16(-32769) # Truncate +[out] +def f(): + x, y, z, a, b, c, d :: i16 +L0: + x = 0 + y = 11 + z = -3 + a = 32767 + b = -32768 + c = -32768 + d = 32767 + return 1 + +[case testI16ExplicitConversionFromVariousTypes] +from mypy_extensions import i16 + +def bool_to_i16(b: bool) -> i16: + return i16(b) + +def str_to_i16(s: str) -> i16: + return i16(s) + +class C: + def __int__(self) -> i16: + return 5 + +def instance_to_i16(c: C) -> i16: + return i16(c) + +def float_to_i16(x: float) -> i16: + return i16(x) +[out] +def bool_to_i16(b): + b :: bool + r0 :: i16 +L0: + r0 = extend b: builtins.bool to i16 + return r0 +def str_to_i16(s): + s :: str + r0 :: object + r1 :: i16 +L0: + r0 = CPyLong_FromStr(s) + r1 = unbox(i16, r0) + return r1 +def C.__int__(self): + self :: __main__.C +L0: + return 5 +def instance_to_i16(c): + c :: __main__.C + r0 :: i16 +L0: + r0 = c.__int__() + return r0 +def float_to_i16(x): + x :: float + r0 :: int + r1 :: native_int + r2, r3, r4 :: bit + r5 :: native_int + r6, r7 :: i16 +L0: + r0 = CPyTagged_FromFloat(x) + r1 = r0 & 1 + r2 = r1 == 0 + if r2 goto L1 else goto L4 :: bool +L1: + r3 = r0 < 65536 :: signed + if r3 goto L2 else goto L4 :: bool +L2: + r4 = r0 >= -65536 :: signed + if r4 goto L3 else goto L4 :: bool +L3: + r5 = r0 >> 1 + r6 = truncate r5: native_int to i16 + r7 = r6 + goto L5 +L4: + CPyInt16_Overflow() + unreachable +L5: + return r7 diff --git a/mypyc/test-data/irbuild-i32.test b/mypyc/test-data/irbuild-i32.test index 7ea3c0864728..7dcb722ec906 100644 --- a/mypyc/test-data/irbuild-i32.test +++ b/mypyc/test-data/irbuild-i32.test @@ -20,7 +20,7 @@ def compare(x: i32, y: i32) -> None: f = -5 < x [out] def add_op(x, y): - x, y, r0, r1, r2, r3, r4 :: int32 + x, y, r0, r1, r2, r3, r4 :: i32 L0: r0 = y + x x = r0 @@ -34,7 +34,7 @@ L0: x = r4 return x def compare(x, y): - x, y :: int32 + x, y :: i32 r0 :: bit a :: bool r1 :: bit @@ -72,7 +72,7 @@ def unary(x: i32) -> i32: return y [out] def unary(x): - x, r0, y, r1 :: int32 + x, r0, y, r1 :: i32 L0: r0 = 0 - x y = r0 @@ -90,15 +90,15 @@ def div_by_constant(x: i32) -> i32: return x [out] def div_by_constant(x): - x, r0, r1 :: int32 + x, r0, r1 :: i32 r2, r3, r4 :: bit - r5 :: int32 + r5 :: i32 r6 :: bit - r7, r8, r9 :: int32 + r7, r8, r9 :: i32 r10, r11, r12 :: bit - r13 :: int32 + r13 :: i32 r14 :: bit - r15 :: int32 + r15 :: i32 L0: r0 = x / 5 r1 = r0 @@ -141,11 +141,11 @@ def mod_by_constant(x: i32) -> i32: return x [out] def mod_by_constant(x): - x, r0, r1 :: int32 + x, r0, r1 :: i32 r2, r3, r4, r5 :: bit - r6, r7, r8 :: int32 + r6, r7, r8 :: i32 r9, r10, r11, r12 :: bit - r13 :: int32 + r13 :: i32 L0: r0 = x % 5 r1 = r0 @@ -185,7 +185,7 @@ def divmod(x: i32, y: i32) -> i32: return a % y [out] def divmod(x, y): - x, y, r0, a, r1 :: int32 + x, y, r0, a, r1 :: i32 L0: r0 = CPyInt32_Divide(x, y) a = r0 @@ -202,12 +202,12 @@ def f(x: Any) -> Any: [out] def f(x): x :: object - r0, y :: int32 + r0, y :: i32 r1 :: object L0: - r0 = unbox(int32, x) + r0 = unbox(i32, x) y = r0 - r1 = box(int32, y) + r1 = box(i32, y) return r1 [case testI32MixedCompare1_64bit] @@ -217,11 +217,11 @@ def f(x: int, y: i32) -> bool: [out] def f(x, y): x :: int - y :: int32 + y :: i32 r0 :: native_int r1, r2, r3 :: bit r4 :: native_int - r5, r6 :: int32 + r5, r6 :: i32 r7 :: bit L0: r0 = x & 1 @@ -235,7 +235,7 @@ L2: if r3 goto L3 else goto L4 :: bool L3: r4 = x >> 1 - r5 = truncate r4: native_int to int32 + r5 = truncate r4: native_int to i32 r6 = r5 goto L5 L4: @@ -251,12 +251,12 @@ def f(x: i32, y: int) -> bool: return x == y [out] def f(x, y): - x :: int32 + x :: i32 y :: int r0 :: native_int r1, r2, r3 :: bit r4 :: native_int - r5, r6 :: int32 + r5, r6 :: i32 r7 :: bit L0: r0 = y & 1 @@ -270,7 +270,7 @@ L2: if r3 goto L3 else goto L4 :: bool L3: r4 = y >> 1 - r5 = truncate r4: native_int to int32 + r5 = truncate r4: native_int to i32 r6 = r5 goto L5 L4: @@ -287,13 +287,13 @@ def f(x: int, y: i32) -> bool: [out] def f(x, y): x :: int - y :: int32 + y :: i32 r0 :: native_int r1 :: bit - r2, r3 :: int32 + r2, r3 :: i32 r4 :: ptr r5 :: c_ptr - r6 :: int32 + r6 :: i32 r7 :: bit L0: r0 = x & 1 @@ -320,11 +320,11 @@ def i32_to_int(a: i32) -> int: return a [out] def i32_to_int(a): - a :: int32 + a :: i32 r0 :: native_int r1 :: int L0: - r0 = extend signed a: int32 to native_int + r0 = extend signed a: i32 to native_int r1 = r0 << 1 return r1 @@ -335,7 +335,7 @@ def i32_to_int(a: i32) -> int: return a [out] def i32_to_int(a): - a :: int32 + a :: i32 r0, r1 :: bit r2, r3, r4 :: int L0: @@ -362,12 +362,12 @@ def f(a: i32) -> None: x += a [out] def f(a): - a :: int32 + a :: i32 x :: int r0 :: native_int r1, r2, r3 :: bit r4 :: native_int - r5, r6, r7 :: int32 + r5, r6, r7 :: i32 r8 :: native_int r9 :: int L0: @@ -383,7 +383,7 @@ L2: if r3 goto L3 else goto L4 :: bool L3: r4 = x >> 1 - r5 = truncate r4: native_int to int32 + r5 = truncate r4: native_int to i32 r6 = r5 goto L5 L4: @@ -391,7 +391,7 @@ L4: unreachable L5: r7 = r6 + a - r8 = extend signed r7: int32 to native_int + r8 = extend signed r7: i32 to native_int r9 = r8 << 1 x = r9 return 1 @@ -405,7 +405,7 @@ def f() -> None: z: i32 = 5 + 7 [out] def f(): - x, y, z :: int32 + x, y, z :: i32 L0: x = 0 y = -127 @@ -413,7 +413,10 @@ L0: return 1 [case testI32ExplicitConversionFromNativeInt] -from mypy_extensions import i64, i32 +from mypy_extensions import i64, i32, i16 + +def from_i16(x: i16) -> i32: + return i32(x) def from_i32(x: i32) -> i32: return i32(x) @@ -421,15 +424,21 @@ def from_i32(x: i32) -> i32: def from_i64(x: i64) -> i32: return i32(x) [out] +def from_i16(x): + x :: i16 + r0 :: i32 +L0: + r0 = extend signed x: i16 to i32 + return r0 def from_i32(x): - x :: int32 + x :: i32 L0: return x def from_i64(x): - x :: int64 - r0 :: int32 + x :: i64 + r0 :: i32 L0: - r0 = truncate x: int64 to int32 + r0 = truncate x: i64 to i32 return r0 [case testI32ExplicitConversionFromInt_64bit] @@ -443,7 +452,7 @@ def f(x): r0 :: native_int r1, r2, r3 :: bit r4 :: native_int - r5, r6 :: int32 + r5, r6 :: i32 L0: r0 = x & 1 r1 = r0 == 0 @@ -456,7 +465,7 @@ L2: if r3 goto L3 else goto L4 :: bool L3: r4 = x >> 1 - r5 = truncate r4: native_int to int32 + r5 = truncate r4: native_int to i32 r6 = r5 goto L5 L4: @@ -465,23 +474,25 @@ L4: L5: return r6 -[case testI32ExplicitConversionFromLiteral] +[case testI32ExplicitConversionFromLiteral_64bit] from mypy_extensions import i32 def f() -> None: x = i32(0) y = i32(11) z = i32(-3) + a = i32(2**31) [out] def f(): - x, y, z :: int32 + x, y, z, a :: i32 L0: x = 0 y = 11 z = -3 + a = -2147483648 return 1 -[case testI32ExplicitConversionFromVariousTypes] +[case testI32ExplicitConversionFromVariousTypes_64bit] from mypy_extensions import i32 def bool_to_i32(b: bool) -> i32: @@ -502,17 +513,17 @@ def float_to_i32(x: float) -> i32: [out] def bool_to_i32(b): b :: bool - r0 :: int32 + r0 :: i32 L0: - r0 = extend b: builtins.bool to int32 + r0 = extend b: builtins.bool to i32 return r0 def str_to_i32(s): s :: str r0 :: object - r1 :: int32 + r1 :: i32 L0: r0 = CPyLong_FromStr(s) - r1 = unbox(int32, r0) + r1 = unbox(i32, r0) return r1 def C.__int__(self): self :: __main__.C @@ -520,15 +531,68 @@ L0: return 5 def instance_to_i32(c): c :: __main__.C - r0 :: int32 + r0 :: i32 L0: r0 = c.__int__() return r0 def float_to_i32(x): x :: float - r0 :: object - r1 :: int32 + r0 :: int + r1 :: native_int + r2, r3, r4 :: bit + r5 :: native_int + r6, r7 :: i32 L0: - r0 = CPyLong_FromFloat(x) - r1 = unbox(int32, r0) - return r1 + r0 = CPyTagged_FromFloat(x) + r1 = r0 & 1 + r2 = r1 == 0 + if r2 goto L1 else goto L4 :: bool +L1: + r3 = r0 < 4294967296 :: signed + if r3 goto L2 else goto L4 :: bool +L2: + r4 = r0 >= -4294967296 :: signed + if r4 goto L3 else goto L4 :: bool +L3: + r5 = r0 >> 1 + r6 = truncate r5: native_int to i32 + r7 = r6 + goto L5 +L4: + CPyInt32_Overflow() + unreachable +L5: + return r7 + +[case testI32ExplicitConversionFromFloat_32bit] +from mypy_extensions import i32 + +def float_to_i32(x: float) -> i32: + return i32(x) +[out] +def float_to_i32(x): + x :: float + r0 :: int + r1 :: native_int + r2 :: bit + r3, r4 :: i32 + r5 :: ptr + r6 :: c_ptr + r7 :: i32 +L0: + r0 = CPyTagged_FromFloat(x) + r1 = r0 & 1 + r2 = r1 == 0 + if r2 goto L1 else goto L2 :: bool +L1: + r3 = r0 >> 1 + r4 = r3 + goto L3 +L2: + r5 = r0 ^ 1 + r6 = r5 + r7 = CPyLong_AsInt32(r6) + r4 = r7 + keep_alive r0 +L3: + return r4 diff --git a/mypyc/test-data/irbuild-i64.test b/mypyc/test-data/irbuild-i64.test index f616893d8fe5..a52de16f3a6c 100644 --- a/mypyc/test-data/irbuild-i64.test +++ b/mypyc/test-data/irbuild-i64.test @@ -7,7 +7,7 @@ def f() -> i64: return y [out] def f(): - x, y :: int64 + x, y :: i64 L0: x = 5 y = x @@ -40,7 +40,7 @@ def all_comparisons(x: i64) -> int: return y [out] def min(x, y): - x, y :: int64 + x, y :: i64 r0 :: bit L0: r0 = x < y :: signed @@ -52,7 +52,7 @@ L2: L3: unreachable def all_comparisons(x): - x :: int64 + x :: i64 r0 :: bit y :: int r1, r2, r3, r4, r5 :: bit @@ -110,7 +110,7 @@ def f(x: i64, y: i64) -> i64: return y - z [out] def f(x, y): - x, y, r0, z, r1 :: int64 + x, y, r0, z, r1 :: i64 L0: r0 = x + y z = r0 @@ -125,7 +125,7 @@ def f() -> i64: return -i [out] def f(): - i, r0 :: int64 + i, r0 :: i64 L0: i = -3 r0 = 0 - i @@ -140,7 +140,7 @@ def unary(x: i64) -> i64: return x [out] def unary(x): - x, r0, y :: int64 + x, r0, y :: i64 L0: r0 = x ^ -1 y = r0 @@ -157,12 +157,12 @@ def f(a: Any) -> None: [out] def f(a): a :: object - r0, b :: int64 + r0, b :: i64 r1 :: object L0: - r0 = unbox(int64, a) + r0 = unbox(i64, a) b = r0 - r1 = box(int64, b) + r1 = box(i64, b) a = r1 return 1 @@ -177,20 +177,20 @@ def set(a: List[i64], i: i64, x: i64) -> None: [out] def get(a, i): a :: list - i :: int64 + i :: i64 r0 :: object - r1 :: int64 + r1 :: i64 L0: r0 = CPyList_GetItemInt64(a, i) - r1 = unbox(int64, r0) + r1 = unbox(i64, r0) return r1 def set(a, i, x): a :: list - i, x :: int64 + i, x :: i64 r0 :: object r1 :: bit L0: - r0 = box(int64, x) + r0 = box(i64, x) r1 = CPyList_SetItemInt64(a, i, r0) return 1 @@ -203,7 +203,7 @@ def f() -> i64: return 3 - b [out] def f(): - a, r0, b, r1 :: int64 + a, r0, b, r1 :: i64 L0: a = 1 r0 = a + 2 @@ -222,7 +222,7 @@ def f(a: i64) -> i64: return 3 [out] def f(a): - a :: int64 + a :: i64 r0, r1 :: bit L0: r0 = a < 3 :: signed @@ -257,7 +257,7 @@ def others(a: i64, b: i64) -> i64: return a [out] def add(a): - a, b, r0, r1 :: int64 + a, b, r0, r1 :: i64 L0: b = a r0 = b + 1 @@ -266,7 +266,7 @@ L0: a = r1 return a def others(a, b): - a, b, r0, r1, r2, r3, r4, r5, r6 :: int64 + a, b, r0, r1, r2, r3, r4, r5, r6 :: i64 L0: r0 = a - b a = r0 @@ -307,7 +307,7 @@ def unary(a: i64) -> i64: return ~a [out] def forward(a, b): - a, b, r0, r1, r2, r3, r4 :: int64 + a, b, r0, r1, r2, r3, r4 :: i64 L0: r0 = a & 1 b = r0 @@ -321,7 +321,7 @@ L0: b = r4 return b def reverse(a, b): - a, b, r0, r1, r2, r3, r4 :: int64 + a, b, r0, r1, r2, r3, r4 :: i64 L0: r0 = 1 & a b = r0 @@ -335,7 +335,7 @@ L0: b = r4 return b def unary(a): - a, r0 :: int64 + a, r0 :: i64 L0: r0 = a ^ -1 return r0 @@ -355,11 +355,11 @@ def divide_by_zero(x: i64) -> i64: return x // 0 [out] def constant_divisor(x): - x, r0, r1 :: int64 + x, r0, r1 :: i64 r2, r3, r4 :: bit - r5 :: int64 + r5 :: i64 r6 :: bit - r7 :: int64 + r7 :: i64 L0: r0 = x / 7 r1 = r0 @@ -377,22 +377,22 @@ L2: L3: return r1 def variable_divisor(x, y): - x, y, r0 :: int64 + x, y, r0 :: i64 L0: r0 = CPyInt64_Divide(x, y) return r0 def constant_lhs(x): - x, r0 :: int64 + x, r0 :: i64 L0: r0 = CPyInt64_Divide(27, x) return r0 def divide_by_neg_one(x): - x, r0 :: int64 + x, r0 :: i64 L0: r0 = CPyInt64_Divide(x, -1) return r0 def divide_by_zero(x): - x, r0 :: int64 + x, r0 :: i64 L0: r0 = CPyInt64_Divide(x, 0) return r0 @@ -410,9 +410,9 @@ def mod_by_zero(x: i64) -> i64: return x % 0 [out] def constant_divisor(x): - x, r0, r1 :: int64 + x, r0, r1 :: i64 r2, r3, r4, r5 :: bit - r6 :: int64 + r6 :: i64 L0: r0 = x % 7 r1 = r0 @@ -429,17 +429,17 @@ L2: L3: return r1 def variable_divisor(x, y): - x, y, r0 :: int64 + x, y, r0 :: i64 L0: r0 = CPyInt64_Remainder(x, y) return r0 def constant_lhs(x): - x, r0 :: int64 + x, r0 :: i64 L0: r0 = CPyInt64_Remainder(27, x) return r0 def mod_by_zero(x): - x, r0 :: int64 + x, r0 :: i64 L0: r0 = CPyInt64_Remainder(x, 0) return r0 @@ -455,11 +455,11 @@ def by_variable(x: i64, y: i64) -> i64: return x [out] def by_constant(x): - x, r0, r1 :: int64 + x, r0, r1 :: i64 r2, r3, r4 :: bit - r5 :: int64 + r5 :: i64 r6 :: bit - r7 :: int64 + r7 :: i64 L0: r0 = x / 7 r1 = r0 @@ -478,7 +478,7 @@ L3: x = r1 return x def by_variable(x, y): - x, y, r0 :: int64 + x, y, r0 :: i64 L0: r0 = CPyInt64_Divide(x, y) x = r0 @@ -495,9 +495,9 @@ def by_variable(x: i64, y: i64) -> i64: return x [out] def by_constant(x): - x, r0, r1 :: int64 + x, r0, r1 :: i64 r2, r3, r4, r5 :: bit - r6 :: int64 + r6 :: i64 L0: r0 = x % 7 r1 = r0 @@ -515,7 +515,7 @@ L3: x = r1 return x def by_variable(x, y): - x, y, r0 :: int64 + x, y, r0 :: i64 L0: r0 = CPyInt64_Remainder(x, y) x = r0 @@ -532,14 +532,14 @@ def f(x: i64) -> None: g(n) [out] def g(a): - a :: int64 + a :: i64 L0: return 1 def f(x): - x, r0, n :: int64 + x, r0, n :: i64 r1 :: bit r2 :: None - r3 :: int64 + r3 :: i64 L0: r0 = 0 n = r0 @@ -566,10 +566,10 @@ def int_to_i64(a): a :: int r0 :: native_int r1 :: bit - r2, r3 :: int64 + r2, r3 :: i64 r4 :: ptr r5 :: c_ptr - r6 :: int64 + r6 :: i64 L0: r0 = a & 1 r1 = r0 == 0 @@ -594,7 +594,7 @@ def i64_to_int(a: i64) -> int: return a [out] def i64_to_int(a): - a :: int64 + a :: i64 r0, r1 :: bit r2, r3, r4 :: int L0: @@ -620,7 +620,7 @@ def i64_to_int(a: i64) -> int: return a [out] def i64_to_int(a): - a :: int64 + a :: i64 r0, r1 :: bit r2, r3 :: int r4 :: native_int @@ -636,7 +636,7 @@ L2: r3 = r2 goto L4 L3: - r4 = truncate a: int64 to native_int + r4 = truncate a: i64 to native_int r5 = r4 << 1 r3 = r5 L4: @@ -650,7 +650,6 @@ def f(x: i64, y: i64) -> Tuple[i64, i64]: return x, y def g() -> Tuple[i64, i64]: - # TODO: Avoid boxing and unboxing return 1, 2 def h() -> i64: @@ -659,25 +658,23 @@ def h() -> i64: return x + y + t[0] [out] def f(x, y): - x, y :: int64 - r0 :: tuple[int64, int64] + x, y :: i64 + r0 :: tuple[i64, i64] L0: r0 = (x, y) return r0 def g(): r0 :: tuple[int, int] - r1 :: object - r2 :: tuple[int64, int64] + r1 :: tuple[i64, i64] L0: r0 = (2, 4) - r1 = box(tuple[int, int], r0) - r2 = unbox(tuple[int64, int64], r1) - return r2 + r1 = (1, 2) + return r1 def h(): - r0 :: tuple[int64, int64] - r1, x, r2, y :: int64 - r3, t :: tuple[int64, int64] - r4, r5, r6 :: int64 + r0 :: tuple[i64, i64] + r1, x, r2, y :: i64 + r3, t :: tuple[i64, i64] + r4, r5, r6 :: i64 L0: r0 = g() r1 = r0[0] @@ -697,14 +694,14 @@ def f(x: i64, y: int) -> i64: return x + y [out] def f(x, y): - x :: int64 + x :: i64 y :: int r0 :: native_int r1 :: bit - r2, r3 :: int64 + r2, r3 :: i64 r4 :: ptr r5 :: c_ptr - r6, r7 :: int64 + r6, r7 :: i64 L0: r0 = y & 1 r1 = r0 == 0 @@ -730,13 +727,13 @@ def f(x: int, y: i64) -> i64: [out] def f(x, y): x :: int - y :: int64 + y :: i64 r0 :: native_int r1 :: bit - r2, r3 :: int64 + r2, r3 :: i64 r4 :: ptr r5 :: c_ptr - r6, r7 :: int64 + r6, r7 :: i64 L0: r0 = x & 1 r1 = r0 == 0 @@ -763,14 +760,14 @@ def f(y: i64) -> int: return x [out] def f(y): - y :: int64 + y :: i64 x :: int r0 :: native_int r1 :: bit - r2, r3 :: int64 + r2, r3 :: i64 r4 :: ptr r5 :: c_ptr - r6, r7 :: int64 + r6, r7 :: i64 r8, r9 :: bit r10, r11, r12 :: int L0: @@ -815,13 +812,13 @@ def f(y: int) -> i64: [out] def f(y): y :: int - x :: int64 + x :: i64 r0 :: native_int r1 :: bit - r2, r3 :: int64 + r2, r3 :: i64 r4 :: ptr r5 :: c_ptr - r6, r7 :: int64 + r6, r7 :: i64 L0: x = 0 r0 = y & 1 @@ -849,13 +846,13 @@ def f(x: int, y: i64) -> bool: [out] def f(x, y): x :: int - y :: int64 + y :: i64 r0 :: native_int r1 :: bit - r2, r3 :: int64 + r2, r3 :: i64 r4 :: ptr r5 :: c_ptr - r6 :: int64 + r6 :: i64 r7 :: bit L0: r0 = x & 1 @@ -881,14 +878,14 @@ def f(x: i64, y: int) -> bool: return x == y [out] def f(x, y): - x :: int64 + x :: i64 y :: int r0 :: native_int r1 :: bit - r2, r3 :: int64 + r2, r3 :: i64 r4 :: ptr r5 :: c_ptr - r6 :: int64 + r6 :: i64 r7 :: bit L0: r0 = y & 1 @@ -915,20 +912,20 @@ def f(x: int, y: i64) -> bool: [out] def f(x, y): x :: int - y :: int64 + y :: i64 r0 :: native_int r1 :: bit - r2, r3, r4 :: int64 + r2, r3, r4 :: i64 r5 :: ptr r6 :: c_ptr - r7 :: int64 + r7 :: i64 r8 :: bit L0: r0 = x & 1 r1 = r0 == 0 if r1 goto L1 else goto L2 :: bool L1: - r2 = extend signed x: builtins.int to int64 + r2 = extend signed x: builtins.int to i64 r3 = r2 >> 1 r4 = r3 goto L3 @@ -952,7 +949,7 @@ def f(x: i64) -> i64: return 3 [out] def f(x): - x :: int64 + x :: i64 r0, r1 :: bit L0: r0 = x != 0 @@ -978,14 +975,14 @@ def g(x: i64, y: int) -> int: return y [out] def f(x, y): - x :: int64 + x :: i64 y :: int r0 :: native_int r1 :: bit - r2, r3 :: int64 + r2, r3 :: i64 r4 :: ptr r5 :: c_ptr - r6 :: int64 + r6 :: i64 L0: r0 = y & 1 r1 = r0 == 0 @@ -1004,7 +1001,7 @@ L3: x = r3 return x def g(x, y): - x :: int64 + x :: i64 y :: int r0, r1 :: bit r2, r3, r4 :: int @@ -1046,7 +1043,7 @@ class C: [out] def add_simple(c): c :: __main__.C - r0, r1, r2 :: int64 + r0, r1, r2 :: i64 L0: r0 = c.x r1 = c.y @@ -1054,7 +1051,7 @@ L0: return r2 def inplace_add_simple(c): c :: __main__.C - r0, r1, r2 :: int64 + r0, r1, r2 :: i64 r3 :: bool L0: r0 = c.x @@ -1065,9 +1062,9 @@ L0: def add_borrow(d): d :: __main__.D r0 :: __main__.C - r1 :: int64 + r1 :: i64 r2 :: __main__.C - r3, r4 :: int64 + r3, r4 :: i64 L0: r0 = borrow d.c r1 = r0.x @@ -1098,7 +1095,7 @@ class C: [out] def bitwise_simple(c): c :: __main__.C - r0, r1, r2 :: int64 + r0, r1, r2 :: i64 L0: r0 = c.x r1 = c.y @@ -1106,7 +1103,7 @@ L0: return r2 def inplace_bitwide_simple(c): c :: __main__.C - r0, r1, r2 :: int64 + r0, r1, r2 :: i64 r3 :: bool L0: r0 = c.x @@ -1117,9 +1114,9 @@ L0: def bitwise_borrow(d): d :: __main__.D r0 :: __main__.C - r1 :: int64 + r1 :: i64 r2 :: __main__.C - r3, r4 :: int64 + r3, r4 :: i64 L0: r0 = borrow d.c r1 = r0.x @@ -1140,27 +1137,26 @@ class C: s: str [out] def f(n): - n :: int64 + n :: i64 r0 :: __main__.C r1 :: list - r2, r3 :: ptr + r2 :: ptr a :: list - r4 :: object - r5 :: __main__.C - r6 :: str + r3 :: object + r4 :: __main__.C + r5 :: str L0: r0 = C() r1 = PyList_New(1) - r2 = get_element_ptr r1 ob_item :: PyListObject - r3 = load_mem r2 :: ptr* - set_mem r3, r0 :: builtins.object* + r2 = list_items r1 + buf_init_item r2, 0, r0 keep_alive r1 a = r1 - r4 = CPyList_GetItemInt64Borrow(a, n) - r5 = borrow cast(__main__.C, r4) - r6 = r5.s - keep_alive a, n, r4 - return r6 + r3 = CPyList_GetItemInt64Borrow(a, n) + r4 = borrow cast(__main__.C, r3) + r5 = r4.s + keep_alive a, n, r3 + return r5 [case testBorrowOverI64ListGetItem2] from typing import List @@ -1173,13 +1169,13 @@ def f(a: List[i64], n: i64) -> bool: [out] def f(a, n): a :: list - n :: int64 + n :: i64 r0 :: object - r1 :: int64 + r1 :: i64 r2 :: bit L0: r0 = CPyList_GetItemInt64Borrow(a, n) - r1 = unbox(int64, r0) + r1 = unbox(i64, r0) r2 = r1 == 0 keep_alive a, n if r2 goto L1 else goto L2 :: bool @@ -1204,40 +1200,34 @@ def g(a: List[i64], y: i64) -> bool: [out] def f(a, y): a :: list - y :: int64 - r0 :: ptr - r1 :: native_int - r2 :: short_int - r3 :: int64 - r4 :: bit + y :: i64 + r0 :: native_int + r1 :: short_int + r2 :: i64 + r3 :: bit L0: - r0 = get_element_ptr a ob_size :: PyVarObject - r1 = load_mem r0 :: native_int* - keep_alive a - r2 = r1 << 1 - r3 = r2 >> 1 - r4 = r3 < y :: signed - if r4 goto L1 else goto L2 :: bool + r0 = var_object_size a + r1 = r0 << 1 + r2 = r1 >> 1 + r3 = r2 < y :: signed + if r3 goto L1 else goto L2 :: bool L1: return 1 L2: return 0 def g(a, y): a :: list - y :: int64 - r0 :: ptr - r1 :: native_int - r2 :: short_int - r3 :: int64 - r4 :: bit + y :: i64 + r0 :: native_int + r1 :: short_int + r2 :: i64 + r3 :: bit L0: - r0 = get_element_ptr a ob_size :: PyVarObject - r1 = load_mem r0 :: native_int* - keep_alive a - r2 = r1 << 1 - r3 = r2 >> 1 - r4 = y < r3 :: signed - if r4 goto L1 else goto L2 :: bool + r0 = var_object_size a + r1 = r0 << 1 + r2 = r1 >> 1 + r3 = y < r2 :: signed + if r3 goto L1 else goto L2 :: bool L1: return 1 L2: @@ -1251,35 +1241,34 @@ def f(n: i64) -> List[i64]: return [n] * n [out] def f(n): - n :: int64 + n :: i64 r0 :: list r1 :: object - r2, r3 :: ptr - r4, r5 :: bit - r6, r7, r8 :: int - r9 :: list + r2 :: ptr + r3, r4 :: bit + r5, r6, r7 :: int + r8 :: list L0: r0 = PyList_New(1) - r1 = box(int64, n) - r2 = get_element_ptr r0 ob_item :: PyListObject - r3 = load_mem r2 :: ptr* - set_mem r3, r1 :: builtins.object* + r1 = box(i64, n) + r2 = list_items r0 + buf_init_item r2, 0, r1 keep_alive r0 - r4 = n <= 4611686018427387903 :: signed - if r4 goto L1 else goto L2 :: bool + r3 = n <= 4611686018427387903 :: signed + if r3 goto L1 else goto L2 :: bool L1: - r5 = n >= -4611686018427387904 :: signed - if r5 goto L3 else goto L2 :: bool + r4 = n >= -4611686018427387904 :: signed + if r4 goto L3 else goto L2 :: bool L2: - r6 = CPyTagged_FromInt64(n) - r7 = r6 + r5 = CPyTagged_FromInt64(n) + r6 = r5 goto L4 L3: - r8 = n << 1 - r7 = r8 + r7 = n << 1 + r6 = r7 L4: - r9 = CPySequence_Multiply(r0, r7) - return r9 + r8 = CPySequence_Multiply(r0, r6) + return r8 [case testShortIntAndI64Op] from mypy_extensions import i64 @@ -1300,70 +1289,58 @@ def lt_i64(a: List[i64], n: i64) -> bool: [out] def add_i64(a, n): a :: list - n :: int64 - r0 :: ptr - r1 :: native_int - r2 :: short_int - r3, r4 :: int64 + n :: i64 + r0 :: native_int + r1 :: short_int + r2, r3 :: i64 L0: - r0 = get_element_ptr a ob_size :: PyVarObject - r1 = load_mem r0 :: native_int* - keep_alive a - r2 = r1 << 1 - r3 = r2 >> 1 - r4 = r3 + n - return r4 + r0 = var_object_size a + r1 = r0 << 1 + r2 = r1 >> 1 + r3 = r2 + n + return r3 def add_i64_2(a, n): a :: list - n :: int64 - r0 :: ptr - r1 :: native_int - r2 :: short_int - r3, r4 :: int64 + n :: i64 + r0 :: native_int + r1 :: short_int + r2, r3 :: i64 L0: - r0 = get_element_ptr a ob_size :: PyVarObject - r1 = load_mem r0 :: native_int* - keep_alive a - r2 = r1 << 1 - r3 = r2 >> 1 - r4 = n + r3 - return r4 + r0 = var_object_size a + r1 = r0 << 1 + r2 = r1 >> 1 + r3 = n + r2 + return r3 def eq_i64(a, n): a :: list - n :: int64 - r0 :: ptr - r1 :: native_int - r2 :: short_int - r3 :: int64 - r4 :: bit + n :: i64 + r0 :: native_int + r1 :: short_int + r2 :: i64 + r3 :: bit L0: - r0 = get_element_ptr a ob_size :: PyVarObject - r1 = load_mem r0 :: native_int* - keep_alive a - r2 = r1 << 1 - r3 = r2 >> 1 - r4 = r3 == n - if r4 goto L1 else goto L2 :: bool + r0 = var_object_size a + r1 = r0 << 1 + r2 = r1 >> 1 + r3 = r2 == n + if r3 goto L1 else goto L2 :: bool L1: return 1 L2: return 0 def lt_i64(a, n): a :: list - n :: int64 - r0 :: ptr - r1 :: native_int - r2 :: short_int - r3 :: int64 - r4 :: bit + n :: i64 + r0 :: native_int + r1 :: short_int + r2 :: i64 + r3 :: bit L0: - r0 = get_element_ptr a ob_size :: PyVarObject - r1 = load_mem r0 :: native_int* - keep_alive a - r2 = r1 << 1 - r3 = r2 >> 1 - r4 = n < r3 :: signed - if r4 goto L1 else goto L2 :: bool + r0 = var_object_size a + r1 = r0 << 1 + r2 = r1 >> 1 + r3 = n < r2 :: signed + if r3 goto L1 else goto L2 :: bool L1: return 1 L2: @@ -1379,10 +1356,10 @@ def f(x: Optional[i64]) -> i64: return x [out] def f(x): - x :: union[int64, None] + x :: union[i64, None] r0 :: object r1 :: bit - r2 :: int64 + r2 :: i64 L0: r0 = load_address _Py_NoneStruct r1 = x == r0 @@ -1390,7 +1367,7 @@ L0: L1: return 1 L2: - r2 = unbox(int64, x) + r2 = unbox(i64, x) return r2 [case testI64DefaultValueSingle] @@ -1403,10 +1380,10 @@ def g() -> i64: return f(7) + f(8, 9) [out] def f(x, y, __bitmap): - x, y :: int64 - __bitmap, r0 :: uint32 + x, y :: i64 + __bitmap, r0 :: u32 r1 :: bit - r2 :: int64 + r2 :: i64 L0: r0 = __bitmap & 1 r1 = r0 == 0 @@ -1417,7 +1394,7 @@ L2: r2 = x + y return r2 def g(): - r0, r1, r2 :: int64 + r0, r1, r2 :: i64 L0: r0 = f(7, 0, 0) r1 = f(8, 9, 1) @@ -1434,12 +1411,12 @@ def g() -> i64: return f(7) + f(8, 9) + f(1, 2, 3) + f(4, 5, 6, 7) [out] def f(a, b, c, d, __bitmap): - a, b :: int64 + a, b :: i64 c :: int - d :: int64 - __bitmap, r0 :: uint32 + d :: i64 + __bitmap, r0 :: u32 r1 :: bit - r2 :: uint32 + r2 :: u32 r3 :: bit L0: r0 = __bitmap & 1 @@ -1461,9 +1438,9 @@ L6: return 0 def g(): r0 :: int - r1 :: int64 + r1 :: i64 r2 :: int - r3, r4, r5, r6, r7, r8 :: int64 + r3, r4, r5, r6, r7, r8 :: i64 L0: r0 = :: int r1 = f(7, 0, r0, 0, 0) @@ -1489,8 +1466,8 @@ def f(c: C) -> None: [out] def C.m(self, x, __bitmap): self :: __main__.C - x :: int64 - __bitmap, r0 :: uint32 + x :: i64 + __bitmap, r0 :: u32 r1 :: bit L0: r0 = __bitmap & 1 @@ -1509,7 +1486,10 @@ L0: return 1 [case testI64ExplicitConversionFromNativeInt] -from mypy_extensions import i64, i32 +from mypy_extensions import i64, i32, i16 + +def from_i16(x: i16) -> i64: + return i64(x) def from_i32(x: i32) -> i64: return i64(x) @@ -1517,14 +1497,20 @@ def from_i32(x: i32) -> i64: def from_i64(x: i64) -> i64: return i64(x) [out] +def from_i16(x): + x :: i16 + r0 :: i64 +L0: + r0 = extend signed x: i16 to i64 + return r0 def from_i32(x): - x :: int32 - r0 :: int64 + x :: i32 + r0 :: i64 L0: - r0 = extend signed x: int32 to int64 + r0 = extend signed x: i32 to i64 return r0 def from_i64(x): - x :: int64 + x :: i64 L0: return x @@ -1538,10 +1524,10 @@ def f(x): x :: int r0 :: native_int r1 :: bit - r2, r3 :: int64 + r2, r3 :: i64 r4 :: ptr r5 :: c_ptr - r6 :: int64 + r6 :: i64 L0: r0 = x & 1 r1 = r0 == 0 @@ -1566,7 +1552,7 @@ def f(x: i64) -> int: return int(x) [out] def f(x): - x :: int64 + x :: i64 r0, r1 :: bit r2, r3, r4 :: int L0: @@ -1594,7 +1580,7 @@ def f() -> None: z = i64(-3) [out] def f(): - x, y, z :: int64 + x, y, z :: i64 L0: x = 0 y = 11 @@ -1609,9 +1595,9 @@ def f() -> None: y = x [out] def f(): - r0, x :: int64 + r0, x :: i64 r1 :: bit - y, r2 :: int64 + y, r2 :: i64 L0: r0 = 0 x = r0 @@ -1636,9 +1622,9 @@ def f() -> None: y = x [out] def f(): - r0, x :: int64 + r0, x :: i64 r1 :: bit - y, r2 :: int64 + y, r2 :: i64 L0: r0 = 0 x = r0 @@ -1665,8 +1651,8 @@ class D(C): [out] def C.f(self, x, __bitmap): self :: __main__.C - x :: int64 - __bitmap, r0 :: uint32 + x :: i64 + __bitmap, r0 :: u32 r1 :: bit L0: r0 = __bitmap & 1 @@ -1678,8 +1664,8 @@ L2: return 1 def D.f(self, x, __bitmap): self :: __main__.D - x :: int64 - __bitmap, r0 :: uint32 + x :: i64 + __bitmap, r0 :: u32 r1 :: bit L0: r0 = __bitmap & 1 @@ -1745,26 +1731,26 @@ def compare_bool_to_i64(n: i64, b: bool) -> bool: return True [out] def add_bool_to_int(n, b): - n :: int64 + n :: i64 b :: bool - r0, r1 :: int64 + r0, r1 :: i64 L0: - r0 = extend b: builtins.bool to int64 + r0 = extend b: builtins.bool to i64 r1 = n + r0 return r1 def compare_bool_to_i64(n, b): - n :: int64 + n :: i64 b :: bool - r0 :: int64 + r0 :: i64 r1 :: bit - r2 :: int64 + r2 :: i64 r3 :: bit L0: - r0 = extend b: builtins.bool to int64 + r0 = extend b: builtins.bool to i64 r1 = n == r0 if r1 goto L1 else goto L2 :: bool L1: - r2 = extend b: builtins.bool to int64 + r2 = extend b: builtins.bool to i64 r3 = r2 != n return r3 L2: @@ -1782,18 +1768,18 @@ def cast_int(x: int) -> i64: [out] def cast_object(o): o :: object - r0 :: int64 + r0 :: i64 L0: - r0 = unbox(int64, o) + r0 = unbox(i64, o) return r0 def cast_int(x): x :: int r0 :: native_int r1 :: bit - r2, r3 :: int64 + r2, r3 :: i64 r4 :: ptr r5 :: c_ptr - r6 :: int64 + r6 :: i64 L0: r0 = x & 1 r1 = r0 == 0 @@ -1822,16 +1808,16 @@ def cast_int(x): x :: int r0 :: native_int r1 :: bit - r2, r3, r4 :: int64 + r2, r3, r4 :: i64 r5 :: ptr r6 :: c_ptr - r7 :: int64 + r7 :: i64 L0: r0 = x & 1 r1 = r0 == 0 if r1 goto L1 else goto L2 :: bool L1: - r2 = extend signed x: builtins.int to int64 + r2 = extend signed x: builtins.int to i64 r3 = r2 >> 1 r4 = r3 goto L3 @@ -1844,7 +1830,7 @@ L2: L3: return r4 -[case testI64ExplicitConversionFromVariousTypes] +[case testI64ExplicitConversionFromVariousTypes_64bit] from mypy_extensions import i64 def bool_to_i64(b: bool) -> i64: @@ -1868,25 +1854,25 @@ def float_to_i64(x: float) -> i64: [out] def bool_to_i64(b): b :: bool - r0 :: int64 + r0 :: i64 L0: - r0 = extend b: builtins.bool to int64 + r0 = extend b: builtins.bool to i64 return r0 def str_to_i64(s): s :: str r0 :: object - r1 :: int64 + r1 :: i64 L0: r0 = CPyLong_FromStr(s) - r1 = unbox(int64, r0) + r1 = unbox(i64, r0) return r1 def str_to_i64_with_base(s): s :: str r0 :: object - r1 :: int64 + r1 :: i64 L0: r0 = CPyLong_FromStrWithBase(s, 4) - r1 = unbox(int64, r0) + r1 = unbox(i64, r0) return r1 def C.__int__(self): self :: __main__.C @@ -1894,18 +1880,129 @@ L0: return 5 def instance_to_i64(c): c :: __main__.C - r0 :: int64 + r0 :: i64 L0: r0 = c.__int__() return r0 def float_to_i64(x): x :: float - r0 :: object - r1 :: int64 + r0 :: int + r1 :: native_int + r2 :: bit + r3, r4 :: i64 + r5 :: ptr + r6 :: c_ptr + r7 :: i64 L0: - r0 = CPyLong_FromFloat(x) - r1 = unbox(int64, r0) - return r1 + r0 = CPyTagged_FromFloat(x) + r1 = r0 & 1 + r2 = r1 == 0 + if r2 goto L1 else goto L2 :: bool +L1: + r3 = r0 >> 1 + r4 = r3 + goto L3 +L2: + r5 = r0 ^ 1 + r6 = r5 + r7 = CPyLong_AsInt64(r6) + r4 = r7 + keep_alive r0 +L3: + return r4 + +[case testI64ExplicitConversionFromFloat_32bit] +from mypy_extensions import i64 + +def float_to_i64(x: float) -> i64: + return i64(x) +[out] +def float_to_i64(x): + x :: float + r0 :: int + r1 :: native_int + r2 :: bit + r3, r4, r5 :: i64 + r6 :: ptr + r7 :: c_ptr + r8 :: i64 +L0: + r0 = CPyTagged_FromFloat(x) + r1 = r0 & 1 + r2 = r1 == 0 + if r2 goto L1 else goto L2 :: bool +L1: + r3 = extend signed r0: builtins.int to i64 + r4 = r3 >> 1 + r5 = r4 + goto L3 +L2: + r6 = r0 ^ 1 + r7 = r6 + r8 = CPyLong_AsInt64(r7) + r5 = r8 + keep_alive r0 +L3: + return r5 + +[case testI64ConvertToFloat_64bit] +from mypy_extensions import i64 + +def i64_to_float(x: i64) -> float: + return float(x) +[out] +def i64_to_float(x): + x :: i64 + r0, r1 :: bit + r2, r3, r4 :: int + r5 :: float +L0: + r0 = x <= 4611686018427387903 :: signed + if r0 goto L1 else goto L2 :: bool +L1: + r1 = x >= -4611686018427387904 :: signed + if r1 goto L3 else goto L2 :: bool +L2: + r2 = CPyTagged_FromInt64(x) + r3 = r2 + goto L4 +L3: + r4 = x << 1 + r3 = r4 +L4: + r5 = CPyFloat_FromTagged(r3) + return r5 + +[case testI64ConvertToFloat_32bit] +from mypy_extensions import i64 + +def i64_to_float(x: i64) -> float: + return float(x) +[out] +def i64_to_float(x): + x :: i64 + r0, r1 :: bit + r2, r3 :: int + r4 :: native_int + r5 :: int + r6 :: float +L0: + r0 = x <= 1073741823 :: signed + if r0 goto L1 else goto L2 :: bool +L1: + r1 = x >= -1073741824 :: signed + if r1 goto L3 else goto L2 :: bool +L2: + r2 = CPyTagged_FromInt64(x) + r3 = r2 + goto L4 +L3: + r4 = truncate x: i64 to native_int + r5 = r4 << 1 + r3 = r5 +L4: + r6 = CPyFloat_FromTagged(r3) + return r6 [case testI64IsinstanceNarrowing] from typing import Union @@ -1925,22 +2022,22 @@ def narrow2(x: Union[C, i64]) -> i64: return x.a [out] def narrow1(x): - x :: union[__main__.C, int64] + x :: union[__main__.C, i64] r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: bool - r4 :: int64 + r4 :: i64 r5 :: __main__.C - r6 :: int64 + r6 :: i64 L0: r0 = load_address PyLong_Type r1 = PyObject_IsInstance(x, r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool if r3 goto L1 else goto L2 :: bool L1: - r4 = unbox(int64, x) + r4 = unbox(i64, x) return r4 L2: r5 = borrow cast(__main__.C, x) @@ -1948,25 +2045,100 @@ L2: keep_alive x return r6 def narrow2(x): - x :: union[__main__.C, int64] + x :: union[__main__.C, i64] r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: bool - r4 :: int64 + r4 :: i64 r5 :: __main__.C - r6 :: int64 + r6 :: i64 L0: r0 = load_address PyLong_Type r1 = PyObject_IsInstance(x, r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool if r3 goto L1 else goto L2 :: bool L1: - r4 = unbox(int64, x) + r4 = unbox(i64, x) return r4 L2: r5 = borrow cast(__main__.C, x) r6 = r5.a keep_alive x return r6 + +[case testI64ConvertBetweenTuples_64bit] +from __future__ import annotations +from mypy_extensions import i64 + +def f(t: tuple[int, i64, int]) -> None: + tt: tuple[int, i64, i64] = t + +def g(n: int) -> None: + t: tuple[i64, i64] = (1, n) +[out] +def f(t): + t :: tuple[int, i64, int] + r0 :: int + r1 :: i64 + r2 :: int + r3 :: native_int + r4 :: bit + r5, r6 :: i64 + r7 :: ptr + r8 :: c_ptr + r9 :: i64 + r10, tt :: tuple[int, i64, i64] +L0: + r0 = t[0] + r1 = t[1] + r2 = t[2] + r3 = r2 & 1 + r4 = r3 == 0 + if r4 goto L1 else goto L2 :: bool +L1: + r5 = r2 >> 1 + r6 = r5 + goto L3 +L2: + r7 = r2 ^ 1 + r8 = r7 + r9 = CPyLong_AsInt64(r8) + r6 = r9 + keep_alive r2 +L3: + r10 = (r0, r1, r6) + tt = r10 + return 1 +def g(n): + n :: int + r0 :: tuple[int, int] + r1 :: int + r2 :: native_int + r3 :: bit + r4, r5 :: i64 + r6 :: ptr + r7 :: c_ptr + r8 :: i64 + r9, t :: tuple[i64, i64] +L0: + r0 = (2, n) + r1 = r0[1] + r2 = r1 & 1 + r3 = r2 == 0 + if r3 goto L1 else goto L2 :: bool +L1: + r4 = r1 >> 1 + r5 = r4 + goto L3 +L2: + r6 = r1 ^ 1 + r7 = r6 + r8 = CPyLong_AsInt64(r7) + r5 = r8 + keep_alive r1 +L3: + r9 = (1, r5) + t = r9 + return 1 diff --git a/mypyc/test-data/irbuild-int.test b/mypyc/test-data/irbuild-int.test index fbe00aff4040..b1a712103e70 100644 --- a/mypyc/test-data/irbuild-int.test +++ b/mypyc/test-data/irbuild-int.test @@ -4,24 +4,10 @@ def f(x: int, y: int) -> bool: [out] def f(x, y): x, y :: int - r0 :: native_int - r1, r2 :: bit - r3 :: bool - r4, r5 :: bit -L0: - r0 = x & 1 - r1 = r0 == 0 - if r1 goto L1 else goto L2 :: bool -L1: - r2 = x != y - r3 = r2 - goto L3 -L2: - r4 = CPyTagged_IsEq_(x, y) - r5 = r4 ^ 1 - r3 = r5 -L3: - return r3 + r0 :: bit +L0: + r0 = int_ne x, y + return r0 [case testShortIntComparisons] def f(x: int) -> int: @@ -39,46 +25,37 @@ def f(x: int) -> int: [out] def f(x): x :: int - r0, r1, r2, r3 :: bit - r4 :: native_int - r5, r6, r7 :: bit + r0, r1, r2, r3, r4 :: bit L0: - r0 = x == 6 + r0 = int_eq x, 6 if r0 goto L1 else goto L2 :: bool L1: return 2 L2: - r1 = x != 8 + r1 = int_ne x, 8 if r1 goto L3 else goto L4 :: bool L3: return 4 L4: - r2 = 10 == x + r2 = int_eq 10, x if r2 goto L5 else goto L6 :: bool L5: return 6 L6: - r3 = 12 != x + r3 = int_ne 12, x if r3 goto L7 else goto L8 :: bool L7: return 8 L8: - r4 = x & 1 - r5 = r4 != 0 - if r5 goto L9 else goto L10 :: bool + r4 = int_lt x, 8 + if r4 goto L9 else goto L10 :: bool L9: - r6 = CPyTagged_IsLt_(x, 8) - if r6 goto L11 else goto L12 :: bool + return 10 L10: - r7 = x < 8 :: signed - if r7 goto L11 else goto L12 :: bool L11: - return 10 L12: L13: L14: -L15: -L16: return 12 [case testIntMin] @@ -87,36 +64,18 @@ def f(x: int, y: int) -> int: [out] def f(x, y): x, y :: int - r0 :: native_int - r1 :: bit - r2 :: native_int - r3, r4, r5 :: bit - r6 :: bool - r7 :: bit - r8 :: int -L0: - r0 = y & 1 - r1 = r0 == 0 - r2 = x & 1 - r3 = r2 == 0 - r4 = r1 & r3 - if r4 goto L1 else goto L2 :: bool + r0 :: bit + r1 :: int +L0: + r0 = int_lt y, x + if r0 goto L1 else goto L2 :: bool L1: - r5 = y < x :: signed - r6 = r5 + r1 = y goto L3 L2: - r7 = CPyTagged_IsLt_(y, x) - r6 = r7 + r1 = x L3: - if r6 goto L4 else goto L5 :: bool -L4: - r8 = y - goto L6 -L5: - r8 = x -L6: - return r8 + return r1 [case testIntFloorDivideByPowerOfTwo] def divby1(x: int) -> int: diff --git a/mypyc/test-data/irbuild-isinstance.test b/mypyc/test-data/irbuild-isinstance.test index 6bb92d0a947e..78da2e9c1e19 100644 --- a/mypyc/test-data/irbuild-isinstance.test +++ b/mypyc/test-data/irbuild-isinstance.test @@ -5,14 +5,14 @@ def is_int(value: object) -> bool: [out] def is_int(value): value, r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: bool L0: r0 = load_address PyLong_Type r1 = PyObject_IsInstance(value, r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool return r3 [case testIsinstanceNotBool1] @@ -22,14 +22,14 @@ def is_not_bool(value: object) -> bool: [out] def is_not_bool(value): value, r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3, r4 :: bool L0: r0 = load_address PyBool_Type r1 = PyObject_IsInstance(value, r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool r4 = r3 ^ 1 return r4 @@ -42,18 +42,18 @@ def is_not_bool_and_is_int(value: object) -> bool: [out] def is_not_bool_and_is_int(value): value, r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3, r4 :: bool r5 :: object - r6 :: int32 + r6 :: i32 r7 :: bit r8, r9 :: bool L0: r0 = load_address PyLong_Type r1 = PyObject_IsInstance(value, r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool if r3 goto L2 else goto L1 :: bool L1: r4 = r3 @@ -62,7 +62,7 @@ L2: r5 = load_address PyBool_Type r6 = PyObject_IsInstance(value, r5) r7 = r6 >= 0 :: signed - r8 = truncate r6: int32 to builtins.bool + r8 = truncate r6: i32 to builtins.bool r9 = r8 ^ 1 r4 = r9 L3: diff --git a/mypyc/test-data/irbuild-lists.test b/mypyc/test-data/irbuild-lists.test index cb9687a2f942..725f218b686a 100644 --- a/mypyc/test-data/irbuild-lists.test +++ b/mypyc/test-data/irbuild-lists.test @@ -84,6 +84,22 @@ L0: x = r0 return 1 +[case testNewListEmptyViaAlias] +from typing import List + +ListAlias = list + +def f() -> None: + x: List[int] = ListAlias() + +[out] +def f(): + r0, x :: list +L0: + r0 = PyList_New(0) + x = r0 + return 1 + [case testNewListTwoItems] from typing import List def f() -> None: @@ -92,17 +108,15 @@ def f() -> None: def f(): r0 :: list r1, r2 :: object - r3, r4, r5 :: ptr + r3 :: ptr x :: list L0: r0 = PyList_New(2) r1 = object 1 r2 = object 2 - r3 = get_element_ptr r0 ob_item :: PyListObject - r4 = load_mem r3 :: ptr* - set_mem r4, r1 :: builtins.object* - r5 = r4 + WORD_SIZE*1 - set_mem r5, r2 :: builtins.object* + r3 = list_items r0 + buf_init_item r3, 0, r1 + buf_init_item r3, 1, r2 keep_alive r0 x = r0 return 1 @@ -140,19 +154,18 @@ def f(a: List[int]) -> None: def f(a): a, r0, b, r1 :: list r2 :: object - r3, r4 :: ptr - r5 :: list + r3 :: ptr + r4 :: list L0: r0 = CPySequence_Multiply(a, 4) b = r0 r1 = PyList_New(1) r2 = object 4 - r3 = get_element_ptr r1 ob_item :: PyListObject - r4 = load_mem r3 :: ptr* - set_mem r4, r2 :: builtins.object* + r3 = list_items r1 + buf_init_item r3, 0, r2 keep_alive r1 - r5 = CPySequence_RMultiply(6, r1) - b = r5 + r4 = CPySequence_RMultiply(6, r1) + b = r4 return 1 [case testListLen] @@ -162,15 +175,12 @@ def f(a: List[int]) -> int: [out] def f(a): a :: list - r0 :: ptr - r1 :: native_int - r2 :: short_int + r0 :: native_int + r1 :: short_int L0: - r0 = get_element_ptr a ob_size :: PyVarObject - r1 = load_mem r0 :: native_int* - keep_alive a - r2 = r1 << 1 - return r2 + r0 = var_object_size a + r1 = r0 << 1 + return r1 [case testListAppend] from typing import List @@ -181,7 +191,7 @@ def f(a, x): a :: list x :: int r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit L0: r0 = box(int, x) @@ -198,33 +208,30 @@ def increment(l: List[int]) -> List[int]: [out] def increment(l): l :: list - r0 :: ptr - r1 :: native_int - r2, r3 :: short_int + r0 :: native_int + r1, r2 :: short_int i :: int - r4 :: bit - r5, r6, r7 :: object - r8 :: bit - r9 :: short_int + r3 :: bit + r4, r5, r6 :: object + r7 :: bit + r8 :: short_int L0: - r0 = get_element_ptr l ob_size :: PyVarObject - r1 = load_mem r0 :: native_int* - keep_alive l - r2 = r1 << 1 - r3 = 0 - i = r3 + r0 = var_object_size l + r1 = r0 << 1 + r2 = 0 + i = r2 L1: - r4 = r3 < r2 :: signed - if r4 goto L2 else goto L4 :: bool + r3 = int_lt r2, r1 + if r3 goto L2 else goto L4 :: bool L2: - r5 = CPyList_GetItem(l, i) - r6 = object 1 - r7 = PyNumber_InPlaceAdd(r5, r6) - r8 = CPyList_SetItem(l, i, r7) + r4 = CPyList_GetItem(l, i) + r5 = object 1 + r6 = PyNumber_InPlaceAdd(r4, r5) + r7 = CPyList_SetItem(l, i, r6) L3: - r9 = r3 + 2 - r3 = r9 - i = r9 + r8 = r2 + 2 + r2 = r8 + i = r8 goto L1 L4: return l @@ -237,25 +244,23 @@ def f(x: List[int], y: List[int]) -> List[int]: def f(x, y): x, y, r0 :: list r1, r2 :: object - r3, r4, r5 :: ptr - r6, r7, r8 :: object - r9 :: int32 - r10 :: bit + r3 :: ptr + r4, r5, r6 :: object + r7 :: i32 + r8 :: bit L0: r0 = PyList_New(2) r1 = object 1 r2 = object 2 - r3 = get_element_ptr r0 ob_item :: PyListObject - r4 = load_mem r3 :: ptr* - set_mem r4, r1 :: builtins.object* - r5 = r4 + WORD_SIZE*1 - set_mem r5, r2 :: builtins.object* + r3 = list_items r0 + buf_init_item r3, 0, r1 + buf_init_item r3, 1, r2 keep_alive r0 - r6 = CPyList_Extend(r0, x) - r7 = CPyList_Extend(r0, y) - r8 = object 3 - r9 = PyList_Append(r0, r8) - r10 = r9 >= 0 :: signed + r4 = CPyList_Extend(r0, x) + r5 = CPyList_Extend(r0, y) + r6 = object 3 + r7 = PyList_Append(r0, r6) + r8 = r7 >= 0 :: signed return r0 [case testListIn] @@ -267,14 +272,14 @@ def f(x, y): x :: list y :: int r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: bool L0: r0 = box(int, y) r1 = PySequence_Contains(x, r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool return r3 [case testListInsert] @@ -286,7 +291,7 @@ def f(x, y): x :: list y :: int r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit L0: r0 = box(int, y) @@ -302,86 +307,75 @@ def f(source: List[int]) -> None: [out] def f(source): source :: list - r0 :: ptr - r1 :: native_int - r2 :: list - r3 :: short_int - r4 :: ptr - r5 :: native_int - r6 :: short_int - r7 :: bit - r8 :: object - r9, x, r10 :: int - r11 :: object - r12 :: bit - r13 :: short_int + r0 :: native_int + r1 :: list + r2 :: short_int + r3 :: native_int + r4 :: short_int + r5 :: bit + r6 :: object + r7, x, r8 :: int + r9 :: object + r10 :: bit + r11 :: short_int a :: list - r14 :: ptr + r12 :: native_int + r13 :: list + r14 :: short_int r15 :: native_int - r16 :: list - r17 :: short_int - r18 :: ptr - r19 :: native_int - r20 :: short_int - r21 :: bit - r22 :: object - r23, x_2, r24 :: int - r25 :: object - r26 :: bit - r27 :: short_int + r16 :: short_int + r17 :: bit + r18 :: object + r19, x_2, r20 :: int + r21 :: object + r22 :: bit + r23 :: short_int b :: list L0: - r0 = get_element_ptr source ob_size :: PyVarObject - r1 = load_mem r0 :: native_int* - keep_alive source - r2 = PyList_New(r1) - r3 = 0 + r0 = var_object_size source + r1 = PyList_New(r0) + r2 = 0 L1: - r4 = get_element_ptr source ob_size :: PyVarObject - r5 = load_mem r4 :: native_int* - keep_alive source - r6 = r5 << 1 - r7 = r3 < r6 :: signed - if r7 goto L2 else goto L4 :: bool + r3 = var_object_size source + r4 = r3 << 1 + r5 = int_lt r2, r4 + if r5 goto L2 else goto L4 :: bool L2: - r8 = CPyList_GetItemUnsafe(source, r3) - r9 = unbox(int, r8) - x = r9 - r10 = CPyTagged_Add(x, 2) - r11 = box(int, r10) - r12 = CPyList_SetItemUnsafe(r2, r3, r11) + r6 = CPyList_GetItemUnsafe(source, r2) + r7 = unbox(int, r6) + x = r7 + r8 = CPyTagged_Add(x, 2) + r9 = box(int, r8) + r10 = CPyList_SetItemUnsafe(r1, r2, r9) L3: - r13 = r3 + 2 - r3 = r13 + r11 = r2 + 2 + r2 = r11 goto L1 L4: - a = r2 - r14 = get_element_ptr source ob_size :: PyVarObject - r15 = load_mem r14 :: native_int* - keep_alive source - r16 = PyList_New(r15) - r17 = 0 + a = r1 + r12 = var_object_size source + r13 = PyList_New(r12) + r14 = 0 L5: - r18 = get_element_ptr source ob_size :: PyVarObject - r19 = load_mem r18 :: native_int* - keep_alive source - r20 = r19 << 1 - r21 = r17 < r20 :: signed - if r21 goto L6 else goto L8 :: bool + r15 = var_object_size source + r16 = r15 << 1 + r17 = int_lt r14, r16 + if r17 goto L6 else goto L8 :: bool L6: - r22 = CPyList_GetItemUnsafe(source, r17) - r23 = unbox(int, r22) - x_2 = r23 - r24 = CPyTagged_Add(x_2, 2) - r25 = box(int, r24) - r26 = CPyList_SetItemUnsafe(r16, r17, r25) + r18 = CPyList_GetItemUnsafe(source, r14) + r19 = unbox(int, r18) + x_2 = r19 + r20 = CPyTagged_Add(x_2, 2) + r21 = box(int, r20) + r22 = CPyList_SetItemUnsafe(r13, r14, r21) L7: - r27 = r17 + 2 - r17 = r27 + r23 = r14 + 2 + r14 = r23 goto L5 L8: - b = r16 + b = r13 return 1 + [case testGeneratorNext] from typing import List, Optional @@ -391,42 +385,39 @@ def test(x: List[int]) -> None: def test(x): x :: list r0 :: short_int - r1 :: ptr - r2 :: native_int - r3 :: short_int - r4 :: bit - r5 :: object - r6, i :: int - r7 :: object - r8 :: union[int, None] - r9 :: short_int - r10 :: object + r1 :: native_int + r2 :: short_int + r3 :: bit + r4 :: object + r5, i :: int + r6 :: object + r7 :: union[int, None] + r8 :: short_int + r9 :: object res :: union[int, None] L0: r0 = 0 L1: - r1 = get_element_ptr x ob_size :: PyVarObject - r2 = load_mem r1 :: native_int* - keep_alive x - r3 = r2 << 1 - r4 = r0 < r3 :: signed - if r4 goto L2 else goto L4 :: bool + r1 = var_object_size x + r2 = r1 << 1 + r3 = int_lt r0, r2 + if r3 goto L2 else goto L4 :: bool L2: - r5 = CPyList_GetItemUnsafe(x, r0) - r6 = unbox(int, r5) - i = r6 - r7 = box(int, i) - r8 = r7 + r4 = CPyList_GetItemUnsafe(x, r0) + r5 = unbox(int, r4) + i = r5 + r6 = box(int, i) + r7 = r6 goto L5 L3: - r9 = r0 + 2 - r0 = r9 + r8 = r0 + 2 + r0 = r8 goto L1 L4: - r10 = box(None, 1) - r8 = r10 + r9 = box(None, 1) + r7 = r9 L5: - res = r8 + res = r7 return 1 [case testSimplifyListUnion] @@ -446,86 +437,77 @@ def nested_union(a: Union[List[str], List[Optional[str]]]) -> None: def narrow(a): a :: union[list, int] r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: bool r4 :: list - r5 :: ptr - r6 :: native_int - r7 :: short_int - r8 :: int + r5 :: native_int + r6 :: short_int + r7 :: int L0: r0 = load_address PyList_Type r1 = PyObject_IsInstance(a, r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool if r3 goto L1 else goto L2 :: bool L1: r4 = borrow cast(list, a) - r5 = get_element_ptr r4 ob_size :: PyVarObject - r6 = load_mem r5 :: native_int* - keep_alive r4 - r7 = r6 << 1 + r5 = var_object_size r4 + r6 = r5 << 1 keep_alive a - return r7 + return r6 L2: - r8 = unbox(int, a) - return r8 + r7 = unbox(int, a) + return r7 def loop(a): a :: list r0 :: short_int - r1 :: ptr - r2 :: native_int - r3 :: short_int - r4 :: bit - r5 :: object - r6, x :: union[str, bytes] - r7 :: short_int + r1 :: native_int + r2 :: short_int + r3 :: bit + r4 :: object + r5, x :: union[str, bytes] + r6 :: short_int L0: r0 = 0 L1: - r1 = get_element_ptr a ob_size :: PyVarObject - r2 = load_mem r1 :: native_int* - keep_alive a - r3 = r2 << 1 - r4 = r0 < r3 :: signed - if r4 goto L2 else goto L4 :: bool + r1 = var_object_size a + r2 = r1 << 1 + r3 = int_lt r0, r2 + if r3 goto L2 else goto L4 :: bool L2: - r5 = CPyList_GetItemUnsafe(a, r0) - r6 = cast(union[str, bytes], r5) - x = r6 + r4 = CPyList_GetItemUnsafe(a, r0) + r5 = cast(union[str, bytes], r4) + x = r5 L3: - r7 = r0 + 2 - r0 = r7 + r6 = r0 + 2 + r0 = r6 goto L1 L4: return 1 def nested_union(a): a :: list r0 :: short_int - r1 :: ptr - r2 :: native_int - r3 :: short_int - r4 :: bit - r5 :: object - r6, x :: union[str, None] - r7 :: short_int + r1 :: native_int + r2 :: short_int + r3 :: bit + r4 :: object + r5, x :: union[str, None] + r6 :: short_int L0: r0 = 0 L1: - r1 = get_element_ptr a ob_size :: PyVarObject - r2 = load_mem r1 :: native_int* - keep_alive a - r3 = r2 << 1 - r4 = r0 < r3 :: signed - if r4 goto L2 else goto L4 :: bool + r1 = var_object_size a + r2 = r1 << 1 + r3 = int_lt r0, r2 + if r3 goto L2 else goto L4 :: bool L2: - r5 = CPyList_GetItemUnsafe(a, r0) - r6 = cast(union[str, None], r5) - x = r6 + r4 = CPyList_GetItemUnsafe(a, r0) + r5 = cast(union[str, None], r4) + x = r5 L3: - r7 = r0 + 2 - r0 = r7 + r6 = r0 + 2 + r0 = r6 goto L1 L4: return 1 diff --git a/mypyc/test-data/irbuild-match.test b/mypyc/test-data/irbuild-match.test index 2afe3d862f51..ab5a19624ba6 100644 --- a/mypyc/test-data/irbuild-match.test +++ b/mypyc/test-data/irbuild-match.test @@ -14,7 +14,7 @@ def f(): r6 :: object_ptr r7, r8 :: object L0: - r0 = 246 == 246 + r0 = int_eq 246, 246 if r0 goto L1 else goto L2 :: bool L1: r1 = 'matched' @@ -30,6 +30,7 @@ L2: L3: r8 = box(None, 1) return r8 + [case testMatchOrPattern_python3_10] def f(): match 123: @@ -46,10 +47,10 @@ def f(): r7 :: object_ptr r8, r9 :: object L0: - r0 = 246 == 246 + r0 = int_eq 246, 246 if r0 goto L3 else goto L1 :: bool L1: - r1 = 246 == 912 + r1 = int_eq 246, 912 if r1 goto L3 else goto L2 :: bool L2: goto L4 @@ -67,6 +68,7 @@ L4: L5: r9 = box(None, 1) return r9 + [case testMatchOrPatternManyPatterns_python3_10] def f(): match 1: @@ -83,16 +85,16 @@ def f(): r9 :: object_ptr r10, r11 :: object L0: - r0 = 2 == 2 + r0 = int_eq 2, 2 if r0 goto L5 else goto L1 :: bool L1: - r1 = 2 == 4 + r1 = int_eq 2, 4 if r1 goto L5 else goto L2 :: bool L2: - r2 = 2 == 6 + r2 = int_eq 2, 6 if r2 goto L5 else goto L3 :: bool L3: - r3 = 2 == 8 + r3 = int_eq 2, 8 if r3 goto L5 else goto L4 :: bool L4: goto L6 @@ -110,6 +112,7 @@ L6: L7: r11 = box(None, 1) return r11 + [case testMatchClassPattern_python3_10] def f(): match 123: @@ -200,7 +203,7 @@ def f(): r14 :: object_ptr r15, r16 :: object L0: - r0 = 246 == 246 + r0 = int_eq 246, 246 if r0 goto L1 else goto L2 :: bool L1: r1 = 'matched' @@ -213,7 +216,7 @@ L1: keep_alive r1 goto L5 L2: - r8 = 246 == 912 + r8 = int_eq 246, 912 if r8 goto L3 else goto L4 :: bool L3: r9 = 'no match' @@ -229,6 +232,7 @@ L4: L5: r16 = box(None, 1) return r16 + [case testMatchMultiBodyAndComplexOr_python3_10] def f(): match 123: @@ -265,7 +269,7 @@ def f(): r23 :: object_ptr r24, r25 :: object L0: - r0 = 246 == 2 + r0 = int_eq 246, 2 if r0 goto L1 else goto L2 :: bool L1: r1 = 'here 1' @@ -278,10 +282,10 @@ L1: keep_alive r1 goto L9 L2: - r8 = 246 == 4 + r8 = int_eq 246, 4 if r8 goto L5 else goto L3 :: bool L3: - r9 = 246 == 6 + r9 = int_eq 246, 6 if r9 goto L5 else goto L4 :: bool L4: goto L6 @@ -296,7 +300,7 @@ L5: keep_alive r10 goto L9 L6: - r17 = 246 == 246 + r17 = int_eq 246, 246 if r17 goto L7 else goto L8 :: bool L7: r18 = 'here 123' @@ -312,6 +316,7 @@ L8: L9: r25 = box(None, 1) return r25 + [case testMatchWithGuard_python3_10] def f(): match 123: @@ -328,7 +333,7 @@ def f(): r6 :: object_ptr r7, r8 :: object L0: - r0 = 246 == 246 + r0 = int_eq 246, 246 if r0 goto L1 else goto L3 :: bool L1: if 1 goto L2 else goto L3 :: bool @@ -346,6 +351,7 @@ L3: L4: r8 = box(None, 1) return r8 + [case testMatchSingleton_python3_10] def f(): match 123: @@ -449,7 +455,7 @@ def f(): r9 :: object_ptr r10, r11 :: object L0: - r0 = 2 == 2 + r0 = int_eq 2, 2 if r0 goto L3 else goto L1 :: bool L1: r1 = load_address PyLong_Type @@ -472,6 +478,7 @@ L4: L5: r11 = box(None, 1) return r11 + [case testMatchAsPattern_python3_10] def f(): match 123: @@ -487,7 +494,7 @@ def f(): r6 :: object_ptr r7, r8 :: object L0: - r0 = 246 == 246 + r0 = int_eq 246, 246 r1 = object 123 x = r1 if r0 goto L1 else goto L2 :: bool @@ -504,6 +511,7 @@ L2: L3: r8 = box(None, 1) return r8 + [case testMatchAsPatternOnOrPattern_python3_10] def f(): match 1: @@ -521,12 +529,12 @@ def f(): r8 :: object_ptr r9, r10 :: object L0: - r0 = 2 == 2 + r0 = int_eq 2, 2 r1 = object 1 x = r1 if r0 goto L3 else goto L1 :: bool L1: - r2 = 2 == 4 + r2 = int_eq 2, 4 r3 = object 2 x = r3 if r2 goto L3 else goto L2 :: bool @@ -545,6 +553,7 @@ L4: L5: r10 = box(None, 1) return r10 + [case testMatchAsPatternOnClassPattern_python3_10] def f(): match 123: @@ -608,22 +617,22 @@ L0: return 1 def f(x): x, r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: bool r4 :: str r5, r6, r7 :: object - r8 :: int32 + r8 :: i32 r9 :: bit r10 :: bool r11 :: str r12, r13, r14 :: object - r15 :: int32 + r15 :: i32 r16 :: bit r17 :: bool r18 :: str r19, r20, r21 :: object - r22 :: int32 + r22 :: i32 r23 :: bit r24 :: bool r25 :: str @@ -637,7 +646,7 @@ L0: r0 = __main__.Position :: type r1 = PyObject_IsInstance(x, r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool if r3 goto L1 else goto L5 :: bool L1: r4 = 'x' @@ -646,7 +655,7 @@ L1: r7 = PyObject_RichCompare(r5, r6, 2) r8 = PyObject_IsTrue(r7) r9 = r8 >= 0 :: signed - r10 = truncate r8: int32 to builtins.bool + r10 = truncate r8: i32 to builtins.bool if r10 goto L2 else goto L5 :: bool L2: r11 = 'y' @@ -655,7 +664,7 @@ L2: r14 = PyObject_RichCompare(r12, r13, 2) r15 = PyObject_IsTrue(r14) r16 = r15 >= 0 :: signed - r17 = truncate r15: int32 to builtins.bool + r17 = truncate r15: i32 to builtins.bool if r17 goto L3 else goto L5 :: bool L3: r18 = 'z' @@ -664,7 +673,7 @@ L3: r21 = PyObject_RichCompare(r19, r20, 2) r22 = PyObject_IsTrue(r21) r23 = r22 >= 0 :: signed - r24 = truncate r22: int32 to builtins.bool + r24 = truncate r22: i32 to builtins.bool if r24 goto L4 else goto L5 :: bool L4: r25 = 'matched' @@ -693,22 +702,22 @@ def f(x): [out] def f(x): x, r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: bool r4 :: str r5, r6, r7 :: object - r8 :: int32 + r8 :: i32 r9 :: bit r10 :: bool r11 :: str r12, r13, r14 :: object - r15 :: int32 + r15 :: i32 r16 :: bit r17 :: bool r18 :: str r19, r20, r21 :: object - r22 :: int32 + r22 :: i32 r23 :: bit r24 :: bool r25 :: str @@ -722,7 +731,7 @@ L0: r0 = __main__.Position :: type r1 = PyObject_IsInstance(x, r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool if r3 goto L1 else goto L5 :: bool L1: r4 = 'z' @@ -731,7 +740,7 @@ L1: r7 = PyObject_RichCompare(r5, r6, 2) r8 = PyObject_IsTrue(r7) r9 = r8 >= 0 :: signed - r10 = truncate r8: int32 to builtins.bool + r10 = truncate r8: i32 to builtins.bool if r10 goto L2 else goto L5 :: bool L2: r11 = 'y' @@ -740,7 +749,7 @@ L2: r14 = PyObject_RichCompare(r12, r13, 2) r15 = PyObject_IsTrue(r14) r16 = r15 >= 0 :: signed - r17 = truncate r15: int32 to builtins.bool + r17 = truncate r15: i32 to builtins.bool if r17 goto L3 else goto L5 :: bool L3: r18 = 'x' @@ -749,7 +758,7 @@ L3: r21 = PyObject_RichCompare(r19, r20, 2) r22 = PyObject_IsTrue(r21) r23 = r22 >= 0 :: signed - r24 = truncate r22: int32 to builtins.bool + r24 = truncate r22: i32 to builtins.bool if r24 goto L4 else goto L5 :: bool L4: r25 = 'matched' @@ -776,16 +785,16 @@ def f(x): [out] def f(x): x, r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: bool r4 :: str r5, r6, r7 :: object - r8 :: int32 + r8 :: i32 r9 :: bit r10 :: bool r11, r12 :: object - r13 :: int32 + r13 :: i32 r14 :: bit r15 :: bool r16 :: str @@ -799,7 +808,7 @@ L0: r0 = __main__.C :: type r1 = PyObject_IsInstance(x, r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool if r3 goto L1 else goto L5 :: bool L1: r4 = 'num' @@ -808,14 +817,14 @@ L1: r7 = PyObject_RichCompare(r5, r6, 2) r8 = PyObject_IsTrue(r7) r9 = r8 >= 0 :: signed - r10 = truncate r8: int32 to builtins.bool + r10 = truncate r8: i32 to builtins.bool if r10 goto L4 else goto L2 :: bool L2: r11 = object 2 r12 = PyObject_RichCompare(r5, r11, 2) r13 = PyObject_IsTrue(r12) r14 = r13 >= 0 :: signed - r15 = truncate r13: int32 to builtins.bool + r15 = truncate r13: i32 to builtins.bool if r15 goto L4 else goto L3 :: bool L3: goto L5 @@ -856,18 +865,18 @@ L0: return 1 def f(x): x, r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: bool r4, y :: __main__.C r5 :: str r6, r7, r8 :: object - r9 :: int32 + r9 :: i32 r10 :: bit r11 :: bool r12 :: str r13, r14, r15 :: object - r16 :: int32 + r16 :: i32 r17 :: bit r18 :: bool r19 :: str @@ -881,7 +890,7 @@ L0: r0 = __main__.C :: type r1 = PyObject_IsInstance(x, r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool if r3 goto L1 else goto L5 :: bool L1: r4 = cast(__main__.C, x) @@ -893,7 +902,7 @@ L2: r8 = PyObject_RichCompare(r6, r7, 2) r9 = PyObject_IsTrue(r8) r10 = r9 >= 0 :: signed - r11 = truncate r9: int32 to builtins.bool + r11 = truncate r9: i32 to builtins.bool if r11 goto L3 else goto L5 :: bool L3: r12 = 'b' @@ -902,7 +911,7 @@ L3: r15 = PyObject_RichCompare(r13, r14, 2) r16 = PyObject_IsTrue(r15) r17 = r16 >= 0 :: signed - r18 = truncate r16: int32 to builtins.bool + r18 = truncate r16: i32 to builtins.bool if r18 goto L4 else goto L5 :: bool L4: r19 = 'matched' @@ -940,7 +949,7 @@ L0: return 1 def f(x): x, r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: bool r4 :: str @@ -957,7 +966,7 @@ L0: r0 = __main__.C :: type r1 = PyObject_IsInstance(x, r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool if r3 goto L1 else goto L3 :: bool L1: r4 = 'x' @@ -986,7 +995,7 @@ def f(x): [out] def f(x): x :: object - r0 :: int32 + r0 :: i32 r1 :: bit r2 :: str r3 :: object @@ -1021,15 +1030,15 @@ def f(x): [out] def f(x): x :: object - r0 :: int32 + r0 :: i32 r1 :: bit r2 :: str - r3 :: int32 + r3 :: i32 r4 :: bit r5 :: object r6 :: str r7 :: object - r8 :: int32 + r8 :: i32 r9 :: bit r10 :: bool r11 :: str @@ -1054,7 +1063,7 @@ L2: r7 = PyObject_RichCompare(r5, r6, 2) r8 = PyObject_IsTrue(r7) r9 = r8 >= 0 :: signed - r10 = truncate r8: int32 to builtins.bool + r10 = truncate r8: i32 to builtins.bool if r10 goto L3 else goto L4 :: bool L3: r11 = 'matched' @@ -1078,7 +1087,7 @@ def f(x): [out] def f(x): x :: object - r0 :: int32 + r0 :: i32 r1 :: bit r2, rest :: dict r3 :: str @@ -1117,19 +1126,19 @@ def f(x): [out] def f(x): x :: object - r0 :: int32 + r0 :: i32 r1 :: bit r2 :: str - r3 :: int32 + r3 :: i32 r4 :: bit r5 :: object r6 :: str r7 :: object - r8 :: int32 + r8 :: i32 r9 :: bit r10 :: bool r11, rest :: dict - r12 :: int32 + r12 :: i32 r13 :: bit r14 :: str r15 :: object @@ -1153,7 +1162,7 @@ L2: r7 = PyObject_RichCompare(r5, r6, 2) r8 = PyObject_IsTrue(r7) r9 = r8 >= 0 :: signed - r10 = truncate r8: int32 to builtins.bool + r10 = truncate r8: i32 to builtins.bool if r10 goto L3 else goto L5 :: bool L3: r11 = CPyDict_FromAny(x) @@ -1182,7 +1191,7 @@ def f(x): [out] def f(x): x :: object - r0 :: int32 + r0 :: i32 r1 :: bit r2 :: native_int r3, r4 :: bit @@ -1224,16 +1233,16 @@ def f(x): [out] def f(x): x :: object - r0 :: int32 + r0 :: i32 r1 :: bit r2 :: native_int r3, r4 :: bit r5, r6, r7 :: object - r8 :: int32 + r8 :: i32 r9 :: bit r10 :: bool r11, r12, r13 :: object - r14 :: int32 + r14 :: i32 r15 :: bit r16 :: bool r17 :: str @@ -1258,7 +1267,7 @@ L2: r7 = PyObject_RichCompare(r5, r6, 2) r8 = PyObject_IsTrue(r7) r9 = r8 >= 0 :: signed - r10 = truncate r8: int32 to builtins.bool + r10 = truncate r8: i32 to builtins.bool if r10 goto L3 else goto L5 :: bool L3: r11 = PySequence_GetItem(x, 1) @@ -1266,7 +1275,7 @@ L3: r13 = PyObject_RichCompare(r11, r12, 2) r14 = PyObject_IsTrue(r13) r15 = r14 >= 0 :: signed - r16 = truncate r14: int32 to builtins.bool + r16 = truncate r14: i32 to builtins.bool if r16 goto L4 else goto L5 :: bool L4: r17 = 'matched' @@ -1290,16 +1299,16 @@ def f(x): [out] def f(x): x :: object - r0 :: int32 + r0 :: i32 r1 :: bit r2 :: native_int r3, r4 :: bit r5, r6, r7 :: object - r8 :: int32 + r8 :: i32 r9 :: bit r10 :: bool r11, r12, r13 :: object - r14 :: int32 + r14 :: i32 r15 :: bit r16 :: bool r17 :: str @@ -1324,7 +1333,7 @@ L2: r7 = PyObject_RichCompare(r5, r6, 2) r8 = PyObject_IsTrue(r7) r9 = r8 >= 0 :: signed - r10 = truncate r8: int32 to builtins.bool + r10 = truncate r8: i32 to builtins.bool if r10 goto L3 else goto L5 :: bool L3: r11 = PySequence_GetItem(x, 1) @@ -1332,7 +1341,7 @@ L3: r13 = PyObject_RichCompare(r11, r12, 2) r14 = PyObject_IsTrue(r13) r15 = r14 >= 0 :: signed - r16 = truncate r14: int32 to builtins.bool + r16 = truncate r14: i32 to builtins.bool if r16 goto L4 else goto L5 :: bool L4: r17 = 'matched' @@ -1356,16 +1365,16 @@ def f(x): [out] def f(x): x :: object - r0 :: int32 + r0 :: i32 r1 :: bit r2 :: native_int r3, r4 :: bit r5, r6, r7 :: object - r8 :: int32 + r8 :: i32 r9 :: bit r10 :: bool r11, r12, r13 :: object - r14 :: int32 + r14 :: i32 r15 :: bit r16 :: bool r17 :: native_int @@ -1392,7 +1401,7 @@ L2: r7 = PyObject_RichCompare(r5, r6, 2) r8 = PyObject_IsTrue(r7) r9 = r8 >= 0 :: signed - r10 = truncate r8: int32 to builtins.bool + r10 = truncate r8: i32 to builtins.bool if r10 goto L3 else goto L6 :: bool L3: r11 = PySequence_GetItem(x, 1) @@ -1400,7 +1409,7 @@ L3: r13 = PyObject_RichCompare(r11, r12, 2) r14 = PyObject_IsTrue(r13) r15 = r14 >= 0 :: signed - r16 = truncate r14: int32 to builtins.bool + r16 = truncate r14: i32 to builtins.bool if r16 goto L4 else goto L6 :: bool L4: r17 = r2 - 0 @@ -1428,21 +1437,21 @@ def f(x): [out] def f(x): x :: object - r0 :: int32 + r0 :: i32 r1 :: bit r2 :: native_int r3, r4 :: bit r5 :: object r6 :: str r7 :: object - r8 :: int32 + r8 :: i32 r9 :: bit r10 :: bool r11 :: native_int r12 :: object r13 :: str r14 :: object - r15 :: int32 + r15 :: i32 r16 :: bit r17 :: bool r18 :: native_int @@ -1469,7 +1478,7 @@ L2: r7 = PyObject_RichCompare(r5, r6, 2) r8 = PyObject_IsTrue(r7) r9 = r8 >= 0 :: signed - r10 = truncate r8: int32 to builtins.bool + r10 = truncate r8: i32 to builtins.bool if r10 goto L3 else goto L6 :: bool L3: r11 = r2 - 1 @@ -1478,7 +1487,7 @@ L3: r14 = PyObject_RichCompare(r12, r13, 2) r15 = PyObject_IsTrue(r14) r16 = r15 >= 0 :: signed - r17 = truncate r15: int32 to builtins.bool + r17 = truncate r15: i32 to builtins.bool if r17 goto L4 else goto L6 :: bool L4: r18 = r2 - 1 @@ -1506,18 +1515,18 @@ def f(x): [out] def f(x): x :: object - r0 :: int32 + r0 :: i32 r1 :: bit r2 :: native_int r3, r4 :: bit r5 :: native_int r6, r7, r8 :: object - r9 :: int32 + r9 :: i32 r10 :: bit r11 :: bool r12 :: native_int r13, r14, r15 :: object - r16 :: int32 + r16 :: i32 r17 :: bit r18 :: bool r19 :: native_int @@ -1545,7 +1554,7 @@ L2: r8 = PyObject_RichCompare(r6, r7, 2) r9 = PyObject_IsTrue(r8) r10 = r9 >= 0 :: signed - r11 = truncate r9: int32 to builtins.bool + r11 = truncate r9: i32 to builtins.bool if r11 goto L3 else goto L6 :: bool L3: r12 = r2 - 1 @@ -1554,7 +1563,7 @@ L3: r15 = PyObject_RichCompare(r13, r14, 2) r16 = PyObject_IsTrue(r15) r17 = r16 >= 0 :: signed - r18 = truncate r16: int32 to builtins.bool + r18 = truncate r16: i32 to builtins.bool if r18 goto L4 else goto L6 :: bool L4: r19 = r2 - 2 @@ -1620,7 +1629,7 @@ def f(x): [out] def f(x): x :: object - r0 :: int32 + r0 :: i32 r1 :: bit r2 :: native_int r3, r4 :: bit @@ -1674,7 +1683,7 @@ def f(x: A | int) -> int: def f(x): x :: union[__main__.A, int] r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: bool r4 :: str @@ -1687,7 +1696,7 @@ L0: r0 = __main__.A :: type r1 = PyObject_IsInstance(x, r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool if r3 goto L1 else goto L3 :: bool L1: r4 = 'a' diff --git a/mypyc/test-data/irbuild-math.test b/mypyc/test-data/irbuild-math.test new file mode 100644 index 000000000000..470e60c74f7d --- /dev/null +++ b/mypyc/test-data/irbuild-math.test @@ -0,0 +1,64 @@ +[case testMathLiteralsAreInlined] +import math +from math import pi, e, tau, inf, nan + +def f1() -> float: + return pi + +def f2() -> float: + return math.pi + +def f3() -> float: + return math.e + +def f4() -> float: + return math.e + +def f5() -> float: + return math.tau + +def f6() -> float: + return math.tau + +def f7() -> float: + return math.inf +def f8() -> float: + return math.inf + +def f9() -> float: + return math.nan + +def f10() -> float: + return math.nan + +[out] +def f1(): +L0: + return 3.141592653589793 +def f2(): +L0: + return 3.141592653589793 +def f3(): +L0: + return 2.718281828459045 +def f4(): +L0: + return 2.718281828459045 +def f5(): +L0: + return 6.283185307179586 +def f6(): +L0: + return 6.283185307179586 +def f7(): +L0: + return inf +def f8(): +L0: + return inf +def f9(): +L0: + return nan +def f10(): +L0: + return nan diff --git a/mypyc/test-data/irbuild-nested.test b/mypyc/test-data/irbuild-nested.test index c5de7d6c02d0..62ae6eb9ee35 100644 --- a/mypyc/test-data/irbuild-nested.test +++ b/mypyc/test-data/irbuild-nested.test @@ -50,25 +50,22 @@ L2: def inner_a_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.inner_a_obj r0 :: __main__.a_env - r1, inner, r2 :: object + r1 :: object L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.inner - inner = r1 - r2 = box(None, 1) - return r2 + r1 = box(None, 1) + return r1 def a(): r0 :: __main__.a_env r1 :: __main__.inner_a_obj - r2, r3 :: bool - r4 :: object + r2 :: bool + inner :: object L0: r0 = a_env() r1 = inner_a_obj() r1.__mypyc_env__ = r0; r2 = is_error - r0.inner = r1; r3 = is_error - r4 = r0.inner - return r4 + inner = r1 + return inner def second_b_first_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -86,15 +83,12 @@ def second_b_first_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.second_b_first_obj r0 :: __main__.first_b_env r1 :: __main__.b_env - r2, second :: object - r3 :: str + r2 :: str L0: r0 = __mypyc_self__.__mypyc_env__ r1 = r0.__mypyc_env__ - r2 = r0.second - second = r2 - r3 = 'b.first.second: nested function' - return r3 + r2 = 'b.first.second: nested function' + return r2 def first_b_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -111,35 +105,30 @@ L2: def first_b_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.first_b_obj r0 :: __main__.b_env - r1, first :: object - r2 :: __main__.first_b_env - r3 :: bool - r4 :: __main__.second_b_first_obj - r5, r6 :: bool - r7 :: object + r1 :: __main__.first_b_env + r2 :: bool + r3 :: __main__.second_b_first_obj + r4 :: bool + second :: object L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.first - first = r1 - r2 = first_b_env() - r2.__mypyc_env__ = r0; r3 = is_error - r4 = second_b_first_obj() - r4.__mypyc_env__ = r2; r5 = is_error - r2.second = r4; r6 = is_error - r7 = r2.second - return r7 + r1 = first_b_env() + r1.__mypyc_env__ = r0; r2 = is_error + r3 = second_b_first_obj() + r3.__mypyc_env__ = r1; r4 = is_error + second = r3 + return second def b(): r0 :: __main__.b_env r1 :: __main__.first_b_obj - r2, r3 :: bool - r4 :: object + r2 :: bool + first :: object L0: r0 = b_env() r1 = first_b_obj() r1.__mypyc_env__ = r0; r2 = is_error - r0.first = r1; r3 = is_error - r4 = r0.first - return r4 + first = r1 + return first def inner_c_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -157,28 +146,24 @@ def inner_c_obj.__call__(__mypyc_self__, s): __mypyc_self__ :: __main__.inner_c_obj s :: str r0 :: __main__.c_env - r1, inner :: object - r2, r3 :: str + r1, r2 :: str L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.inner - inner = r1 - r2 = '!' - r3 = PyUnicode_Concat(s, r2) - return r3 + r1 = '!' + r2 = PyUnicode_Concat(s, r1) + return r2 def c(num): num :: float r0 :: __main__.c_env r1 :: __main__.inner_c_obj - r2, r3 :: bool - r4 :: object + r2 :: bool + inner :: object L0: r0 = c_env() r1 = inner_c_obj() r1.__mypyc_env__ = r0; r2 = is_error - r0.inner = r1; r3 = is_error - r4 = r0.inner - return r4 + inner = r1 + return inner def inner_d_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -196,40 +181,36 @@ def inner_d_obj.__call__(__mypyc_self__, s): __mypyc_self__ :: __main__.inner_d_obj s :: str r0 :: __main__.d_env - r1, inner :: object - r2, r3 :: str + r1, r2 :: str L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.inner - inner = r1 - r2 = '?' - r3 = PyUnicode_Concat(s, r2) - return r3 + r1 = '?' + r2 = PyUnicode_Concat(s, r1) + return r2 def d(num): num :: float r0 :: __main__.d_env r1 :: __main__.inner_d_obj - r2, r3 :: bool - r4 :: str - r5, r6 :: object - r7, a, r8 :: str - r9, r10 :: object - r11, b :: str + r2 :: bool + inner :: object + r3 :: str + r4 :: object + r5, a, r6 :: str + r7 :: object + r8, b :: str L0: r0 = d_env() r1 = inner_d_obj() r1.__mypyc_env__ = r0; r2 = is_error - r0.inner = r1; r3 = is_error - r4 = 'one' - r5 = r0.inner - r6 = PyObject_CallFunctionObjArgs(r5, r4, 0) - r7 = cast(str, r6) - a = r7 - r8 = 'two' - r9 = r0.inner - r10 = PyObject_CallFunctionObjArgs(r9, r8, 0) - r11 = cast(str, r10) - b = r11 + inner = r1 + r3 = 'one' + r4 = PyObject_CallFunctionObjArgs(inner, r3, 0) + r5 = cast(str, r4) + a = r5 + r6 = 'two' + r7 = PyObject_CallFunctionObjArgs(inner, r6, 0) + r8 = cast(str, r7) + b = r8 return a def inner(): r0 :: str @@ -290,32 +271,28 @@ L2: def inner_a_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.inner_a_obj r0 :: __main__.a_env - r1, inner :: object - r2 :: int + r1 :: int L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.inner - inner = r1 - r2 = r0.num - return r2 + r1 = r0.num + return r1 def a(num): num :: int r0 :: __main__.a_env r1 :: bool r2 :: __main__.inner_a_obj - r3, r4 :: bool - r5, r6 :: object - r7 :: int + r3 :: bool + inner, r4 :: object + r5 :: int L0: r0 = a_env() r0.num = num; r1 = is_error r2 = inner_a_obj() r2.__mypyc_env__ = r0; r3 = is_error - r0.inner = r2; r4 = is_error - r5 = r0.inner - r6 = PyObject_CallFunctionObjArgs(r5, 0) - r7 = unbox(int, r6) - return r7 + inner = r2 + r4 = PyObject_CallFunctionObjArgs(inner, 0) + r5 = unbox(int, r4) + return r5 def inner_b_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -332,36 +309,32 @@ L2: def inner_b_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.inner_b_obj r0 :: __main__.b_env - r1, inner :: object - r2 :: bool - foo, r3 :: int + r1 :: bool + foo, r2 :: int L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.inner - inner = r1 - r0.num = 8; r2 = is_error + r0.num = 8; r1 = is_error foo = 12 - r3 = r0.num - return r3 + r2 = r0.num + return r2 def b(): r0 :: __main__.b_env r1 :: bool r2 :: __main__.inner_b_obj - r3, r4 :: bool - r5, r6 :: object - r7, r8, r9 :: int + r3 :: bool + inner, r4 :: object + r5, r6, r7 :: int L0: r0 = b_env() r0.num = 6; r1 = is_error r2 = inner_b_obj() r2.__mypyc_env__ = r0; r3 = is_error - r0.inner = r2; r4 = is_error - r5 = r0.inner - r6 = PyObject_CallFunctionObjArgs(r5, 0) - r7 = unbox(int, r6) - r8 = r0.num - r9 = CPyTagged_Add(r7, r8) - return r9 + inner = r2 + r4 = PyObject_CallFunctionObjArgs(inner, 0) + r5 = unbox(int, r4) + r6 = r0.num + r7 = CPyTagged_Add(r5, r6) + return r7 def inner_c_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -378,14 +351,11 @@ L2: def inner_c_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.inner_c_obj r0 :: __main__.c_env - r1, inner :: object - r2 :: str + r1 :: str L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.inner - inner = r1 - r2 = 'f.inner: first definition' - return r2 + r1 = 'f.inner: first definition' + return r1 def inner_c_obj_0.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -402,40 +372,37 @@ L2: def inner_c_obj_0.__call__(__mypyc_self__): __mypyc_self__ :: __main__.inner_c_obj_0 r0 :: __main__.c_env - r1, inner :: object - r2 :: str + r1 :: str L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.inner - inner = r1 - r2 = 'f.inner: second definition' - return r2 + r1 = 'f.inner: second definition' + return r1 def c(flag): flag :: bool r0 :: __main__.c_env r1 :: __main__.inner_c_obj - r2, r3 :: bool - r4 :: __main__.inner_c_obj_0 - r5, r6 :: bool - r7, r8 :: object - r9 :: str + r2 :: bool + inner :: object + r3 :: __main__.inner_c_obj_0 + r4 :: bool + r5 :: object + r6 :: str L0: r0 = c_env() if flag goto L1 else goto L2 :: bool L1: r1 = inner_c_obj() r1.__mypyc_env__ = r0; r2 = is_error - r0.inner = r1; r3 = is_error + inner = r1 goto L3 L2: - r4 = inner_c_obj_0() - r4.__mypyc_env__ = r0; r5 = is_error - r0.inner = r4; r6 = is_error + r3 = inner_c_obj_0() + r3.__mypyc_env__ = r0; r4 = is_error + inner = r3 L3: - r7 = r0.inner - r8 = PyObject_CallFunctionObjArgs(r7, 0) - r9 = cast(str, r8) - return r9 + r5 = PyObject_CallFunctionObjArgs(inner, 0) + r6 = cast(str, r5) + return r6 [case testSpecialNested] def a() -> int: @@ -465,15 +432,12 @@ def c_a_b_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.c_a_b_obj r0 :: __main__.b_a_env r1 :: __main__.a_env - r2, c :: object - r3 :: int + r2 :: int L0: r0 = __mypyc_self__.__mypyc_env__ r1 = r0.__mypyc_env__ - r2 = r0.c - c = r2 - r3 = r1.x - return r3 + r2 = r1.x + return r2 def b_a_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -490,48 +454,43 @@ L2: def b_a_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.b_a_obj r0 :: __main__.a_env - r1, b :: object - r2 :: __main__.b_a_env - r3 :: bool - r4, r5 :: int - r6 :: bool - r7 :: __main__.c_a_b_obj - r8, r9 :: bool - r10, r11 :: object - r12 :: int + r1 :: __main__.b_a_env + r2 :: bool + r3, r4 :: int + r5 :: bool + r6 :: __main__.c_a_b_obj + r7 :: bool + c, r8 :: object + r9 :: int L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.b - b = r1 - r2 = b_a_env() - r2.__mypyc_env__ = r0; r3 = is_error - r4 = r0.x - r5 = CPyTagged_Add(r4, 2) - r0.x = r5; r6 = is_error - r7 = c_a_b_obj() - r7.__mypyc_env__ = r2; r8 = is_error - r2.c = r7; r9 = is_error - r10 = r2.c - r11 = PyObject_CallFunctionObjArgs(r10, 0) - r12 = unbox(int, r11) - return r12 + r1 = b_a_env() + r1.__mypyc_env__ = r0; r2 = is_error + r3 = r0.x + r4 = CPyTagged_Add(r3, 2) + r0.x = r4; r5 = is_error + r6 = c_a_b_obj() + r6.__mypyc_env__ = r1; r7 = is_error + c = r6 + r8 = PyObject_CallFunctionObjArgs(c, 0) + r9 = unbox(int, r8) + return r9 def a(): r0 :: __main__.a_env r1 :: bool r2 :: __main__.b_a_obj - r3, r4 :: bool - r5, r6 :: object - r7 :: int + r3 :: bool + b, r4 :: object + r5 :: int L0: r0 = a_env() r0.x = 2; r1 = is_error r2 = b_a_obj() r2.__mypyc_env__ = r0; r3 = is_error - r0.b = r2; r4 = is_error - r5 = r0.b - r6 = PyObject_CallFunctionObjArgs(r5, 0) - r7 = unbox(int, r6) - return r7 + b = r2 + r4 = PyObject_CallFunctionObjArgs(b, 0) + r5 = unbox(int, r4) + return r5 [case testNestedFunctionInsideStatements] def f(flag: bool) -> str: @@ -559,14 +518,11 @@ L2: def inner_f_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.inner_f_obj r0 :: __main__.f_env - r1, inner :: object - r2 :: str + r1 :: str L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.inner - inner = r1 - r2 = 'f.inner: first definition' - return r2 + r1 = 'f.inner: first definition' + return r1 def inner_f_obj_0.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -583,40 +539,37 @@ L2: def inner_f_obj_0.__call__(__mypyc_self__): __mypyc_self__ :: __main__.inner_f_obj_0 r0 :: __main__.f_env - r1, inner :: object - r2 :: str + r1 :: str L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.inner - inner = r1 - r2 = 'f.inner: second definition' - return r2 + r1 = 'f.inner: second definition' + return r1 def f(flag): flag :: bool r0 :: __main__.f_env r1 :: __main__.inner_f_obj - r2, r3 :: bool - r4 :: __main__.inner_f_obj_0 - r5, r6 :: bool - r7, r8 :: object - r9 :: str + r2 :: bool + inner :: object + r3 :: __main__.inner_f_obj_0 + r4 :: bool + r5 :: object + r6 :: str L0: r0 = f_env() if flag goto L1 else goto L2 :: bool L1: r1 = inner_f_obj() r1.__mypyc_env__ = r0; r2 = is_error - r0.inner = r1; r3 = is_error + inner = r1 goto L3 L2: - r4 = inner_f_obj_0() - r4.__mypyc_env__ = r0; r5 = is_error - r0.inner = r4; r6 = is_error + r3 = inner_f_obj_0() + r3.__mypyc_env__ = r0; r4 = is_error + inner = r3 L3: - r7 = r0.inner - r8 = PyObject_CallFunctionObjArgs(r7, 0) - r9 = cast(str, r8) - return r9 + r5 = PyObject_CallFunctionObjArgs(inner, 0) + r6 = cast(str, r5) + return r6 [case testNestedFunctionsCallEachOther] from typing import Callable, List @@ -652,15 +605,12 @@ L2: def foo_f_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.foo_f_obj r0 :: __main__.f_env - r1, foo :: object - r2, r3 :: int + r1, r2 :: int L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.foo - foo = r1 - r2 = r0.a - r3 = CPyTagged_Add(r2, 2) - return r3 + r1 = r0.a + r2 = CPyTagged_Add(r1, 2) + return r2 def bar_f_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -677,16 +627,14 @@ L2: def bar_f_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.bar_f_obj r0 :: __main__.f_env - r1, bar, r2, r3 :: object - r4 :: int + r1, r2 :: object + r3 :: int L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.bar - bar = r1 - r2 = r0.foo - r3 = PyObject_CallFunctionObjArgs(r2, 0) - r4 = unbox(int, r3) - return r4 + r1 = r0.foo + r2 = PyObject_CallFunctionObjArgs(r1, 0) + r3 = unbox(int, r2) + return r3 def baz_f_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -704,23 +652,21 @@ def baz_f_obj.__call__(__mypyc_self__, n): __mypyc_self__ :: __main__.baz_f_obj n :: int r0 :: __main__.f_env - r1, baz :: object - r2 :: bit - r3 :: int - r4, r5 :: object + r1 :: bit + r2 :: int + r3, r4, r5 :: object r6, r7 :: int L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.baz - baz = r1 - r2 = n == 0 - if r2 goto L1 else goto L2 :: bool + r1 = int_eq n, 0 + if r1 goto L1 else goto L2 :: bool L1: return 0 L2: - r3 = CPyTagged_Subtract(n, 2) - r4 = box(int, r3) - r5 = PyObject_CallFunctionObjArgs(baz, r4, 0) + r2 = CPyTagged_Subtract(n, 2) + r3 = r0.baz + r4 = box(int, r2) + r5 = PyObject_CallFunctionObjArgs(r3, r4, 0) r6 = unbox(int, r5) r7 = CPyTagged_Add(n, r6) return r7 @@ -850,7 +796,7 @@ def baz(n): r0 :: bit r1, r2, r3 :: int L0: - r0 = n == 0 + r0 = int_eq n, 0 if r0 goto L1 else goto L2 :: bool L1: return 0 @@ -859,4 +805,3 @@ L2: r2 = baz(r1) r3 = CPyTagged_Add(n, r2) return r3 - diff --git a/mypyc/test-data/irbuild-optional.test b/mypyc/test-data/irbuild-optional.test index e98cf1b19e2e..75c008586999 100644 --- a/mypyc/test-data/irbuild-optional.test +++ b/mypyc/test-data/irbuild-optional.test @@ -91,7 +91,7 @@ def f(x): r0 :: object r1 :: bit r2 :: __main__.A - r3 :: int32 + r3 :: i32 r4 :: bit r5 :: bool L0: @@ -102,7 +102,7 @@ L1: r2 = cast(__main__.A, x) r3 = PyObject_IsTrue(r2) r4 = r3 >= 0 :: signed - r5 = truncate r3: int32 to builtins.bool + r5 = truncate r3: i32 to builtins.bool if r5 goto L2 else goto L3 :: bool L2: return 2 @@ -222,7 +222,7 @@ def f(y): L0: r0 = box(None, 1) x = r0 - r1 = y == 2 + r1 = int_eq y, 2 if r1 goto L1 else goto L2 :: bool L1: r2 = box(int, y) @@ -252,7 +252,7 @@ def f(x: Union[int, A]) -> int: def f(x): x :: union[int, __main__.A] r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: bool r4, r5 :: int @@ -262,7 +262,7 @@ L0: r0 = load_address PyLong_Type r1 = PyObject_IsInstance(x, r0) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool if r3 goto L1 else goto L2 :: bool L1: r4 = unbox(int, x) @@ -337,7 +337,7 @@ L3: def set(o, s): o :: union[__main__.A, __main__.B] s, r0 :: str - r1 :: int32 + r1 :: i32 r2 :: bit L0: r0 = 'a' diff --git a/mypyc/test-data/irbuild-set.test b/mypyc/test-data/irbuild-set.test index c567422abac7..1ac638754a8b 100644 --- a/mypyc/test-data/irbuild-set.test +++ b/mypyc/test-data/irbuild-set.test @@ -6,13 +6,13 @@ def f() -> Set[int]: def f(): r0 :: set r1 :: object - r2 :: int32 + r2 :: i32 r3 :: bit r4 :: object - r5 :: int32 + r5 :: i32 r6 :: bit r7 :: object - r8 :: int32 + r8 :: i32 r9 :: bit L0: r0 = PySet_New(0) @@ -79,58 +79,52 @@ L0: def test1(): r0 :: list r1, r2, r3 :: object - r4, r5, r6, r7 :: ptr + r4 :: ptr tmp_list :: list - r8 :: set - r9 :: short_int - r10 :: ptr - r11 :: native_int - r12 :: short_int - r13 :: bit - r14 :: object - r15, x, r16 :: int - r17 :: object - r18 :: int32 - r19 :: bit - r20 :: short_int + r5 :: set + r6 :: short_int + r7 :: native_int + r8 :: short_int + r9 :: bit + r10 :: object + r11, x, r12 :: int + r13 :: object + r14 :: i32 + r15 :: bit + r16 :: short_int a :: set L0: r0 = PyList_New(3) r1 = object 1 r2 = object 3 r3 = object 5 - r4 = get_element_ptr r0 ob_item :: PyListObject - r5 = load_mem r4 :: ptr* - set_mem r5, r1 :: builtins.object* - r6 = r5 + WORD_SIZE*1 - set_mem r6, r2 :: builtins.object* - r7 = r5 + WORD_SIZE*2 - set_mem r7, r3 :: builtins.object* + r4 = list_items r0 + buf_init_item r4, 0, r1 + buf_init_item r4, 1, r2 + buf_init_item r4, 2, r3 keep_alive r0 tmp_list = r0 - r8 = PySet_New(0) - r9 = 0 + r5 = PySet_New(0) + r6 = 0 L1: - r10 = get_element_ptr tmp_list ob_size :: PyVarObject - r11 = load_mem r10 :: native_int* - keep_alive tmp_list - r12 = r11 << 1 - r13 = r9 < r12 :: signed - if r13 goto L2 else goto L4 :: bool + r7 = var_object_size tmp_list + r8 = r7 << 1 + r9 = int_lt r6, r8 + if r9 goto L2 else goto L4 :: bool L2: - r14 = CPyList_GetItemUnsafe(tmp_list, r9) - r15 = unbox(int, r14) - x = r15 - r16 = f(x) - r17 = box(int, r16) - r18 = PySet_Add(r8, r17) - r19 = r18 >= 0 :: signed + r10 = CPyList_GetItemUnsafe(tmp_list, r6) + r11 = unbox(int, r10) + x = r11 + r12 = f(x) + r13 = box(int, r12) + r14 = PySet_Add(r5, r13) + r15 = r14 >= 0 :: signed L3: - r20 = r9 + 2 - r9 = r20 + r16 = r6 + 2 + r6 = r16 goto L1 L4: - a = r8 + a = r5 return 1 def test2(): r0, tmp_tuple :: tuple[int, int, int] @@ -138,7 +132,7 @@ def test2(): r2, r3, r4 :: object r5, x, r6 :: int r7 :: object - r8 :: int32 + r8 :: i32 r9, r10 :: bit b :: set L0: @@ -179,7 +173,7 @@ def test3(): r15 :: object r16, x, r17 :: int r18 :: object - r19 :: int32 + r19 :: i32 r20, r21, r22 :: bit c :: set L0: @@ -225,7 +219,7 @@ def test4(): r2 :: bit r3 :: int r4 :: object - r5 :: int32 + r5 :: i32 r6 :: bit r7 :: short_int d :: set @@ -234,7 +228,7 @@ L0: r1 = 2 x = r1 L1: - r2 = r1 < 12 :: signed + r2 = int_lt r1, 12 if r2 goto L2 else goto L4 :: bool L2: r3 = f(x) @@ -256,7 +250,7 @@ def test5(): r2 :: bit r3 :: int r4 :: object - r5 :: int32 + r5 :: i32 r6 :: bit r7 :: short_int e :: set @@ -265,7 +259,7 @@ L0: r1 = 2 x = r1 L1: - r2 = r1 < 12 :: signed + r2 = int_lt r1, 12 if r2 goto L2 else goto L4 :: bool L2: r3 = f(x) @@ -312,38 +306,32 @@ L0: def test(): r0 :: list r1, r2, r3, r4, r5 :: object - r6, r7, r8, r9, r10, r11 :: ptr + r6 :: ptr tmp_list :: list - r12 :: set - r13, r14 :: list - r15 :: short_int - r16 :: ptr - r17 :: native_int - r18 :: short_int - r19 :: bit - r20 :: object - r21, z :: int - r22 :: native_int - r23 :: bit - r24 :: native_int - r25, r26, r27 :: bit - r28 :: bool - r29 :: bit - r30 :: int - r31 :: object - r32 :: int32 - r33 :: bit - r34 :: short_int - r35, r36, r37 :: object - r38, y, r39 :: int - r40 :: object - r41 :: int32 - r42, r43 :: bit - r44, r45, r46 :: object - r47, x, r48 :: int - r49 :: object - r50 :: int32 - r51, r52 :: bit + r7 :: set + r8, r9 :: list + r10 :: short_int + r11 :: native_int + r12 :: short_int + r13 :: bit + r14 :: object + r15, z :: int + r16 :: bit + r17 :: int + r18 :: object + r19 :: i32 + r20 :: bit + r21 :: short_int + r22, r23, r24 :: object + r25, y, r26 :: int + r27 :: object + r28 :: i32 + r29, r30 :: bit + r31, r32, r33 :: object + r34, x, r35 :: int + r36 :: object + r37 :: i32 + r38, r39 :: bit a :: set L0: r0 = PyList_New(5) @@ -352,96 +340,76 @@ L0: r3 = object 3 r4 = object 4 r5 = object 5 - r6 = get_element_ptr r0 ob_item :: PyListObject - r7 = load_mem r6 :: ptr* - set_mem r7, r1 :: builtins.object* - r8 = r7 + WORD_SIZE*1 - set_mem r8, r2 :: builtins.object* - r9 = r7 + WORD_SIZE*2 - set_mem r9, r3 :: builtins.object* - r10 = r7 + WORD_SIZE*3 - set_mem r10, r4 :: builtins.object* - r11 = r7 + WORD_SIZE*4 - set_mem r11, r5 :: builtins.object* + r6 = list_items r0 + buf_init_item r6, 0, r1 + buf_init_item r6, 1, r2 + buf_init_item r6, 2, r3 + buf_init_item r6, 3, r4 + buf_init_item r6, 4, r5 keep_alive r0 tmp_list = r0 - r12 = PySet_New(0) - r13 = PyList_New(0) - r14 = PyList_New(0) - r15 = 0 + r7 = PySet_New(0) + r8 = PyList_New(0) + r9 = PyList_New(0) + r10 = 0 L1: - r16 = get_element_ptr tmp_list ob_size :: PyVarObject - r17 = load_mem r16 :: native_int* - keep_alive tmp_list - r18 = r17 << 1 - r19 = r15 < r18 :: signed - if r19 goto L2 else goto L9 :: bool + r11 = var_object_size tmp_list + r12 = r11 << 1 + r13 = int_lt r10, r12 + if r13 goto L2 else goto L6 :: bool L2: - r20 = CPyList_GetItemUnsafe(tmp_list, r15) - r21 = unbox(int, r20) - z = r21 - r22 = z & 1 - r23 = r22 == 0 - r24 = 8 & 1 - r25 = r24 == 0 - r26 = r23 & r25 - if r26 goto L3 else goto L4 :: bool + r14 = CPyList_GetItemUnsafe(tmp_list, r10) + r15 = unbox(int, r14) + z = r15 + r16 = int_lt z, 8 + if r16 goto L4 else goto L3 :: bool L3: - r27 = z < 8 :: signed - r28 = r27 goto L5 L4: - r29 = CPyTagged_IsLt_(z, 8) - r28 = r29 + r17 = f1(z) + r18 = box(int, r17) + r19 = PyList_Append(r9, r18) + r20 = r19 >= 0 :: signed L5: - if r28 goto L7 else goto L6 :: bool + r21 = r10 + 2 + r10 = r21 + goto L1 L6: - goto L8 + r22 = PyObject_GetIter(r9) + r23 = PyObject_GetIter(r22) L7: - r30 = f1(z) - r31 = box(int, r30) - r32 = PyList_Append(r14, r31) - r33 = r32 >= 0 :: signed + r24 = PyIter_Next(r23) + if is_error(r24) goto L10 else goto L8 L8: - r34 = r15 + 2 - r15 = r34 - goto L1 + r25 = unbox(int, r24) + y = r25 + r26 = f2(y) + r27 = box(int, r26) + r28 = PyList_Append(r8, r27) + r29 = r28 >= 0 :: signed L9: - r35 = PyObject_GetIter(r14) - r36 = PyObject_GetIter(r35) + goto L7 L10: - r37 = PyIter_Next(r36) - if is_error(r37) goto L13 else goto L11 + r30 = CPy_NoErrOccured() L11: - r38 = unbox(int, r37) - y = r38 - r39 = f2(y) - r40 = box(int, r39) - r41 = PyList_Append(r13, r40) - r42 = r41 >= 0 :: signed + r31 = PyObject_GetIter(r8) + r32 = PyObject_GetIter(r31) L12: - goto L10 + r33 = PyIter_Next(r32) + if is_error(r33) goto L15 else goto L13 L13: - r43 = CPy_NoErrOccured() + r34 = unbox(int, r33) + x = r34 + r35 = f3(x) + r36 = box(int, r35) + r37 = PySet_Add(r7, r36) + r38 = r37 >= 0 :: signed L14: - r44 = PyObject_GetIter(r13) - r45 = PyObject_GetIter(r44) + goto L12 L15: - r46 = PyIter_Next(r45) - if is_error(r46) goto L18 else goto L16 + r39 = CPy_NoErrOccured() L16: - r47 = unbox(int, r46) - x = r47 - r48 = f3(x) - r49 = box(int, r48) - r50 = PySet_Add(r12, r49) - r51 = r50 >= 0 :: signed -L17: - goto L15 -L18: - r52 = CPy_NoErrOccured() -L19: - a = r12 + a = r7 return 1 [case testSetSize] @@ -452,13 +420,13 @@ def f() -> int: def f(): r0 :: set r1 :: object - r2 :: int32 + r2 :: i32 r3 :: bit r4 :: object - r5 :: int32 + r5 :: i32 r6 :: bit r7 :: object - r8 :: int32 + r8 :: i32 r9 :: bit r10 :: ptr r11 :: native_int @@ -489,14 +457,14 @@ def f() -> bool: def f(): r0 :: set r1 :: object - r2 :: int32 + r2 :: i32 r3 :: bit r4 :: object - r5 :: int32 + r5 :: i32 r6 :: bit x :: set r7 :: object - r8 :: int32 + r8 :: i32 r9 :: bit r10 :: bool L0: @@ -511,7 +479,7 @@ L0: r7 = object 5 r8 = PySet_Contains(x, r7) r9 = r8 >= 0 :: signed - r10 = truncate r8: int32 to builtins.bool + r10 = truncate r8: i32 to builtins.bool return r10 [case testSetRemove] @@ -542,7 +510,7 @@ def f() -> Set[int]: def f(): r0, x :: set r1 :: object - r2 :: int32 + r2 :: i32 r3 :: bit L0: r0 = PySet_New(0) @@ -562,7 +530,7 @@ def f() -> Set[int]: def f(): r0, x :: set r1 :: object - r2 :: int32 + r2 :: i32 r3 :: bit L0: r0 = PySet_New(0) @@ -581,7 +549,7 @@ def f() -> Set[int]: [out] def f(): r0, x :: set - r1 :: int32 + r1 :: i32 r2 :: bit L0: r0 = PySet_New(0) @@ -612,7 +580,7 @@ def update(s: Set[int], x: List[int]) -> None: def update(s, x): s :: set x :: list - r0 :: int32 + r0 :: i32 r1 :: bit L0: r0 = _PySet_Update(s, x) @@ -627,17 +595,17 @@ def f(x: Set[int], y: Set[int]) -> Set[int]: def f(x, y): x, y, r0 :: set r1 :: object - r2 :: int32 + r2 :: i32 r3 :: bit r4 :: object - r5 :: int32 + r5 :: i32 r6 :: bit - r7 :: int32 + r7 :: i32 r8 :: bit - r9 :: int32 + r9 :: i32 r10 :: bit r11 :: object - r12 :: int32 + r12 :: i32 r13 :: bit L0: r0 = PySet_New(0) @@ -672,14 +640,14 @@ def not_precomputed_nested_set(i: int) -> bool: def precomputed(i): i :: object r0 :: set - r1 :: int32 + r1 :: i32 r2 :: bit r3 :: bool L0: r0 = frozenset({(), (None, (27,)), 1, 2.0, 3, 4j, False, b'bar', 'daylily', 'foo'}) r1 = PySet_Contains(r0, i) r2 = r1 >= 0 :: signed - r3 = truncate r1: int32 to builtins.bool + r3 = truncate r1: i32 to builtins.bool return r3 def not_precomputed_non_final_name(i): i :: int @@ -689,10 +657,10 @@ def not_precomputed_non_final_name(i): r3 :: int r4 :: set r5 :: object - r6 :: int32 + r6 :: i32 r7 :: bit r8 :: object - r9 :: int32 + r9 :: i32 r10 :: bit r11 :: bool L0: @@ -707,23 +675,23 @@ L0: r8 = box(int, i) r9 = PySet_Contains(r4, r8) r10 = r9 >= 0 :: signed - r11 = truncate r9: int32 to builtins.bool + r11 = truncate r9: i32 to builtins.bool return r11 def not_precomputed_nested_set(i): i :: int r0 :: set r1 :: object - r2 :: int32 + r2 :: i32 r3 :: bit r4 :: object r5 :: set - r6 :: int32 + r6 :: i32 r7 :: bit r8 :: object - r9 :: int32 + r9 :: i32 r10 :: bit r11 :: object - r12 :: int32 + r12 :: i32 r13 :: bit r14 :: bool L0: @@ -741,7 +709,7 @@ L0: r11 = box(int, i) r12 = PySet_Contains(r5, r11) r13 = r12 >= 0 :: signed - r14 = truncate r12: int32 to builtins.bool + r14 = truncate r12: i32 to builtins.bool return r14 [case testForSetLiteral] @@ -809,7 +777,7 @@ def not_precomputed(): r3 :: int r4 :: set r5 :: object - r6 :: int32 + r6 :: i32 r7 :: bit r8, r9 :: object r10, not_optimized :: int @@ -836,4 +804,3 @@ L4: r11 = CPy_NoErrOccured() L5: return 1 - diff --git a/mypyc/test-data/irbuild-singledispatch.test b/mypyc/test-data/irbuild-singledispatch.test index 4e18bbf50d4e..e1053397546f 100644 --- a/mypyc/test-data/irbuild-singledispatch.test +++ b/mypyc/test-data/irbuild-singledispatch.test @@ -16,7 +16,7 @@ def f_obj.__init__(__mypyc_self__): __mypyc_self__ :: __main__.f_obj r0, r1 :: dict r2 :: str - r3 :: int32 + r3 :: i32 r4 :: bit L0: r0 = PyDict_New() @@ -39,7 +39,7 @@ def f_obj.__call__(__mypyc_self__, arg): r9 :: object r10 :: dict r11 :: object - r12 :: int32 + r12 :: i32 r13 :: bit r14 :: object r15 :: ptr @@ -81,7 +81,7 @@ L3: if r17 goto L4 else goto L7 :: bool L4: r18 = unbox(int, r6) - r19 = r18 == 0 + r19 = int_eq r18, 0 if r19 goto L5 else goto L6 :: bool L5: r20 = unbox(int, arg) @@ -148,7 +148,7 @@ def f_obj.__init__(__mypyc_self__): __mypyc_self__ :: __main__.f_obj r0, r1 :: dict r2 :: str - r3 :: int32 + r3 :: i32 r4 :: bit L0: r0 = PyDict_New() @@ -171,7 +171,7 @@ def f_obj.__call__(__mypyc_self__, x): r9 :: object r10 :: dict r11 :: object - r12 :: int32 + r12 :: i32 r13 :: bit r14 :: object r15 :: ptr diff --git a/mypyc/test-data/irbuild-statements.test b/mypyc/test-data/irbuild-statements.test index 090c7ed9f3df..f9d3354b317c 100644 --- a/mypyc/test-data/irbuild-statements.test +++ b/mypyc/test-data/irbuild-statements.test @@ -16,7 +16,7 @@ L0: r0 = 0 i = r0 L1: - r1 = r0 < 10 :: signed + r1 = int_lt r0, 10 if r1 goto L2 else goto L4 :: bool L2: r2 = CPyTagged_Add(x, i) @@ -36,39 +36,21 @@ def f(a: int) -> None: [out] def f(a): a, r0, i :: int - r1 :: native_int - r2 :: bit - r3 :: native_int - r4, r5, r6 :: bit - r7 :: bool - r8 :: bit - r9 :: int + r1 :: bit + r2 :: int L0: r0 = 0 i = r0 L1: - r1 = r0 & 1 - r2 = r1 == 0 - r3 = a & 1 - r4 = r3 == 0 - r5 = r2 & r4 - if r5 goto L2 else goto L3 :: bool + r1 = int_lt r0, a + if r1 goto L2 else goto L4 :: bool L2: - r6 = r0 < a :: signed - r7 = r6 - goto L4 L3: - r8 = CPyTagged_IsLt_(r0, a) - r7 = r8 -L4: - if r7 goto L5 else goto L7 :: bool -L5: -L6: - r9 = CPyTagged_Add(r0, 2) - r0 = r9 - i = r9 + r2 = CPyTagged_Add(r0, 2) + r0 = r2 + i = r2 goto L1 -L7: +L4: return 1 [case testForInNegativeRange] @@ -85,7 +67,7 @@ L0: r0 = 20 i = r0 L1: - r1 = r0 > 0 :: signed + r1 = int_gt r0, 0 if r1 goto L2 else goto L4 :: bool L2: L3: @@ -104,22 +86,14 @@ def f() -> None: [out] def f(): n :: int - r0 :: native_int - r1, r2, r3 :: bit + r0 :: bit L0: n = 0 L1: - r0 = n & 1 - r1 = r0 != 0 - if r1 goto L2 else goto L3 :: bool + r0 = int_lt n, 10 + if r0 goto L2 else goto L3 :: bool L2: - r2 = CPyTagged_IsLt_(n, 10) - if r2 goto L4 else goto L5 :: bool L3: - r3 = n < 10 :: signed - if r3 goto L4 else goto L5 :: bool -L4: -L5: return 1 [case testBreakFor] @@ -136,7 +110,7 @@ L0: r0 = 0 n = r0 L1: - r1 = r0 < 10 :: signed + r1 = int_lt r0, 10 if r1 goto L2 else goto L4 :: bool L2: goto L4 @@ -158,36 +132,19 @@ def f() -> None: [out] def f(): n :: int - r0 :: native_int - r1, r2, r3 :: bit - r4 :: native_int - r5, r6, r7 :: bit + r0, r1 :: bit L0: n = 0 L1: - r0 = n & 1 - r1 = r0 != 0 - if r1 goto L2 else goto L3 :: bool + r0 = int_lt n, 10 + if r0 goto L2 else goto L6 :: bool L2: - r2 = CPyTagged_IsLt_(n, 10) - if r2 goto L4 else goto L10 :: bool L3: - r3 = n < 10 :: signed - if r3 goto L4 else goto L10 :: bool + r1 = int_lt n, 8 + if r1 goto L4 else goto L5 :: bool L4: L5: - r4 = n & 1 - r5 = r4 != 0 - if r5 goto L6 else goto L7 :: bool L6: - r6 = CPyTagged_IsLt_(n, 8) - if r6 goto L8 else goto L9 :: bool -L7: - r7 = n < 8 :: signed - if r7 goto L8 else goto L9 :: bool -L8: -L9: -L10: return 1 [case testContinue] @@ -198,23 +155,15 @@ def f() -> None: [out] def f(): n :: int - r0 :: native_int - r1, r2, r3 :: bit + r0 :: bit L0: n = 0 L1: - r0 = n & 1 - r1 = r0 != 0 - if r1 goto L2 else goto L3 :: bool + r0 = int_lt n, 10 + if r0 goto L2 else goto L3 :: bool L2: - r2 = CPyTagged_IsLt_(n, 10) - if r2 goto L4 else goto L5 :: bool -L3: - r3 = n < 10 :: signed - if r3 goto L4 else goto L5 :: bool -L4: goto L1 -L5: +L3: return 1 [case testContinueFor] @@ -231,7 +180,7 @@ L0: r0 = 0 n = r0 L1: - r1 = r0 < 10 :: signed + r1 = int_lt r0, 10 if r1 goto L2 else goto L4 :: bool L2: L3: @@ -252,38 +201,21 @@ def f() -> None: [out] def f(): n :: int - r0 :: native_int - r1, r2, r3 :: bit - r4 :: native_int - r5, r6, r7 :: bit + r0, r1 :: bit L0: n = 0 L1: - r0 = n & 1 - r1 = r0 != 0 - if r1 goto L2 else goto L3 :: bool + r0 = int_lt n, 10 + if r0 goto L2 else goto L6 :: bool L2: - r2 = CPyTagged_IsLt_(n, 10) - if r2 goto L4 else goto L10 :: bool L3: - r3 = n < 10 :: signed - if r3 goto L4 else goto L10 :: bool + r1 = int_lt n, 8 + if r1 goto L4 else goto L5 :: bool L4: + goto L3 L5: - r4 = n & 1 - r5 = r4 != 0 - if r5 goto L6 else goto L7 :: bool -L6: - r6 = CPyTagged_IsLt_(n, 8) - if r6 goto L8 else goto L9 :: bool -L7: - r7 = n < 8 :: signed - if r7 goto L8 else goto L9 :: bool -L8: - goto L5 -L9: goto L1 -L10: +L6: return 1 [case testForList] @@ -299,32 +231,29 @@ def f(ls): ls :: list y :: int r0 :: short_int - r1 :: ptr - r2 :: native_int - r3 :: short_int - r4 :: bit - r5 :: object - r6, x, r7 :: int - r8 :: short_int + r1 :: native_int + r2 :: short_int + r3 :: bit + r4 :: object + r5, x, r6 :: int + r7 :: short_int L0: y = 0 r0 = 0 L1: - r1 = get_element_ptr ls ob_size :: PyVarObject - r2 = load_mem r1 :: native_int* - keep_alive ls - r3 = r2 << 1 - r4 = r0 < r3 :: signed - if r4 goto L2 else goto L4 :: bool + r1 = var_object_size ls + r2 = r1 << 1 + r3 = int_lt r0, r2 + if r3 goto L2 else goto L4 :: bool L2: - r5 = CPyList_GetItemUnsafe(ls, r0) - r6 = unbox(int, r5) - x = r6 - r7 = CPyTagged_Add(y, x) - y = r7 + r4 = CPyList_GetItemUnsafe(ls, r0) + r5 = unbox(int, r4) + x = r5 + r6 = CPyTagged_Add(y, x) + y = r6 L3: - r8 = r0 + 2 - r0 = r8 + r7 = r0 + 2 + r0 = r7 goto L1 L4: return y @@ -502,16 +431,16 @@ L0: [case testMultipleAssignmentBasicUnpacking] from typing import Tuple, Any -def from_tuple(t: Tuple[int, str]) -> None: +def from_tuple(t: Tuple[bool, None]) -> None: x, y = t def from_any(a: Any) -> None: x, y = a [out] def from_tuple(t): - t :: tuple[int, str] - r0, x :: int - r1, y :: str + t :: tuple[bool, None] + r0, x :: bool + r1, y :: None L0: r0 = t[0] x = r0 @@ -563,16 +492,19 @@ def from_any(a: Any) -> None: [out] def from_tuple(t): t :: tuple[int, object] - r0 :: int - r1, x, r2 :: object - r3, y :: int + r0, r1 :: int + r2, x, r3, r4 :: object + r5, y :: int L0: - r0 = t[0] - r1 = box(int, r0) - x = r1 - r2 = t[1] - r3 = unbox(int, r2) - y = r3 + r0 = borrow t[0] + r1 = unborrow r0 + r2 = box(int, r1) + x = r2 + r3 = borrow t[1] + r4 = unborrow r3 + r5 = unbox(int, r4) + y = r5 + keep_alive steal t return 1 def from_any(a): a, r0, r1 :: object @@ -651,11 +583,11 @@ def f(l: List[int], t: Tuple[int, ...]) -> None: def f(l, t): l :: list t :: tuple - r0 :: int32 + r0 :: i32 r1 :: bit r2, r3, x :: object r4, y :: int - r5 :: int32 + r5 :: i32 r6 :: bit r7, r8 :: object r9 :: int @@ -701,13 +633,13 @@ L2: return 2 def literal_msg(x): x :: object - r0 :: int32 + r0 :: i32 r1 :: bit r2, r3 :: bool L0: r0 = PyObject_IsTrue(x) r1 = r0 >= 0 :: signed - r2 = truncate r0: int32 to builtins.bool + r2 = truncate r0: i32 to builtins.bool if r2 goto L2 else goto L1 :: bool L1: r3 = raise AssertionError('message') @@ -753,40 +685,38 @@ def delListMultiple() -> None: def delList(): r0 :: list r1, r2 :: object - r3, r4, r5 :: ptr + r3 :: ptr l :: list - r6 :: object - r7 :: int32 - r8 :: bit + r4 :: object + r5 :: i32 + r6 :: bit L0: r0 = PyList_New(2) r1 = object 1 r2 = object 2 - r3 = get_element_ptr r0 ob_item :: PyListObject - r4 = load_mem r3 :: ptr* - set_mem r4, r1 :: builtins.object* - r5 = r4 + WORD_SIZE*1 - set_mem r5, r2 :: builtins.object* + r3 = list_items r0 + buf_init_item r3, 0, r1 + buf_init_item r3, 1, r2 keep_alive r0 l = r0 - r6 = object 1 - r7 = PyObject_DelItem(l, r6) - r8 = r7 >= 0 :: signed + r4 = object 1 + r5 = PyObject_DelItem(l, r4) + r6 = r5 >= 0 :: signed return 1 def delListMultiple(): r0 :: list r1, r2, r3, r4, r5, r6, r7 :: object - r8, r9, r10, r11, r12, r13, r14, r15 :: ptr + r8 :: ptr l :: list - r16 :: object - r17 :: int32 - r18 :: bit - r19 :: object - r20 :: int32 - r21 :: bit - r22 :: object - r23 :: int32 - r24 :: bit + r9 :: object + r10 :: i32 + r11 :: bit + r12 :: object + r13 :: i32 + r14 :: bit + r15 :: object + r16 :: i32 + r17 :: bit L0: r0 = PyList_New(7) r1 = object 1 @@ -796,32 +726,25 @@ L0: r5 = object 5 r6 = object 6 r7 = object 7 - r8 = get_element_ptr r0 ob_item :: PyListObject - r9 = load_mem r8 :: ptr* - set_mem r9, r1 :: builtins.object* - r10 = r9 + WORD_SIZE*1 - set_mem r10, r2 :: builtins.object* - r11 = r9 + WORD_SIZE*2 - set_mem r11, r3 :: builtins.object* - r12 = r9 + WORD_SIZE*3 - set_mem r12, r4 :: builtins.object* - r13 = r9 + WORD_SIZE*4 - set_mem r13, r5 :: builtins.object* - r14 = r9 + WORD_SIZE*5 - set_mem r14, r6 :: builtins.object* - r15 = r9 + WORD_SIZE*6 - set_mem r15, r7 :: builtins.object* + r8 = list_items r0 + buf_init_item r8, 0, r1 + buf_init_item r8, 1, r2 + buf_init_item r8, 2, r3 + buf_init_item r8, 3, r4 + buf_init_item r8, 4, r5 + buf_init_item r8, 5, r6 + buf_init_item r8, 6, r7 keep_alive r0 l = r0 - r16 = object 1 - r17 = PyObject_DelItem(l, r16) - r18 = r17 >= 0 :: signed - r19 = object 2 - r20 = PyObject_DelItem(l, r19) - r21 = r20 >= 0 :: signed - r22 = object 3 - r23 = PyObject_DelItem(l, r22) - r24 = r23 >= 0 :: signed + r9 = object 1 + r10 = PyObject_DelItem(l, r9) + r11 = r10 >= 0 :: signed + r12 = object 2 + r13 = PyObject_DelItem(l, r12) + r14 = r13 >= 0 :: signed + r15 = object 3 + r16 = PyObject_DelItem(l, r15) + r17 = r16 >= 0 :: signed return 1 [case testDelDict] @@ -837,7 +760,7 @@ def delDict(): r2, r3 :: object r4, d :: dict r5 :: str - r6 :: int32 + r6 :: i32 r7 :: bit L0: r0 = 'one' @@ -855,9 +778,9 @@ def delDictMultiple(): r4, r5, r6, r7 :: object r8, d :: dict r9, r10 :: str - r11 :: int32 + r11 :: i32 r12 :: bit - r13 :: int32 + r13 :: i32 r14 :: bit L0: r0 = 'one' @@ -901,7 +824,7 @@ L0: def delAttribute(): r0, dummy :: __main__.Dummy r1 :: str - r2 :: int32 + r2 :: i32 r3 :: bit L0: r0 = Dummy(2, 4) @@ -913,10 +836,10 @@ L0: def delAttributeMultiple(): r0, dummy :: __main__.Dummy r1 :: str - r2 :: int32 + r2 :: i32 r3 :: bit r4 :: str - r5 :: int32 + r5 :: i32 r6 :: bit L0: r0 = Dummy(2, 4) @@ -944,35 +867,32 @@ def f(a): r0 :: short_int i :: int r1 :: short_int - r2 :: ptr - r3 :: native_int - r4 :: short_int - r5 :: bit - r6 :: object - r7, x, r8 :: int - r9, r10 :: short_int + r2 :: native_int + r3 :: short_int + r4 :: bit + r5 :: object + r6, x, r7 :: int + r8, r9 :: short_int L0: r0 = 0 i = 0 r1 = 0 L1: - r2 = get_element_ptr a ob_size :: PyVarObject - r3 = load_mem r2 :: native_int* - keep_alive a - r4 = r3 << 1 - r5 = r1 < r4 :: signed - if r5 goto L2 else goto L4 :: bool + r2 = var_object_size a + r3 = r2 << 1 + r4 = int_lt r1, r3 + if r4 goto L2 else goto L4 :: bool L2: - r6 = CPyList_GetItemUnsafe(a, r1) - r7 = unbox(int, r6) - x = r7 - r8 = CPyTagged_Add(i, x) + r5 = CPyList_GetItemUnsafe(a, r1) + r6 = unbox(int, r5) + x = r6 + r7 = CPyTagged_Add(i, x) L3: - r9 = r0 + 2 - r0 = r9 - i = r9 - r10 = r1 + 2 - r1 = r10 + r8 = r0 + 2 + r0 = r8 + i = r8 + r9 = r1 + 2 + r1 = r9 goto L1 L4: L5: @@ -1022,50 +942,47 @@ def f(a, b): b :: object r0 :: short_int r1 :: object - r2 :: ptr - r3 :: native_int - r4 :: short_int - r5 :: bit - r6, r7 :: object - r8, x :: int - r9, y :: bool - r10 :: int32 - r11 :: bit - r12 :: bool - r13 :: short_int - r14 :: bit + r2 :: native_int + r3 :: short_int + r4 :: bit + r5, r6 :: object + r7, x :: int + r8, y :: bool + r9 :: i32 + r10 :: bit + r11 :: bool + r12 :: short_int + r13 :: bit L0: r0 = 0 r1 = PyObject_GetIter(b) L1: - r2 = get_element_ptr a ob_size :: PyVarObject - r3 = load_mem r2 :: native_int* - keep_alive a - r4 = r3 << 1 - r5 = r0 < r4 :: signed - if r5 goto L2 else goto L7 :: bool + r2 = var_object_size a + r3 = r2 << 1 + r4 = int_lt r0, r3 + if r4 goto L2 else goto L7 :: bool L2: - r6 = PyIter_Next(r1) - if is_error(r6) goto L7 else goto L3 + r5 = PyIter_Next(r1) + if is_error(r5) goto L7 else goto L3 L3: - r7 = CPyList_GetItemUnsafe(a, r0) - r8 = unbox(int, r7) - x = r8 - r9 = unbox(bool, r6) - y = r9 - r10 = PyObject_IsTrue(b) - r11 = r10 >= 0 :: signed - r12 = truncate r10: int32 to builtins.bool - if r12 goto L4 else goto L5 :: bool + r6 = CPyList_GetItemUnsafe(a, r0) + r7 = unbox(int, r6) + x = r7 + r8 = unbox(bool, r5) + y = r8 + r9 = PyObject_IsTrue(b) + r10 = r9 >= 0 :: signed + r11 = truncate r9: i32 to builtins.bool + if r11 goto L4 else goto L5 :: bool L4: x = 2 L5: L6: - r13 = r0 + 2 - r0 = r13 + r12 = r0 + 2 + r0 = r12 goto L1 L7: - r14 = CPy_NoErrOccured() + r13 = CPy_NoErrOccured() L8: return 1 def g(a, b): @@ -1075,15 +992,14 @@ def g(a, b): r1, r2 :: short_int z :: int r3 :: object - r4 :: ptr - r5 :: native_int - r6 :: short_int - r7, r8 :: bit - r9, x :: bool - r10 :: object - r11, y :: int - r12, r13 :: short_int - r14 :: bit + r4 :: native_int + r5 :: short_int + r6, r7 :: bit + r8, x :: bool + r9 :: object + r10, y :: int + r11, r12 :: short_int + r13 :: bit L0: r0 = PyObject_GetIter(a) r1 = 0 @@ -1093,30 +1009,52 @@ L1: r3 = PyIter_Next(r0) if is_error(r3) goto L6 else goto L2 L2: - r4 = get_element_ptr b ob_size :: PyVarObject - r5 = load_mem r4 :: native_int* - keep_alive b - r6 = r5 << 1 - r7 = r1 < r6 :: signed - if r7 goto L3 else goto L6 :: bool + r4 = var_object_size b + r5 = r4 << 1 + r6 = int_lt r1, r5 + if r6 goto L3 else goto L6 :: bool L3: - r8 = r2 < 10 :: signed - if r8 goto L4 else goto L6 :: bool + r7 = int_lt r2, 10 + if r7 goto L4 else goto L6 :: bool L4: - r9 = unbox(bool, r3) - x = r9 - r10 = CPyList_GetItemUnsafe(b, r1) - r11 = unbox(int, r10) - y = r11 + r8 = unbox(bool, r3) + x = r8 + r9 = CPyList_GetItemUnsafe(b, r1) + r10 = unbox(int, r9) + y = r10 x = 0 L5: - r12 = r1 + 2 - r1 = r12 - r13 = r2 + 2 - r2 = r13 - z = r13 + r11 = r1 + 2 + r1 = r11 + r12 = r2 + 2 + r2 = r12 + z = r12 goto L1 L6: - r14 = CPy_NoErrOccured() + r13 = CPy_NoErrOccured() L7: return 1 + +[case testConditionalFunctionDefinition] +if int(): + def foo() -> int: + return 0 +else: + def foo() -> int: # E + return 1 + +def bar() -> int: + return 0 + +if int(): + def bar() -> int: # E + return 1 +[out] +main:5: error: Duplicate definition of "foo" not supported by mypyc +main:12: error: Duplicate definition of "bar" not supported by mypyc + +[case testRepeatedUnderscoreFunctions] +def _(arg): pass +def _(arg): pass +[out] +main:2: error: Duplicate definition of "_" not supported by mypyc diff --git a/mypyc/test-data/irbuild-str.test b/mypyc/test-data/irbuild-str.test index 63be7250ebd1..771dcc4c0e68 100644 --- a/mypyc/test-data/irbuild-str.test +++ b/mypyc/test-data/irbuild-str.test @@ -65,7 +65,7 @@ def neq(x: str, y: str) -> bool: [out] def eq(x, y): x, y :: str - r0 :: int32 + r0 :: i32 r1 :: bit r2 :: object r3, r4, r5 :: bit @@ -84,7 +84,7 @@ L3: return r5 def neq(x, y): x, y :: str - r0 :: int32 + r0 :: i32 r1 :: bit r2 :: object r3, r4, r5 :: bit @@ -203,8 +203,8 @@ def f(var, num): r12 :: object r13 :: str r14 :: list - r15, r16, r17 :: ptr - r18, s2, r19, s3, r20, s4 :: str + r15 :: ptr + r16, s2, r17, s3, r18, s4 :: str L0: r0 = "Hi! I'm " r1 = '. I am ' @@ -222,18 +222,16 @@ L0: r12 = CPyObject_CallMethodObjArgs(r7, r11, var, r10, 0) r13 = cast(str, r12) r14 = PyList_New(2) - r15 = get_element_ptr r14 ob_item :: PyListObject - r16 = load_mem r15 :: ptr* - set_mem r16, r6 :: builtins.object* - r17 = r16 + WORD_SIZE*1 - set_mem r17, r13 :: builtins.object* + r15 = list_items r14 + buf_init_item r15, 0, r6 + buf_init_item r15, 1, r13 keep_alive r14 - r18 = PyUnicode_Join(r5, r14) - s2 = r18 - r19 = '' - s3 = r19 - r20 = 'abc' - s4 = r20 + r16 = PyUnicode_Join(r5, r14) + s2 = r16 + r17 = '' + s3 = r17 + r18 = 'abc' + s4 = r18 return 1 [case testStringFormattingCStyle] diff --git a/mypyc/test-data/irbuild-strip-asserts.test b/mypyc/test-data/irbuild-strip-asserts.test index e90905dc5d81..25fd29818202 100644 --- a/mypyc/test-data/irbuild-strip-asserts.test +++ b/mypyc/test-data/irbuild-strip-asserts.test @@ -10,4 +10,3 @@ L0: r0 = object 3 x = r0 return x - diff --git a/mypyc/test-data/irbuild-try.test b/mypyc/test-data/irbuild-try.test index faf3fa1dbd2f..a5b7b9a55b86 100644 --- a/mypyc/test-data/irbuild-try.test +++ b/mypyc/test-data/irbuild-try.test @@ -337,7 +337,7 @@ def foo(x): r11, r12 :: object r13, r14 :: tuple[object, object, object] r15, r16, r17, r18 :: object - r19 :: int32 + r19 :: i32 r20 :: bit r21 :: bool r22 :: bit @@ -372,7 +372,7 @@ L3: (handler for L2) r18 = PyObject_CallFunctionObjArgs(r3, r0, r15, r16, r17, 0) r19 = PyObject_IsTrue(r18) r20 = r19 >= 0 :: signed - r21 = truncate r19: int32 to builtins.bool + r21 = truncate r19: i32 to builtins.bool if r21 goto L5 else goto L4 :: bool L4: CPy_Reraise() @@ -448,7 +448,7 @@ def foo(x): r9, r10, r11 :: object r12 :: None r13 :: object - r14 :: int32 + r14 :: i32 r15 :: bit r16 :: bool r17 :: bit @@ -478,7 +478,7 @@ L3: (handler for L2) r13 = box(None, r12) r14 = PyObject_IsTrue(r13) r15 = r14 >= 0 :: signed - r16 = truncate r14: int32 to builtins.bool + r16 = truncate r14: i32 to builtins.bool if r16 goto L5 else goto L4 :: bool L4: CPy_Reraise() diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index 6a86a6c6781b..a6813de4ee44 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -62,15 +62,12 @@ def f(x: Tuple[int, ...]) -> int: [out] def f(x): x :: tuple - r0 :: ptr - r1 :: native_int - r2 :: short_int + r0 :: native_int + r1 :: short_int L0: - r0 = get_element_ptr x ob_size :: PyVarObject - r1 = load_mem r0 :: native_int* - keep_alive x - r2 = r1 << 1 - return r2 + r0 = var_object_size x + r1 = r0 << 1 + return r1 [case testSequenceTupleForced] from typing import Tuple @@ -101,28 +98,26 @@ def f(x, y): x, y :: object r0 :: list r1, r2 :: object - r3, r4, r5 :: ptr - r6, r7, r8 :: object - r9 :: int32 - r10 :: bit - r11 :: tuple + r3 :: ptr + r4, r5, r6 :: object + r7 :: i32 + r8 :: bit + r9 :: tuple L0: r0 = PyList_New(2) r1 = object 1 r2 = object 2 - r3 = get_element_ptr r0 ob_item :: PyListObject - r4 = load_mem r3 :: ptr* - set_mem r4, r1 :: builtins.object* - r5 = r4 + WORD_SIZE*1 - set_mem r5, r2 :: builtins.object* + r3 = list_items r0 + buf_init_item r3, 0, r1 + buf_init_item r3, 1, r2 keep_alive r0 - r6 = CPyList_Extend(r0, x) - r7 = CPyList_Extend(r0, y) - r8 = object 3 - r9 = PyList_Append(r0, r8) - r10 = r9 >= 0 :: signed - r11 = PyList_AsTuple(r0) - return r11 + r4 = CPyList_Extend(r0, x) + r5 = CPyList_Extend(r0, y) + r6 = object 3 + r7 = PyList_Append(r0, r6) + r8 = r7 >= 0 :: signed + r9 = PyList_AsTuple(r0) + return r9 [case testTupleFor] from typing import Tuple, List @@ -133,29 +128,26 @@ def f(xs: Tuple[str, ...]) -> None: def f(xs): xs :: tuple r0 :: short_int - r1 :: ptr - r2 :: native_int - r3 :: short_int - r4 :: bit - r5 :: object - r6, x :: str - r7 :: short_int + r1 :: native_int + r2 :: short_int + r3 :: bit + r4 :: object + r5, x :: str + r6 :: short_int L0: r0 = 0 L1: - r1 = get_element_ptr xs ob_size :: PyVarObject - r2 = load_mem r1 :: native_int* - keep_alive xs - r3 = r2 << 1 - r4 = r0 < r3 :: signed - if r4 goto L2 else goto L4 :: bool + r1 = var_object_size xs + r2 = r1 << 1 + r3 = int_lt r0, r2 + if r3 goto L2 else goto L4 :: bool L2: - r5 = CPySequenceTuple_GetItem(xs, r0) - r6 = cast(str, r5) - x = r6 + r4 = CPySequenceTuple_GetItem(xs, r0) + r5 = cast(str, r4) + x = r5 L3: - r7 = r0 + 2 - r0 = r7 + r6 = r0 + 2 + r0 = r6 goto L1 L4: return 1 @@ -195,70 +187,30 @@ def f(i: int) -> bool: [out] def f(i): i :: int - r0 :: native_int - r1, r2 :: bit + r0 :: bit + r1 :: bool + r2 :: bit r3 :: bool r4 :: bit - r5 :: bool - r6 :: native_int - r7, r8 :: bit - r9 :: bool - r10 :: bit - r11 :: bool - r12 :: native_int - r13, r14 :: bit - r15 :: bool - r16 :: bit L0: - r0 = i & 1 - r1 = r0 == 0 - if r1 goto L1 else goto L2 :: bool + r0 = int_eq i, 2 + if r0 goto L1 else goto L2 :: bool L1: - r2 = i == 2 - r3 = r2 + r1 = r0 goto L3 L2: - r4 = CPyTagged_IsEq_(i, 2) - r3 = r4 + r2 = int_eq i, 4 + r1 = r2 L3: - if r3 goto L4 else goto L5 :: bool + if r1 goto L4 else goto L5 :: bool L4: - r5 = r3 - goto L9 + r3 = r1 + goto L6 L5: - r6 = i & 1 - r7 = r6 == 0 - if r7 goto L6 else goto L7 :: bool + r4 = int_eq i, 6 + r3 = r4 L6: - r8 = i == 4 - r9 = r8 - goto L8 -L7: - r10 = CPyTagged_IsEq_(i, 4) - r9 = r10 -L8: - r5 = r9 -L9: - if r5 goto L10 else goto L11 :: bool -L10: - r11 = r5 - goto L15 -L11: - r12 = i & 1 - r13 = r12 == 0 - if r13 goto L12 else goto L13 :: bool -L12: - r14 = i == 6 - r15 = r14 - goto L14 -L13: - r16 = CPyTagged_IsEq_(i, 6) - r15 = r16 -L14: - r11 = r15 -L15: - return r11 - + return r3 [case testTupleBuiltFromList] def f(val: int) -> bool: @@ -270,83 +222,61 @@ def test() -> None: [out] def f(val): val, r0 :: int - r1 :: native_int - r2, r3 :: bit - r4 :: bool - r5 :: bit + r1 :: bit L0: r0 = CPyTagged_Remainder(val, 4) - r1 = r0 & 1 - r2 = r1 == 0 - if r2 goto L1 else goto L2 :: bool -L1: - r3 = r0 == 0 - r4 = r3 - goto L3 -L2: - r5 = CPyTagged_IsEq_(r0, 0) - r4 = r5 -L3: - return r4 + r1 = int_eq r0, 0 + return r1 def test(): r0 :: list r1, r2, r3 :: object - r4, r5, r6, r7 :: ptr + r4 :: ptr source :: list - r8 :: ptr - r9 :: native_int - r10 :: tuple - r11 :: short_int - r12 :: ptr - r13 :: native_int - r14 :: short_int + r5 :: native_int + r6 :: tuple + r7 :: short_int + r8 :: native_int + r9 :: short_int + r10 :: bit + r11 :: object + r12, x :: int + r13 :: bool + r14 :: object r15 :: bit - r16 :: object - r17, x :: int - r18 :: bool - r19 :: object - r20 :: bit - r21 :: short_int + r16 :: short_int a :: tuple L0: r0 = PyList_New(3) r1 = object 1 r2 = object 2 r3 = object 3 - r4 = get_element_ptr r0 ob_item :: PyListObject - r5 = load_mem r4 :: ptr* - set_mem r5, r1 :: builtins.object* - r6 = r5 + WORD_SIZE*1 - set_mem r6, r2 :: builtins.object* - r7 = r5 + WORD_SIZE*2 - set_mem r7, r3 :: builtins.object* + r4 = list_items r0 + buf_init_item r4, 0, r1 + buf_init_item r4, 1, r2 + buf_init_item r4, 2, r3 keep_alive r0 source = r0 - r8 = get_element_ptr source ob_size :: PyVarObject - r9 = load_mem r8 :: native_int* - keep_alive source - r10 = PyTuple_New(r9) - r11 = 0 + r5 = var_object_size source + r6 = PyTuple_New(r5) + r7 = 0 L1: - r12 = get_element_ptr source ob_size :: PyVarObject - r13 = load_mem r12 :: native_int* - keep_alive source - r14 = r13 << 1 - r15 = r11 < r14 :: signed - if r15 goto L2 else goto L4 :: bool + r8 = var_object_size source + r9 = r8 << 1 + r10 = int_lt r7, r9 + if r10 goto L2 else goto L4 :: bool L2: - r16 = CPyList_GetItemUnsafe(source, r11) - r17 = unbox(int, r16) - x = r17 - r18 = f(x) - r19 = box(bool, r18) - r20 = CPySequenceTuple_SetItemUnsafe(r10, r11, r19) + r11 = CPyList_GetItemUnsafe(source, r7) + r12 = unbox(int, r11) + x = r12 + r13 = f(x) + r14 = box(bool, r13) + r15 = CPySequenceTuple_SetItemUnsafe(r6, r7, r14) L3: - r21 = r11 + 2 - r11 = r21 + r16 = r7 + 2 + r7 = r16 goto L1 L4: - a = r10 + a = r6 return 1 [case testTupleBuiltFromStr] @@ -388,7 +318,7 @@ L1: r5 = CPyStr_Size_size_t(source) r6 = r5 >= 0 :: signed r7 = r5 << 1 - r8 = r4 < r7 :: signed + r8 = int_lt r4, r7 if r8 goto L2 else goto L4 :: bool L2: r9 = CPyStr_GetItem(source, r4) @@ -419,44 +349,38 @@ L0: return r0 def test(source): source :: tuple - r0 :: ptr - r1 :: native_int - r2 :: tuple - r3 :: short_int - r4 :: ptr - r5 :: native_int - r6 :: short_int - r7 :: bit - r8 :: object - r9, x, r10 :: bool - r11 :: object - r12 :: bit - r13 :: short_int + r0 :: native_int + r1 :: tuple + r2 :: short_int + r3 :: native_int + r4 :: short_int + r5 :: bit + r6 :: object + r7, x, r8 :: bool + r9 :: object + r10 :: bit + r11 :: short_int a :: tuple L0: - r0 = get_element_ptr source ob_size :: PyVarObject - r1 = load_mem r0 :: native_int* - keep_alive source - r2 = PyTuple_New(r1) - r3 = 0 + r0 = var_object_size source + r1 = PyTuple_New(r0) + r2 = 0 L1: - r4 = get_element_ptr source ob_size :: PyVarObject - r5 = load_mem r4 :: native_int* - keep_alive source - r6 = r5 << 1 - r7 = r3 < r6 :: signed - if r7 goto L2 else goto L4 :: bool + r3 = var_object_size source + r4 = r3 << 1 + r5 = int_lt r2, r4 + if r5 goto L2 else goto L4 :: bool L2: - r8 = CPySequenceTuple_GetItem(source, r3) - r9 = unbox(bool, r8) - x = r9 - r10 = f(x) - r11 = box(bool, r10) - r12 = CPySequenceTuple_SetItemUnsafe(r2, r3, r11) + r6 = CPySequenceTuple_GetItem(source, r2) + r7 = unbox(bool, r6) + x = r7 + r8 = f(x) + r9 = box(bool, r8) + r10 = CPySequenceTuple_SetItemUnsafe(r1, r2, r9) L3: - r13 = r3 + 2 - r3 = r13 + r11 = r2 + 2 + r2 = r11 goto L1 L4: - a = r2 + a = r1 return 1 diff --git a/mypyc/test-data/irbuild-u8.test b/mypyc/test-data/irbuild-u8.test new file mode 100644 index 000000000000..14f691c9451f --- /dev/null +++ b/mypyc/test-data/irbuild-u8.test @@ -0,0 +1,543 @@ +# Test cases for u8 native ints. Focus on things that are different from i64; no need to +# duplicate all i64 test cases here. + +[case testU8BinaryOp] +from mypy_extensions import u8 + +def add_op(x: u8, y: u8) -> u8: + x = y + x + y = x + 5 + y += x + y += 7 + x = 5 + y + return x +def compare(x: u8, y: u8) -> None: + a = x == y + b = x == 5 + c = x < y + d = x < 5 + e = 5 == x + f = 5 < x +[out] +def add_op(x, y): + x, y, r0, r1, r2, r3, r4 :: u8 +L0: + r0 = y + x + x = r0 + r1 = x + 5 + y = r1 + r2 = y + x + y = r2 + r3 = y + 7 + y = r3 + r4 = 5 + y + x = r4 + return x +def compare(x, y): + x, y :: u8 + r0 :: bit + a :: bool + r1 :: bit + b :: bool + r2 :: bit + c :: bool + r3 :: bit + d :: bool + r4 :: bit + e :: bool + r5 :: bit + f :: bool +L0: + r0 = x == y + a = r0 + r1 = x == 5 + b = r1 + r2 = x < y :: unsigned + c = r2 + r3 = x < 5 :: unsigned + d = r3 + r4 = 5 == x + e = r4 + r5 = 5 < x :: unsigned + f = r5 + return 1 + +[case testU8UnaryOp] +from mypy_extensions import u8 + +def unary(x: u8) -> u8: + y = -x + x = ~y + y = +x + return y +[out] +def unary(x): + x, r0, y, r1 :: u8 +L0: + r0 = 0 - x + y = r0 + r1 = y ^ 255 + x = r1 + y = x + return y + +[case testU8DivisionByConstant] +from mypy_extensions import u8 + +def div_by_constant(x: u8) -> u8: + x = x // 5 + x //= 17 + return x +[out] +def div_by_constant(x): + x, r0, r1 :: u8 +L0: + r0 = x / 5 + x = r0 + r1 = x / 17 + x = r1 + return x + +[case testU8ModByConstant] +from mypy_extensions import u8 + +def mod_by_constant(x: u8) -> u8: + x = x % 5 + x %= 17 + return x +[out] +def mod_by_constant(x): + x, r0, r1 :: u8 +L0: + r0 = x % 5 + x = r0 + r1 = x % 17 + x = r1 + return x + +[case testU8DivModByVariable] +from mypy_extensions import u8 + +def divmod(x: u8, y: u8) -> u8: + a = x // y + return a % y +[out] +def divmod(x, y): + x, y :: u8 + r0 :: bit + r1 :: bool + r2, a :: u8 + r3 :: bit + r4 :: bool + r5 :: u8 +L0: + r0 = y == 0 + if r0 goto L1 else goto L2 :: bool +L1: + r1 = raise ZeroDivisionError('integer division or modulo by zero') + unreachable +L2: + r2 = x / y + a = r2 + r3 = y == 0 + if r3 goto L3 else goto L4 :: bool +L3: + r4 = raise ZeroDivisionError('integer division or modulo by zero') + unreachable +L4: + r5 = a % y + return r5 + +[case testU8BinaryOperationWithOutOfRangeOperand] +from mypy_extensions import u8 + +def out_of_range(x: u8) -> None: + x + (-1) + (-2) + x + x * 256 + -1 < x + x > -5 + x == 1000 + x + 255 # OK + 255 + x # OK +[out] +main:4: error: Value -1 is out of range for "u8" +main:5: error: Value -2 is out of range for "u8" +main:6: error: Value 256 is out of range for "u8" +main:7: error: Value -1 is out of range for "u8" +main:8: error: Value -5 is out of range for "u8" +main:9: error: Value 1000 is out of range for "u8" + +[case testU8DetectMoreOutOfRangeLiterals] +from mypy_extensions import u8 + +def out_of_range() -> None: + a: u8 = 256 + b: u8 = -1 + f(256) + # The following are ok + c: u8 = 0 + d: u8 = 255 + f(0) + f(255) + +def f(x: u8) -> None: pass +[out] +main:4: error: Value 256 is out of range for "u8" +main:5: error: Value -1 is out of range for "u8" +main:6: error: Value 256 is out of range for "u8" + +[case testU8BoxAndUnbox] +from typing import Any +from mypy_extensions import u8 + +def f(x: Any) -> Any: + y: u8 = x + return y +[out] +def f(x): + x :: object + r0, y :: u8 + r1 :: object +L0: + r0 = unbox(u8, x) + y = r0 + r1 = box(u8, y) + return r1 + +[case testU8MixedCompare1] +from mypy_extensions import u8 +def f(x: int, y: u8) -> bool: + return x == y +[out] +def f(x, y): + x :: int + y :: u8 + r0 :: native_int + r1, r2, r3 :: bit + r4 :: native_int + r5, r6 :: u8 + r7 :: bit +L0: + r0 = x & 1 + r1 = r0 == 0 + if r1 goto L1 else goto L4 :: bool +L1: + r2 = x < 512 :: signed + if r2 goto L2 else goto L4 :: bool +L2: + r3 = x >= 0 :: signed + if r3 goto L3 else goto L4 :: bool +L3: + r4 = x >> 1 + r5 = truncate r4: native_int to u8 + r6 = r5 + goto L5 +L4: + CPyUInt8_Overflow() + unreachable +L5: + r7 = r6 == y + return r7 + +[case testU8MixedCompare2] +from mypy_extensions import u8 +def f(x: u8, y: int) -> bool: + return x == y +[out] +def f(x, y): + x :: u8 + y :: int + r0 :: native_int + r1, r2, r3 :: bit + r4 :: native_int + r5, r6 :: u8 + r7 :: bit +L0: + r0 = y & 1 + r1 = r0 == 0 + if r1 goto L1 else goto L4 :: bool +L1: + r2 = y < 512 :: signed + if r2 goto L2 else goto L4 :: bool +L2: + r3 = y >= 0 :: signed + if r3 goto L3 else goto L4 :: bool +L3: + r4 = y >> 1 + r5 = truncate r4: native_int to u8 + r6 = r5 + goto L5 +L4: + CPyUInt8_Overflow() + unreachable +L5: + r7 = x == r6 + return r7 + +[case testU8ConvertToInt] +from mypy_extensions import u8 + +def u8_to_int(a: u8) -> int: + return a +[out] +def u8_to_int(a): + a :: u8 + r0 :: native_int + r1 :: int +L0: + r0 = extend a: u8 to native_int + r1 = r0 << 1 + return r1 + +[case testU8OperatorAssignmentMixed] +from mypy_extensions import u8 + +def f(a: u8) -> None: + x = 0 + x += a +[out] +def f(a): + a :: u8 + x :: int + r0 :: native_int + r1, r2, r3 :: bit + r4 :: native_int + r5, r6, r7 :: u8 + r8 :: native_int + r9 :: int +L0: + x = 0 + r0 = x & 1 + r1 = r0 == 0 + if r1 goto L1 else goto L4 :: bool +L1: + r2 = x < 512 :: signed + if r2 goto L2 else goto L4 :: bool +L2: + r3 = x >= 0 :: signed + if r3 goto L3 else goto L4 :: bool +L3: + r4 = x >> 1 + r5 = truncate r4: native_int to u8 + r6 = r5 + goto L5 +L4: + CPyUInt8_Overflow() + unreachable +L5: + r7 = r6 + a + r8 = extend r7: u8 to native_int + r9 = r8 << 1 + x = r9 + return 1 + +[case testU8InitializeFromLiteral] +from mypy_extensions import u8, i64 + +def f() -> None: + x: u8 = 0 + y: u8 = 255 + z: u8 = 5 + 7 +[out] +def f(): + x, y, z :: u8 +L0: + x = 0 + y = 255 + z = 12 + return 1 + +[case testU8ExplicitConversionFromNativeInt] +from mypy_extensions import i64, i32, i16, u8 + +def from_u8(x: u8) -> u8: + return u8(x) + +def from_i16(x: i16) -> u8: + return u8(x) + +def from_i32(x: i32) -> u8: + return u8(x) + +def from_i64(x: i64) -> u8: + return u8(x) +[out] +def from_u8(x): + x :: u8 +L0: + return x +def from_i16(x): + x :: i16 + r0 :: u8 +L0: + r0 = truncate x: i16 to u8 + return r0 +def from_i32(x): + x :: i32 + r0 :: u8 +L0: + r0 = truncate x: i32 to u8 + return r0 +def from_i64(x): + x :: i64 + r0 :: u8 +L0: + r0 = truncate x: i64 to u8 + return r0 + +[case testU8ExplicitConversionToNativeInt] +from mypy_extensions import i64, i32, i16, u8 + +def to_i16(x: u8) -> i16: + return i16(x) + +def to_i32(x: u8) -> i32: + return i32(x) + +def to_i64(x: u8) -> i64: + return i64(x) +[out] +def to_i16(x): + x :: u8 + r0 :: i16 +L0: + r0 = extend x: u8 to i16 + return r0 +def to_i32(x): + x :: u8 + r0 :: i32 +L0: + r0 = extend x: u8 to i32 + return r0 +def to_i64(x): + x :: u8 + r0 :: i64 +L0: + r0 = extend x: u8 to i64 + return r0 + +[case testU8ExplicitConversionFromInt] +from mypy_extensions import u8 + +def f(x: int) -> u8: + return u8(x) +[out] +def f(x): + x :: int + r0 :: native_int + r1, r2, r3 :: bit + r4 :: native_int + r5, r6 :: u8 +L0: + r0 = x & 1 + r1 = r0 == 0 + if r1 goto L1 else goto L4 :: bool +L1: + r2 = x < 512 :: signed + if r2 goto L2 else goto L4 :: bool +L2: + r3 = x >= 0 :: signed + if r3 goto L3 else goto L4 :: bool +L3: + r4 = x >> 1 + r5 = truncate r4: native_int to u8 + r6 = r5 + goto L5 +L4: + CPyUInt8_Overflow() + unreachable +L5: + return r6 + +[case testU8ExplicitConversionFromLiteral] +from mypy_extensions import u8 + +def f() -> None: + x = u8(0) + y = u8(11) + z = u8(-3) # Truncate + zz = u8(258) # Truncate + a = u8(255) +[out] +def f(): + x, y, z, zz, a :: u8 +L0: + x = 0 + y = 11 + z = 253 + zz = 2 + a = 255 + return 1 + +[case testU8ExplicitConversionFromVariousTypes] +from mypy_extensions import u8 + +def bool_to_u8(b: bool) -> u8: + return u8(b) + +def str_to_u8(s: str) -> u8: + return u8(s) + +class C: + def __int__(self) -> u8: + return 5 + +def instance_to_u8(c: C) -> u8: + return u8(c) + +def float_to_u8(x: float) -> u8: + return u8(x) +[out] +def bool_to_u8(b): + b :: bool + r0 :: u8 +L0: + r0 = extend b: builtins.bool to u8 + return r0 +def str_to_u8(s): + s :: str + r0 :: object + r1 :: u8 +L0: + r0 = CPyLong_FromStr(s) + r1 = unbox(u8, r0) + return r1 +def C.__int__(self): + self :: __main__.C +L0: + return 5 +def instance_to_u8(c): + c :: __main__.C + r0 :: u8 +L0: + r0 = c.__int__() + return r0 +def float_to_u8(x): + x :: float + r0 :: int + r1 :: native_int + r2, r3, r4 :: bit + r5 :: native_int + r6, r7 :: u8 +L0: + r0 = CPyTagged_FromFloat(x) + r1 = r0 & 1 + r2 = r1 == 0 + if r2 goto L1 else goto L4 :: bool +L1: + r3 = r0 < 512 :: signed + if r3 goto L2 else goto L4 :: bool +L2: + r4 = r0 >= 0 :: signed + if r4 goto L3 else goto L4 :: bool +L3: + r5 = r0 >> 1 + r6 = truncate r5: native_int to u8 + r7 = r6 + goto L5 +L4: + CPyUInt8_Overflow() + unreachable +L5: + return r7 diff --git a/mypyc/test-data/irbuild-unreachable.test b/mypyc/test-data/irbuild-unreachable.test index 2c164491a5a1..b5188c91ac58 100644 --- a/mypyc/test-data/irbuild-unreachable.test +++ b/mypyc/test-data/irbuild-unreachable.test @@ -1,4 +1,4 @@ -# Test cases for unreachable expressions +# Test cases for unreachable expressions and statements [case testUnreachableMemberExpr] import sys @@ -11,7 +11,7 @@ def f(): r1 :: str r2 :: object r3, r4 :: str - r5 :: int32 + r5 :: i32 r6 :: bit r7 :: object r8, r9, r10 :: bit @@ -68,7 +68,7 @@ def f(): r1 :: str r2 :: object r3, r4 :: str - r5 :: int32 + r5 :: i32 r6 :: bit r7 :: object r8, r9, r10 :: bit @@ -104,3 +104,138 @@ L5: L6: y = r11 return 1 + +[case testUnreachableStatementAfterReturn] +def f(x: bool) -> int: + if x: + return 1 + f(False) + return 2 +[out] +def f(x): + x :: bool +L0: + if x goto L1 else goto L2 :: bool +L1: + return 2 +L2: + return 4 + +[case testUnreachableStatementAfterContinue] +def c() -> bool: + return False + +def f() -> None: + n = True + while n: + if c(): + continue + if int(): + f() + n = False +[out] +def c(): +L0: + return 0 +def f(): + n, r0 :: bool +L0: + n = 1 +L1: + if n goto L2 else goto L5 :: bool +L2: + r0 = c() + if r0 goto L3 else goto L4 :: bool +L3: + goto L1 +L4: + n = 0 + goto L1 +L5: + return 1 + +[case testUnreachableStatementAfterBreak] +def c() -> bool: + return False + +def f() -> None: + n = True + while n: + if c(): + break + if int(): + f() + n = False +[out] +def c(): +L0: + return 0 +def f(): + n, r0 :: bool +L0: + n = 1 +L1: + if n goto L2 else goto L5 :: bool +L2: + r0 = c() + if r0 goto L3 else goto L4 :: bool +L3: + goto L5 +L4: + n = 0 + goto L1 +L5: + return 1 + +[case testUnreachableStatementAfterRaise] +def f(x: bool) -> int: + if x: + raise ValueError() + print('hello') + return 2 +[out] +def f(x): + x :: bool + r0 :: object + r1 :: str + r2, r3 :: object +L0: + if x goto L1 else goto L2 :: bool +L1: + r0 = builtins :: module + r1 = 'ValueError' + r2 = CPyObject_GetAttr(r0, r1) + r3 = PyObject_CallFunctionObjArgs(r2, 0) + CPy_Raise(r3) + unreachable +L2: + return 4 + +[case testUnreachableStatementAfterAssertFalse] +def f(x: bool) -> int: + if x: + assert False + print('hello') + return 2 +[out] +def f(x): + x, r0 :: bool + r1 :: str + r2 :: object + r3 :: str + r4, r5 :: object +L0: + if x goto L1 else goto L4 :: bool +L1: + if 0 goto L3 else goto L2 :: bool +L2: + r0 = raise AssertionError + unreachable +L3: + r1 = 'hello' + r2 = builtins :: module + r3 = 'print' + r4 = CPyObject_GetAttr(r2, r3) + r5 = PyObject_CallFunctionObjArgs(r4, r1, 0) +L4: + return 4 diff --git a/mypyc/test-data/lowering-int.test b/mypyc/test-data/lowering-int.test new file mode 100644 index 000000000000..e7df944c4458 --- /dev/null +++ b/mypyc/test-data/lowering-int.test @@ -0,0 +1,377 @@ +-- Test cases for converting high-level IR to lower-level IR (lowering). + +[case testLowerIntEq] +def f(x: int, y: int) -> int: + if x == y: + return 1 + else: + return 2 +[out] +def f(x, y): + x, y :: int + r0 :: native_int + r1, r2, r3 :: bit +L0: + r0 = x & 1 + r1 = r0 != 0 + if r1 goto L1 else goto L2 :: bool +L1: + r2 = CPyTagged_IsEq_(x, y) + if r2 goto L3 else goto L4 :: bool +L2: + r3 = x == y + if r3 goto L3 else goto L4 :: bool +L3: + return 2 +L4: + return 4 + +[case testLowerIntNe] +def f(x: int, y: int) -> int: + if x != y: + return 1 + else: + return 2 +[out] +def f(x, y): + x, y :: int + r0 :: native_int + r1, r2, r3, r4 :: bit +L0: + r0 = x & 1 + r1 = r0 != 0 + if r1 goto L1 else goto L2 :: bool +L1: + r2 = CPyTagged_IsEq_(x, y) + r3 = r2 ^ 1 + if r3 goto L3 else goto L4 :: bool +L2: + r4 = x != y + if r4 goto L3 else goto L4 :: bool +L3: + return 2 +L4: + return 4 + +[case testLowerIntEqWithConstant] +def f(x: int, y: int) -> int: + if x == 2: + return 1 + elif -1 == x: + return 2 + return 3 +[out] +def f(x, y): + x, y :: int + r0, r1 :: bit +L0: + r0 = x == 4 + if r0 goto L1 else goto L2 :: bool +L1: + return 2 +L2: + r1 = -2 == x + if r1 goto L3 else goto L4 :: bool +L3: + return 4 +L4: + return 6 + +[case testLowerIntNeWithConstant] +def f(x: int, y: int) -> int: + if x != 2: + return 1 + elif -1 != x: + return 2 + return 3 +[out] +def f(x, y): + x, y :: int + r0, r1 :: bit +L0: + r0 = x != 4 + if r0 goto L1 else goto L2 :: bool +L1: + return 2 +L2: + r1 = -2 != x + if r1 goto L3 else goto L4 :: bool +L3: + return 4 +L4: + return 6 + +[case testLowerIntEqValueContext] +def f(x: int, y: int) -> bool: + return x == y +[out] +def f(x, y): + x, y :: int + r0 :: native_int + r1, r2 :: bit + r3 :: bool + r4 :: bit +L0: + r0 = x & 1 + r1 = r0 != 0 + if r1 goto L1 else goto L2 :: bool +L1: + r2 = CPyTagged_IsEq_(x, y) + r3 = r2 + goto L3 +L2: + r4 = x == y + r3 = r4 +L3: + return r3 + +[case testLowerIntLt] +def f(x: int, y: int) -> int: + if x < y: + return 1 + else: + return 2 +[out] +def f(x, y): + x, y :: int + r0 :: native_int + r1 :: bit + r2 :: native_int + r3, r4, r5 :: bit +L0: + r0 = x & 1 + r1 = r0 != 0 + if r1 goto L2 else goto L1 :: bool +L1: + r2 = y & 1 + r3 = r2 != 0 + if r3 goto L2 else goto L3 :: bool +L2: + r4 = CPyTagged_IsLt_(x, y) + if r4 goto L4 else goto L5 :: bool +L3: + r5 = x < y :: signed + if r5 goto L4 else goto L5 :: bool +L4: + return 2 +L5: + return 4 + +[case testLowerIntLe] +def f(x: int, y: int) -> int: + if x <= y: + return 1 + else: + return 2 +[out] +def f(x, y): + x, y :: int + r0 :: native_int + r1 :: bit + r2 :: native_int + r3, r4, r5, r6 :: bit +L0: + r0 = x & 1 + r1 = r0 != 0 + if r1 goto L2 else goto L1 :: bool +L1: + r2 = y & 1 + r3 = r2 != 0 + if r3 goto L2 else goto L3 :: bool +L2: + r4 = CPyTagged_IsLt_(y, x) + r5 = r4 ^ 1 + if r5 goto L4 else goto L5 :: bool +L3: + r6 = x <= y :: signed + if r6 goto L4 else goto L5 :: bool +L4: + return 2 +L5: + return 4 + +[case testLowerIntGt] +def f(x: int, y: int) -> int: + if x > y: + return 1 + else: + return 2 +[out] +def f(x, y): + x, y :: int + r0 :: native_int + r1 :: bit + r2 :: native_int + r3, r4, r5 :: bit +L0: + r0 = x & 1 + r1 = r0 != 0 + if r1 goto L2 else goto L1 :: bool +L1: + r2 = y & 1 + r3 = r2 != 0 + if r3 goto L2 else goto L3 :: bool +L2: + r4 = CPyTagged_IsLt_(y, x) + if r4 goto L4 else goto L5 :: bool +L3: + r5 = x > y :: signed + if r5 goto L4 else goto L5 :: bool +L4: + return 2 +L5: + return 4 + +[case testLowerIntGe] +def f(x: int, y: int) -> int: + if x >= y: + return 1 + else: + return 2 +[out] +def f(x, y): + x, y :: int + r0 :: native_int + r1 :: bit + r2 :: native_int + r3, r4, r5, r6 :: bit +L0: + r0 = x & 1 + r1 = r0 != 0 + if r1 goto L2 else goto L1 :: bool +L1: + r2 = y & 1 + r3 = r2 != 0 + if r3 goto L2 else goto L3 :: bool +L2: + r4 = CPyTagged_IsLt_(x, y) + r5 = r4 ^ 1 + if r5 goto L4 else goto L5 :: bool +L3: + r6 = x >= y :: signed + if r6 goto L4 else goto L5 :: bool +L4: + return 2 +L5: + return 4 + +[case testLowerIntLtShort] +def both() -> int: + if 3 < 5: + return 1 + else: + return 2 + +def rhs_only(x: int) -> int: + if x < 5: + return 1 + else: + return 2 + +def lhs_only(x: int) -> int: + if 5 < x: + return 1 + else: + return 2 +[out] +def both(): + r0 :: bit +L0: + r0 = 6 < 10 :: signed + if r0 goto L1 else goto L2 :: bool +L1: + return 2 +L2: + return 4 +def rhs_only(x): + x :: int + r0 :: native_int + r1 :: bit + r2 :: native_int + r3, r4, r5 :: bit +L0: + r0 = x & 1 + r1 = r0 != 0 + if r1 goto L2 else goto L1 :: bool +L1: + r2 = 10 & 1 + r3 = r2 != 0 + if r3 goto L2 else goto L3 :: bool +L2: + r4 = CPyTagged_IsLt_(x, 10) + if r4 goto L4 else goto L5 :: bool +L3: + r5 = x < 10 :: signed + if r5 goto L4 else goto L5 :: bool +L4: + return 2 +L5: + return 4 +def lhs_only(x): + x :: int + r0 :: native_int + r1 :: bit + r2 :: native_int + r3, r4, r5 :: bit +L0: + r0 = 10 & 1 + r1 = r0 != 0 + if r1 goto L2 else goto L1 :: bool +L1: + r2 = x & 1 + r3 = r2 != 0 + if r3 goto L2 else goto L3 :: bool +L2: + r4 = CPyTagged_IsLt_(10, x) + if r4 goto L4 else goto L5 :: bool +L3: + r5 = 10 < x :: signed + if r5 goto L4 else goto L5 :: bool +L4: + return 2 +L5: + return 4 + +[case testLowerIntForLoop] +from __future__ import annotations + +def f(l: list[int]) -> None: + for x in l: + pass +[out] +def f(l): + l :: list + r0 :: short_int + r1 :: ptr + r2 :: native_int + r3 :: short_int + r4 :: bit + r5 :: object + r6, x :: int + r7 :: short_int + r8 :: None +L0: + r0 = 0 +L1: + r1 = get_element_ptr l ob_size :: PyVarObject + r2 = load_mem r1 :: native_int* + r3 = r2 << 1 + r4 = r0 < r3 :: signed + if r4 goto L2 else goto L5 :: bool +L2: + r5 = CPyList_GetItemUnsafe(l, r0) + r6 = unbox(int, r5) + dec_ref r5 + if is_error(r6) goto L6 (error at f:4) else goto L3 +L3: + x = r6 + dec_ref x :: int +L4: + r7 = r0 + 2 + r0 = r7 + goto L1 +L5: + return 1 +L6: + r8 = :: None + return r8 diff --git a/mypyc/test-data/lowering-list.test b/mypyc/test-data/lowering-list.test new file mode 100644 index 000000000000..c8438d869970 --- /dev/null +++ b/mypyc/test-data/lowering-list.test @@ -0,0 +1,33 @@ +[case testLowerListDisplay] +def f() -> None: + a = [4, 6, 7] +[out] +def f(): + r0 :: list + r1, r2, r3 :: object + r4, r5, r6, r7 :: ptr + a :: list + r8 :: None +L0: + r0 = PyList_New(3) + if is_error(r0) goto L2 (error at f:2) else goto L1 +L1: + r1 = object 4 + r2 = object 6 + r3 = object 7 + r4 = get_element_ptr r0 ob_item :: PyListObject + r5 = load_mem r4 :: ptr* + inc_ref r1 + set_mem r5, r1 :: builtins.object* + inc_ref r2 + r6 = r5 + WORD_SIZE*1 + set_mem r6, r2 :: builtins.object* + inc_ref r3 + r7 = r5 + WORD_SIZE*2 + set_mem r7, r3 :: builtins.object* + a = r0 + dec_ref a + return 1 +L2: + r8 = :: None + return r8 diff --git a/mypyc/test-data/opt-copy-propagation.test b/mypyc/test-data/opt-copy-propagation.test new file mode 100644 index 000000000000..49b80f4385fc --- /dev/null +++ b/mypyc/test-data/opt-copy-propagation.test @@ -0,0 +1,400 @@ +-- Test cases for copy propagation optimization. This also tests IR transforms in general, +-- as copy propagation was the first IR transform that was implemented. + +[case testCopyPropagationSimple] +def g() -> int: + return 1 + +def f() -> int: + y = g() + return y +[out] +def g(): +L0: + return 2 +def f(): + r0 :: int +L0: + r0 = g() + return r0 + +[case testCopyPropagationChain] +def f(x: int) -> int: + y = x + z = y + return z +[out] +def f(x): + x :: int +L0: + return x + +[case testCopyPropagationChainPartial] +def f(x: int) -> int: + y = x + z = y + x = 2 + return z +[out] +def f(x): + x, y :: int +L0: + y = x + x = 4 + return y + +[case testCopyPropagationChainBad] +def f(x: int) -> int: + y = x + z = y + y = 2 + return z +[out] +def f(x): + x, y, z :: int +L0: + y = x + z = y + y = 4 + return z + +[case testCopyPropagationMutatedSource1] +def f(x: int) -> int: + y = x + x = 1 + return y +[out] +def f(x): + x, y :: int +L0: + y = x + x = 2 + return y + +[case testCopyPropagationMutatedSource2] +def f() -> int: + z = 1 + y = z + z = 2 + return y +[out] +def f(): + z, y :: int +L0: + z = 2 + y = z + z = 4 + return y + +[case testCopyPropagationTooComplex] +def f(b: bool, x: int) -> int: + if b: + y = x + return y + else: + y = 1 + return y +[out] +def f(b, x): + b :: bool + x, y :: int +L0: + if b goto L1 else goto L2 :: bool +L1: + y = x + return y +L2: + y = 2 + return y + +[case testCopyPropagationArg] +def f(x: int) -> int: + x = 2 + return x +[out] +def f(x): + x :: int +L0: + x = 4 + return x + +[case testCopyPropagationPartiallyDefined1] +def f(b: bool) -> int: + if b: + x = 1 + y = x + return y +[out] +def f(b): + b :: bool + r0, x :: int + r1 :: bool + y :: int +L0: + r0 = :: int + x = r0 + if b goto L1 else goto L2 :: bool +L1: + x = 2 +L2: + if is_error(x) goto L3 else goto L4 +L3: + r1 = raise UnboundLocalError('local variable "x" referenced before assignment') + unreachable +L4: + y = x + return y + +-- The remaining test cases test basic IRTransform functionality and are not +-- all needed for testing copy propagation as such. + +[case testIRTransformBranch] +from mypy_extensions import i64 + +def f(x: bool) -> int: + y = x + if y: + return 1 + else: + return 2 +[out] +def f(x): + x :: bool +L0: + if x goto L1 else goto L2 :: bool +L1: + return 2 +L2: + return 4 + +[case testIRTransformAssignment] +def f(b: bool, x: int) -> int: + y = x + if b: + return y + else: + return 1 +[out] +def f(b, x): + b :: bool + x :: int +L0: + if b goto L1 else goto L2 :: bool +L1: + return x +L2: + return 2 + +[case testIRTransformRegisterOps1] +from __future__ import annotations +from typing import cast + +class C: + a: int + + def m(self, x: int) -> None: pass + +def get_attr(x: C) -> int: + y = x + return y.a + +def set_attr(x: C) -> None: + y = x + y.a = 1 + +def tuple_get(x: tuple[int, int]) -> int: + y = x + return y[0] + +def tuple_set(x: int, xx: int) -> tuple[int, int]: + y = x + z = xx + return y, z + +def call(x: int) -> int: + y = x + return call(y) + +def method_call(c: C, x: int) -> None: + y = x + c.m(y) + +def cast_op(x: object) -> str: + y = x + return cast(str, y) + +def box(x: int) -> object: + y = x + return y + +def unbox(x: object) -> int: + y = x + return cast(int, y) + +def call_c(x: list[str]) -> None: + y = x + y.append("x") + +def keep_alive(x: C) -> int: + y = x + return y.a + 1 +[out] +def C.m(self, x): + self :: __main__.C + x :: int +L0: + return 1 +def get_attr(x): + x :: __main__.C + r0 :: int +L0: + r0 = x.a + return r0 +def set_attr(x): + x :: __main__.C + r0 :: bool +L0: + x.a = 2; r0 = is_error + return 1 +def tuple_get(x): + x :: tuple[int, int] + r0 :: int +L0: + r0 = x[0] + return r0 +def tuple_set(x, xx): + x, xx :: int + r0 :: tuple[int, int] +L0: + r0 = (x, xx) + return r0 +def call(x): + x, r0 :: int +L0: + r0 = call(x) + return r0 +def method_call(c, x): + c :: __main__.C + x :: int + r0 :: None +L0: + r0 = c.m(x) + return 1 +def cast_op(x): + x :: object + r0 :: str +L0: + r0 = cast(str, x) + return r0 +def box(x): + x :: int + r0 :: object +L0: + r0 = box(int, x) + return r0 +def unbox(x): + x :: object + r0 :: int +L0: + r0 = unbox(int, x) + return r0 +def call_c(x): + x :: list + r0 :: str + r1 :: i32 + r2 :: bit +L0: + r0 = 'x' + r1 = PyList_Append(x, r0) + r2 = r1 >= 0 :: signed + return 1 +def keep_alive(x): + x :: __main__.C + r0, r1 :: int +L0: + r0 = borrow x.a + r1 = CPyTagged_Add(r0, 2) + keep_alive x + return r1 + +[case testIRTransformRegisterOps2] +from mypy_extensions import i32, i64 + +def truncate(x: i64) -> i32: + y = x + return i32(y) + +def extend(x: i32) -> i64: + y = x + return i64(y) + +def int_op(x: i64, xx: i64) -> i64: + y = x + z = xx + return y + z + +def comparison_op(x: i64, xx: i64) -> bool: + y = x + z = xx + return y == z + +def float_op(x: float, xx: float) -> float: + y = x + z = xx + return y + z + +def float_neg(x: float) -> float: + y = x + return -y + +def float_comparison_op(x: float, xx: float) -> bool: + y = x + z = xx + return y == z +[out] +def truncate(x): + x :: i64 + r0 :: i32 +L0: + r0 = truncate x: i64 to i32 + return r0 +def extend(x): + x :: i32 + r0 :: i64 +L0: + r0 = extend signed x: i32 to i64 + return r0 +def int_op(x, xx): + x, xx, r0 :: i64 +L0: + r0 = x + xx + return r0 +def comparison_op(x, xx): + x, xx :: i64 + r0 :: bit +L0: + r0 = x == xx + return r0 +def float_op(x, xx): + x, xx, r0 :: float +L0: + r0 = x + xx + return r0 +def float_neg(x): + x, r0 :: float +L0: + r0 = -x + return r0 +def float_comparison_op(x, xx): + x, xx :: float + r0 :: bit +L0: + r0 = x == xx + return r0 + +-- Note that transforms of these ops aren't tested here: +-- * LoadMem +-- * SetMem +-- * GetElementPtr +-- * LoadAddress +-- * Unborrow diff --git a/mypyc/test-data/opt-flag-elimination.test b/mypyc/test-data/opt-flag-elimination.test new file mode 100644 index 000000000000..337ced70a355 --- /dev/null +++ b/mypyc/test-data/opt-flag-elimination.test @@ -0,0 +1,296 @@ +-- Test cases for "flag elimination" optimization. Used to optimize away +-- registers that are always used immediately after assignment as branch conditions. + +[case testFlagEliminationSimple] +def c() -> bool: + return True +def d() -> bool: + return True + +def f(x: bool) -> int: + if x: + b = c() + else: + b = d() + if b: + return 1 + else: + return 2 +[out] +def c(): +L0: + return 1 +def d(): +L0: + return 1 +def f(x): + x, r0, r1 :: bool +L0: + if x goto L1 else goto L2 :: bool +L1: + r0 = c() + if r0 goto L3 else goto L4 :: bool +L2: + r1 = d() + if r1 goto L3 else goto L4 :: bool +L3: + return 2 +L4: + return 4 + +[case testFlagEliminationOneAssignment] +def c() -> bool: + return True + +def f(x: bool) -> int: + # Not applied here + b = c() + if b: + return 1 + else: + return 2 +[out] +def c(): +L0: + return 1 +def f(x): + x, r0, b :: bool +L0: + r0 = c() + b = r0 + if b goto L1 else goto L2 :: bool +L1: + return 2 +L2: + return 4 + +[case testFlagEliminationThreeCases] +def c(x: int) -> bool: + return True + +def f(x: bool, y: bool) -> int: + if x: + b = c(1) + elif y: + b = c(2) + else: + b = c(3) + if b: + return 1 + else: + return 2 +[out] +def c(x): + x :: int +L0: + return 1 +def f(x, y): + x, y, r0, r1, r2 :: bool +L0: + if x goto L1 else goto L2 :: bool +L1: + r0 = c(2) + if r0 goto L5 else goto L6 :: bool +L2: + if y goto L3 else goto L4 :: bool +L3: + r1 = c(4) + if r1 goto L5 else goto L6 :: bool +L4: + r2 = c(6) + if r2 goto L5 else goto L6 :: bool +L5: + return 2 +L6: + return 4 + +[case testFlagEliminationAssignmentNotLastOp] +def f(x: bool) -> int: + y = 0 + if x: + b = True + y = 1 + else: + b = False + if b: + return 1 + else: + return 2 +[out] +def f(x): + x :: bool + y :: int + b :: bool +L0: + y = 0 + if x goto L1 else goto L2 :: bool +L1: + b = 1 + y = 2 + goto L3 +L2: + b = 0 +L3: + if b goto L4 else goto L5 :: bool +L4: + return 2 +L5: + return 4 + +[case testFlagEliminationAssignmentNoDirectGoto] +def f(x: bool) -> int: + if x: + b = True + else: + b = False + if x: + if b: + return 1 + else: + return 2 + return 4 +[out] +def f(x): + x, b :: bool +L0: + if x goto L1 else goto L2 :: bool +L1: + b = 1 + goto L3 +L2: + b = 0 +L3: + if x goto L4 else goto L7 :: bool +L4: + if b goto L5 else goto L6 :: bool +L5: + return 2 +L6: + return 4 +L7: + return 8 + +[case testFlagEliminationBranchNotNextOpAfterGoto] +def f(x: bool) -> int: + if x: + b = True + else: + b = False + y = 1 # Prevents the optimization + if b: + return 1 + else: + return 2 +[out] +def f(x): + x, b :: bool + y :: int +L0: + if x goto L1 else goto L2 :: bool +L1: + b = 1 + goto L3 +L2: + b = 0 +L3: + y = 2 + if b goto L4 else goto L5 :: bool +L4: + return 2 +L5: + return 4 + +[case testFlagEliminationFlagReadTwice] +def f(x: bool) -> bool: + if x: + b = True + else: + b = False + if b: + return b # Prevents the optimization + else: + return False +[out] +def f(x): + x, b :: bool +L0: + if x goto L1 else goto L2 :: bool +L1: + b = 1 + goto L3 +L2: + b = 0 +L3: + if b goto L4 else goto L5 :: bool +L4: + return b +L5: + return 0 + +[case testFlagEliminationArgumentNotEligible] +def f(x: bool, b: bool) -> bool: + if x: + b = True + else: + b = False + if b: + return True + else: + return False +[out] +def f(x, b): + x, b :: bool +L0: + if x goto L1 else goto L2 :: bool +L1: + b = 1 + goto L3 +L2: + b = 0 +L3: + if b goto L4 else goto L5 :: bool +L4: + return 1 +L5: + return 0 + +[case testFlagEliminationFlagNotAlwaysDefined] +def f(x: bool, y: bool) -> bool: + if x: + b = True + elif y: + b = False + else: + bb = False # b not assigned here -> can't optimize + if b: + return True + else: + return False +[out] +def f(x, y): + x, y, r0, b, bb, r1 :: bool +L0: + r0 = :: bool + b = r0 + if x goto L1 else goto L2 :: bool +L1: + b = 1 + goto L5 +L2: + if y goto L3 else goto L4 :: bool +L3: + b = 0 + goto L5 +L4: + bb = 0 +L5: + if is_error(b) goto L6 else goto L7 +L6: + r1 = raise UnboundLocalError('local variable "b" referenced before assignment') + unreachable +L7: + if b goto L8 else goto L9 :: bool +L8: + return 1 +L9: + return 0 diff --git a/mypyc/test-data/refcount.test b/mypyc/test-data/refcount.test index 372956a00cab..e719ecb2afe1 100644 --- a/mypyc/test-data/refcount.test +++ b/mypyc/test-data/refcount.test @@ -67,7 +67,7 @@ def f(): L0: x = 2 y = 4 - r0 = x == 2 + r0 = int_eq x, 2 if r0 goto L3 else goto L4 :: bool L1: return x @@ -185,34 +185,26 @@ def f(a: int) -> int: [out] def f(a): a :: int - r0 :: native_int - r1, r2, r3 :: bit - x, r4, y :: int + r0 :: bit + x, r1, y :: int L0: - r0 = a & 1 - r1 = r0 != 0 - if r1 goto L1 else goto L2 :: bool + r0 = int_eq a, a + if r0 goto L1 else goto L2 :: bool L1: - r2 = CPyTagged_IsEq_(a, a) - if r2 goto L3 else goto L4 :: bool -L2: - r3 = a == a - if r3 goto L3 else goto L4 :: bool -L3: a = 2 - goto L5 -L4: + goto L3 +L2: x = 4 dec_ref x :: int - goto L6 -L5: - r4 = CPyTagged_Add(a, 2) + goto L4 +L3: + r1 = CPyTagged_Add(a, 2) dec_ref a :: int - y = r4 + y = r1 return y -L6: +L4: inc_ref a :: int - goto L5 + goto L3 [case testConditionalAssignToArgument2] def f(a: int) -> int: @@ -225,33 +217,25 @@ def f(a: int) -> int: [out] def f(a): a :: int - r0 :: native_int - r1, r2, r3 :: bit - x, r4, y :: int + r0 :: bit + x, r1, y :: int L0: - r0 = a & 1 - r1 = r0 != 0 - if r1 goto L1 else goto L2 :: bool + r0 = int_eq a, a + if r0 goto L1 else goto L2 :: bool L1: - r2 = CPyTagged_IsEq_(a, a) - if r2 goto L3 else goto L4 :: bool -L2: - r3 = a == a - if r3 goto L3 else goto L4 :: bool -L3: x = 4 dec_ref x :: int - goto L6 -L4: + goto L4 +L2: a = 2 -L5: - r4 = CPyTagged_Add(a, 2) +L3: + r1 = CPyTagged_Add(a, 2) dec_ref a :: int - y = r4 + y = r1 return y -L6: +L4: inc_ref a :: int - goto L5 + goto L3 [case testConditionalAssignToArgument3] def f(a: int) -> int: @@ -261,25 +245,17 @@ def f(a: int) -> int: [out] def f(a): a :: int - r0 :: native_int - r1, r2, r3 :: bit + r0 :: bit L0: - r0 = a & 1 - r1 = r0 != 0 - if r1 goto L1 else goto L2 :: bool + r0 = int_eq a, a + if r0 goto L1 else goto L3 :: bool L1: - r2 = CPyTagged_IsEq_(a, a) - if r2 goto L3 else goto L5 :: bool -L2: - r3 = a == a - if r3 goto L3 else goto L5 :: bool -L3: a = 2 -L4: +L2: return a -L5: +L3: inc_ref a :: int - goto L4 + goto L2 [case testAssignRegisterToItself] def f(a: int) -> int: @@ -438,40 +414,32 @@ def f() -> int: [out] def f(): x, y, z :: int - r0 :: native_int - r1, r2, r3 :: bit - a, r4, r5 :: int + r0 :: bit + a, r1, r2 :: int L0: x = 2 y = 4 z = 6 - r0 = z & 1 - r1 = r0 != 0 - if r1 goto L1 else goto L2 :: bool + r0 = int_eq z, z + if r0 goto L3 else goto L4 :: bool L1: - r2 = CPyTagged_IsEq_(z, z) - if r2 goto L5 else goto L6 :: bool -L2: - r3 = z == z - if r3 goto L5 else goto L6 :: bool -L3: return z -L4: +L2: a = 2 - r4 = CPyTagged_Add(x, y) + r1 = CPyTagged_Add(x, y) dec_ref x :: int dec_ref y :: int - r5 = CPyTagged_Subtract(r4, a) - dec_ref r4 :: int + r2 = CPyTagged_Subtract(r1, a) + dec_ref r1 :: int dec_ref a :: int - return r5 -L5: + return r2 +L3: dec_ref x :: int dec_ref y :: int - goto L3 -L6: + goto L1 +L4: dec_ref z :: int - goto L4 + goto L2 [case testLoop] def f(a: int) -> int: @@ -484,41 +452,27 @@ def f(a: int) -> int: [out] def f(a): a, sum, i :: int - r0 :: native_int - r1 :: bit - r2 :: native_int - r3, r4, r5 :: bit - r6, r7 :: int + r0 :: bit + r1, r2 :: int L0: sum = 0 i = 0 L1: - r0 = i & 1 - r1 = r0 != 0 - if r1 goto L3 else goto L2 :: bool + r0 = int_le i, a + if r0 goto L2 else goto L4 :: bool L2: - r2 = a & 1 - r3 = r2 != 0 - if r3 goto L3 else goto L4 :: bool -L3: - r4 = CPyTagged_IsLt_(a, i) - if r4 goto L7 else goto L5 :: bool -L4: - r5 = i <= a :: signed - if r5 goto L5 else goto L7 :: bool -L5: - r6 = CPyTagged_Add(sum, i) + r1 = CPyTagged_Add(sum, i) dec_ref sum :: int - sum = r6 - r7 = CPyTagged_Add(i, 2) + sum = r1 + r2 = CPyTagged_Add(i, 2) dec_ref i :: int - i = r7 + i = r2 goto L1 -L6: +L3: return sum -L7: +L4: dec_ref i :: int - goto L6 + goto L3 [case testCall] def f(a: int) -> int: @@ -544,19 +498,17 @@ def f() -> int: def f(): r0 :: list r1, r2 :: object - r3, r4, r5 :: ptr + r3 :: ptr a :: list L0: r0 = PyList_New(2) r1 = object 0 r2 = object 1 - r3 = get_element_ptr r0 ob_item :: PyListObject - r4 = load_mem r3 :: ptr* + r3 = list_items r0 inc_ref r1 - set_mem r4, r1 :: builtins.object* - r5 = r4 + WORD_SIZE*1 + buf_init_item r3, 0, r1 inc_ref r2 - set_mem r5, r2 :: builtins.object* + buf_init_item r3, 1, r2 a = r0 dec_ref a return 0 @@ -623,21 +575,20 @@ def f() -> None: def f(): r0 :: __main__.C r1 :: list - r2, r3 :: ptr + r2 :: ptr a :: list - r4 :: object - r5, d :: __main__.C + r3 :: object + r4, d :: __main__.C L0: r0 = C() r1 = PyList_New(1) - r2 = get_element_ptr r1 ob_item :: PyListObject - r3 = load_mem r2 :: ptr* - set_mem r3, r0 :: builtins.object* + r2 = list_items r1 + buf_init_item r2, 0, r0 a = r1 - r4 = CPyList_GetItemShort(a, 0) + r3 = CPyList_GetItemShort(a, 0) dec_ref a - r5 = cast(__main__.C, r4) - d = r5 + r4 = cast(__main__.C, r3) + d = r4 dec_ref d return 1 @@ -656,6 +607,66 @@ L1: L2: return 4 +[case testReturnTuple] +from typing import Tuple + +class C: pass +def f() -> Tuple[C, C]: + a = C() + b = C() + return a, b +[out] +def f(): + r0, a, r1, b :: __main__.C + r2 :: tuple[__main__.C, __main__.C] +L0: + r0 = C() + a = r0 + r1 = C() + b = r1 + r2 = (a, b) + return r2 + +[case testDecomposeTuple] +from typing import Tuple + +class C: + a: int + +def f() -> int: + x, y = g() + return x.a + y.a + +def g() -> Tuple[C, C]: + return C(), C() +[out] +def f(): + r0 :: tuple[__main__.C, __main__.C] + r1, r2, x, r3, r4, y :: __main__.C + r5, r6, r7 :: int +L0: + r0 = g() + r1 = borrow r0[0] + r2 = unborrow r1 + x = r2 + r3 = borrow r0[1] + r4 = unborrow r3 + y = r4 + r5 = borrow x.a + r6 = borrow y.a + r7 = CPyTagged_Add(r5, r6) + dec_ref x + dec_ref y + return r7 +def g(): + r0, r1 :: __main__.C + r2 :: tuple[__main__.C, __main__.C] +L0: + r0 = C() + r1 = C() + r2 = (r0, r1) + return r2 + [case testUnicodeLiteral] def f() -> str: return "some string" @@ -702,7 +713,7 @@ def f(a, x): a :: list x :: int r0 :: object - r1 :: int32 + r1 :: i32 r2 :: bit L0: inc_ref x :: int @@ -802,17 +813,15 @@ def f() -> int: [out] def f(): r0, x :: list - r1 :: ptr - r2 :: native_int - r3 :: short_int + r1 :: native_int + r2 :: short_int L0: r0 = PyList_New(0) x = r0 - r1 = get_element_ptr x ob_size :: PyVarObject - r2 = load_mem r1 :: native_int* + r1 = var_object_size x dec_ref x - r3 = r2 << 1 - return r3 + r2 = r1 << 1 + return r2 [case testSometimesUninitializedVariable] def f(x: bool) -> int: @@ -1053,15 +1062,13 @@ class C: def f(x): x :: __main__.C r0 :: list - r1 :: ptr - r2 :: native_int - r3 :: short_int + r1 :: native_int + r2 :: short_int L0: r0 = borrow x.a - r1 = get_element_ptr r0 ob_size :: PyVarObject - r2 = load_mem r1 :: native_int* - r3 = r2 << 1 - return r3 + r1 = var_object_size r0 + r2 = r1 << 1 + return r2 [case testBorrowIsinstanceArgument] from typing import List @@ -1242,23 +1249,22 @@ class C: def f(): r0 :: __main__.C r1 :: list - r2, r3 :: ptr + r2 :: ptr a :: list - r4 :: object - r5 :: __main__.C - r6 :: str + r3 :: object + r4 :: __main__.C + r5 :: str L0: r0 = C() r1 = PyList_New(1) - r2 = get_element_ptr r1 ob_item :: PyListObject - r3 = load_mem r2 :: ptr* - set_mem r3, r0 :: builtins.object* + r2 = list_items r1 + buf_init_item r2, 0, r0 a = r1 - r4 = CPyList_GetItemShortBorrow(a, 0) - r5 = borrow cast(__main__.C, r4) - r6 = r5.s + r3 = CPyList_GetItemShortBorrow(a, 0) + r4 = borrow cast(__main__.C, r3) + r5 = r4.s dec_ref a - return r6 + return r5 [case testBorrowSetAttrObject] from typing import Optional @@ -1311,25 +1317,12 @@ class C: def add(c): c :: __main__.C r0, r1 :: int - r2 :: native_int - r3, r4 :: bit - r5 :: bool - r6 :: bit + r2 :: bit L0: r0 = borrow c.x r1 = borrow c.y - r2 = r0 & 1 - r3 = r2 == 0 - if r3 goto L1 else goto L2 :: bool -L1: - r4 = r0 == r1 - r5 = r4 - goto L3 -L2: - r6 = CPyTagged_IsEq_(r0, r1) - r5 = r6 -L3: - return r5 + r2 = int_eq r0, r1 + return r2 [case testBorrowIntLessThan] def add(c: C) -> bool: @@ -1342,30 +1335,12 @@ class C: def add(c): c :: __main__.C r0, r1 :: int - r2 :: native_int - r3 :: bit - r4 :: native_int - r5, r6, r7 :: bit - r8 :: bool - r9 :: bit + r2 :: bit L0: r0 = borrow c.x r1 = borrow c.y - r2 = r0 & 1 - r3 = r2 == 0 - r4 = r1 & 1 - r5 = r4 == 0 - r6 = r3 & r5 - if r6 goto L1 else goto L2 :: bool -L1: - r7 = r0 < r1 :: signed - r8 = r7 - goto L3 -L2: - r9 = CPyTagged_IsLt_(r0, r1) - r8 = r9 -L3: - return r8 + r2 = int_lt r0, r1 + return r2 [case testBorrowIntCompareFinal] from typing_extensions import Final @@ -1381,24 +1356,11 @@ class C: def add(c): c :: __main__.C r0 :: int - r1 :: native_int - r2, r3 :: bit - r4 :: bool - r5 :: bit + r1 :: bit L0: r0 = borrow c.x - r1 = r0 & 1 - r2 = r1 == 0 - if r2 goto L1 else goto L2 :: bool -L1: - r3 = r0 == 20 - r4 = r3 - goto L3 -L2: - r5 = CPyTagged_IsEq_(r0, 20) - r4 = r5 -L3: - return r4 + r1 = int_eq r0, 20 + return r1 [case testBorrowIntArithmetic] def add(c: C) -> int: @@ -1441,23 +1403,15 @@ class C: def add(c, n): c :: __main__.C n, r0, r1 :: int - r2 :: native_int - r3, r4, r5 :: bit + r2 :: bit L0: r0 = borrow c.x r1 = borrow c.y - r2 = r0 & 1 - r3 = r2 != 0 - if r3 goto L1 else goto L2 :: bool + r2 = int_eq r0, r1 + if r2 goto L1 else goto L2 :: bool L1: - r4 = CPyTagged_IsEq_(r0, r1) - if r4 goto L3 else goto L4 :: bool -L2: - r5 = r0 == r1 - if r5 goto L3 else goto L4 :: bool -L3: return 1 -L4: +L2: return 0 [case testBorrowIntInPlaceOp] @@ -1504,10 +1458,10 @@ def f(x): x, r0 :: int r1 :: native_int r2 :: bit - r3, r4 :: int64 + r3, r4 :: i64 r5 :: ptr r6 :: c_ptr - r7 :: int64 + r7 :: i64 L0: r0 = CPyTagged_Add(x, 2) r1 = r0 & 1 diff --git a/mypyc/test-data/run-async.test b/mypyc/test-data/run-async.test index 85ad172d61df..8488632e6574 100644 --- a/mypyc/test-data/run-async.test +++ b/mypyc/test-data/run-async.test @@ -143,3 +143,31 @@ async def foo() -> AsyncIterable[int]: yields, val = run_generator(async_iter(foo())) assert yields == (0,1,2), yields assert val == 'lol no', val + +[case testAsyncWithVarReuse] +class ConMan: + async def __aenter__(self) -> int: + return 1 + async def __aexit__(self, *exc: object): + pass + +class ConManB: + async def __aenter__(self) -> int: + return 2 + async def __aexit__(self, *exc: object): + pass + +async def x() -> None: + value = 2 + async with ConMan() as f: + value += f + assert value == 3, value + async with ConManB() as f: + value += f + assert value == 5, value + +[typing fixtures/typing-full.pyi] +[file driver.py] +import asyncio +import native +asyncio.run(native.x()) diff --git a/mypyc/test-data/run-bools.test b/mypyc/test-data/run-bools.test index 522296592c54..d7a2aa37ade7 100644 --- a/mypyc/test-data/run-bools.test +++ b/mypyc/test-data/run-bools.test @@ -221,3 +221,9 @@ def test_mixed_comparisons_i64() -> None: assert neq_mixed_i64(n, x) == (n != int(x)) assert lt_mixed_i64(x, n) == (int(x) < n) assert gt_mixed_i64(n, x) == (n > int(x)) + +[case testBoolMixInt] +y = False +print((y or 0) and True) +[out] +0 diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index 92ec3873bf38..59617714f7e7 100644 --- a/mypyc/test-data/run-classes.test +++ b/mypyc/test-data/run-classes.test @@ -662,42 +662,107 @@ Traceback (most recent call last): AttributeError: attribute 'x' of 'X' undefined [case testClassMethods] -MYPY = False -if MYPY: - from typing import ClassVar +from typing import ClassVar, Any +from typing_extensions import final +from mypy_extensions import mypyc_attr + +from interp import make_interpreted_subclass + class C: - lurr: 'ClassVar[int]' = 9 + lurr: ClassVar[int] = 9 @staticmethod - def foo(x: int) -> int: return 10 + x + def foo(x: int) -> int: + return 10 + x @classmethod - def bar(cls, x: int) -> int: return cls.lurr + x + def bar(cls, x: int) -> int: + return cls.lurr + x @staticmethod - def baz(x: int, y: int = 10) -> int: return y - x + def baz(x: int, y: int = 10) -> int: + return y - x + @classmethod + def quux(cls, x: int, y: int = 10) -> int: + return y - x @classmethod - def quux(cls, x: int, y: int = 10) -> int: return y - x + def call_other(cls, x: int) -> int: + return cls.quux(x, 3) class D(C): def f(self) -> int: return super().foo(1) + super().bar(2) + super().baz(10) + super().quux(10) -def test1() -> int: +def ctest1() -> int: return C.foo(1) + C.bar(2) + C.baz(10) + C.quux(10) + C.quux(y=10, x=9) -def test2() -> int: + +def ctest2() -> int: c = C() return c.foo(1) + c.bar(2) + c.baz(10) -[file driver.py] -from native import * -assert C.foo(10) == 20 -assert C.bar(10) == 19 -c = C() -assert c.foo(10) == 20 -assert c.bar(10) == 19 -assert test1() == 23 -assert test2() == 22 +CAny: Any = C + +def test_classmethod_using_any() -> None: + assert CAny.foo(10) == 20 + assert CAny.bar(10) == 19 + +def test_classmethod_on_instance() -> None: + c = C() + assert c.foo(10) == 20 + assert c.bar(10) == 19 + assert c.call_other(1) == 2 + +def test_classmethod_misc() -> None: + assert ctest1() == 23 + assert ctest2() == 22 + assert C.call_other(2) == 1 -d = D() -assert d.f() == 22 +def test_classmethod_using_super() -> None: + d = D() + assert d.f() == 22 + +@final +class F1: + @classmethod + def f(cls, x: int) -> int: + return cls.g(x) + + @classmethod + def g(cls, x: int) -> int: + return x + 1 + +class F2: # Implicitly final (no subclasses) + @classmethod + def f(cls, x: int) -> int: + return cls.g(x) + + @classmethod + def g(cls, x: int) -> int: + return x + 1 + +def test_classmethod_of_final_class() -> None: + assert F1.f(5) == 6 + assert F2.f(7) == 8 + +@mypyc_attr(allow_interpreted_subclasses=True) +class CI: + @classmethod + def f(cls, x: int) -> int: + return cls.g(x) + + @classmethod + def g(cls, x: int) -> int: + return x + 1 + +def test_classmethod_with_allow_interpreted() -> None: + assert CI.f(4) == 5 + sub = make_interpreted_subclass(CI) + assert sub.f(4) == 7 + +[file interp.py] +def make_interpreted_subclass(base): + class Sub(base): + @classmethod + def g(cls, x: int) -> int: + return x + 3 + return Sub [case testSuper] from mypy_extensions import trait @@ -1305,9 +1370,8 @@ except TypeError as e: [case testMetaclass] from meta import Meta -import six -class Nothing1(metaclass=Meta): +class Nothing(metaclass=Meta): pass def ident(x): return x @@ -1316,15 +1380,7 @@ def ident(x): return x class Test: pass -class Nothing2(six.with_metaclass(Meta, Test)): - pass - -@six.add_metaclass(Meta) -class Nothing3: - pass - [file meta.py] -from typing import Any class Meta(type): def __new__(mcs, name, bases, dct): dct['X'] = 10 @@ -1332,10 +1388,8 @@ class Meta(type): [file driver.py] -from native import Nothing1, Nothing2, Nothing3 -assert Nothing1.X == 10 -assert Nothing2.X == 10 -assert Nothing3.X == 10 +from native import Nothing +assert Nothing.X == 10 [case testPickling] from mypy_extensions import trait, mypyc_attr diff --git a/mypyc/test-data/run-dicts.test b/mypyc/test-data/run-dicts.test index 41675e7fcc91..58b862e3f303 100644 --- a/mypyc/test-data/run-dicts.test +++ b/mypyc/test-data/run-dicts.test @@ -95,7 +95,13 @@ assert get_content_set(od) == ({1, 3}, {2, 4}, {(1, 2), (3, 4)}) [typing fixtures/typing-full.pyi] [case testDictIterationMethodsRun] -from typing import Dict +from typing import Dict, Union +from typing_extensions import TypedDict + +class ExtensionDict(TypedDict): + python: str + c: str + def print_dict_methods(d1: Dict[int, int], d2: Dict[int, int], d3: Dict[int, int]) -> None: @@ -107,13 +113,27 @@ def print_dict_methods(d1: Dict[int, int], for v in d3.values(): print(v) +def print_dict_methods_special(d1: Union[Dict[int, int], Dict[str, str]], + d2: ExtensionDict) -> None: + for k in d1.keys(): + print(k) + for k, v in d1.items(): + print(k) + print(v) + for v2 in d2.values(): + print(v2) + for k2, v2 in d2.items(): + print(k2) + print(v2) + + def clear_during_iter(d: Dict[int, int]) -> None: for k in d: d.clear() class Custom(Dict[int, int]): pass [file driver.py] -from native import print_dict_methods, Custom, clear_during_iter +from native import print_dict_methods, print_dict_methods_special, Custom, clear_during_iter from collections import OrderedDict print_dict_methods({}, {}, {}) print_dict_methods({1: 2}, {3: 4, 5: 6}, {7: 8}) @@ -124,6 +144,7 @@ print('==') d = OrderedDict([(1, 2), (3, 4)]) print_dict_methods(d, d, d) print('==') +print_dict_methods_special({1: 2}, {"python": ".py", "c": ".c"}) d.move_to_end(1) print_dict_methods(d, d, d) clear_during_iter({}) # OK @@ -185,6 +206,15 @@ else: 2 4 == +1 +1 +2 +.py +.c +python +.py +c +.c 3 1 3 diff --git a/mypyc/test-data/run-floats.test b/mypyc/test-data/run-floats.test index 1b67a1190cd8..2c101100549d 100644 --- a/mypyc/test-data/run-floats.test +++ b/mypyc/test-data/run-floats.test @@ -1,30 +1,516 @@ # Test cases for floats (compile and run) -[case testStrToFloat] +[case testFloatOps] +from __future__ import annotations +from typing import Any, cast +from typing_extensions import Final +from testutil import assertRaises, float_vals, FLOAT_MAGIC +import math + +def test_arithmetic() -> None: + zero = float(0.0) + one = zero + 1.0 + x = one + one / 2.0 + assert x == 1.5 + assert x - one == 0.5 + assert x * x == 2.25 + assert x / 2.0 == 0.75 + assert x * (-0.5) == -0.75 + assert -x == -1.5 + for x in float_vals: + assert repr(-x) == repr(getattr(x, "__neg__")()) + + for y in float_vals: + assert repr(x + y) == repr(getattr(x, "__add__")(y)) + assert repr(x - y) == repr(getattr(x, "__sub__")(y)) + assert repr(x * y) == repr(getattr(x, "__mul__")(y)) + if y != 0: + assert repr(x / y) == repr(getattr(x, "__truediv__")(y)) + +def test_mod() -> None: + zero = float(0.0) + one = zero + 1.0 + x = one + one / 2.0 + assert x % 0.4 == 0.29999999999999993 + assert (-x) % 0.4 == 0.10000000000000009 + assert x % -0.4 == -0.10000000000000009 + assert (-x) % -0.4 == -0.29999999999999993 + for x in float_vals: + for y in float_vals: + if y != 0: + assert repr(x % y) == repr(getattr(x, "__mod__")(y)) + +def test_floor_div() -> None: + for x in float_vals: + for y in float_vals: + if y != 0: + assert repr(x // y) == repr(getattr(x, "__floordiv__")(y)) + else: + with assertRaises(ZeroDivisionError, "float floor division by zero"): + x // y + +def test_mixed_arithmetic() -> None: + zf = float(0.0) + zn = int() + assert (zf + 5.5) + (zn + 1) == 6.5 + assert (zn - 2) - (zf - 5.5) == 3.5 + x = zf + 3.4 + x += zn + 2 + assert x == 5.4 + +def test_arithmetic_errors() -> None: + zero = float(0.0) + one = zero + 1.0 + with assertRaises(ZeroDivisionError, "float division by zero"): + print(one / zero) + with assertRaises(ZeroDivisionError, "float modulo"): + print(one % zero) + +def test_comparisons() -> None: + zero = float(0.0) + one = zero + 1.0 + x = one + one / 2.0 + assert x < (1.51 + zero) + assert not (x < (1.49 + zero)) + assert x > (1.49 + zero) + assert not (x > (1.51 + zero)) + assert x <= (1.5 + zero) + assert not (x <= (1.49 + zero)) + assert x >= (1.5 + zero) + assert not (x >= (1.51 + zero)) + for x in float_vals: + for y in float_vals: + assert (x <= y) == getattr(x, "__le__")(y) + assert (x < y) == getattr(x, "__lt__")(y) + assert (x >= y) == getattr(x, "__ge__")(y) + assert (x > y) == getattr(x, "__gt__")(y) + assert (x == y) == getattr(x, "__eq__")(y) + assert (x != y) == getattr(x, "__ne__")(y) + +def test_mixed_comparisons() -> None: + zf = float(0.0) + zn = int() + if (zf + 1.0) == (zn + 1): + assert True + else: + assert False + if (zf + 1.1) == (zn + 1): + assert False + else: + assert True + assert (zf + 1.1) != (zn + 1) + assert (zf + 1.1) > (zn + 1) + assert not (zf + 0.9) > (zn + 1) + assert (zn + 1) < (zf + 1.1) + +def test_boxing_and_unboxing() -> None: + x = 1.5 + boxed: Any = x + assert repr(boxed) == "1.5" + assert type(boxed) is float + y: float = boxed + assert y == x + boxed_int: Any = 5 + assert [type(boxed_int)] == [int] # Avoid mypy type narrowing + z: float = boxed_int + assert z == 5.0 + for xx in float_vals: + bb: Any = xx + yy: float = bb + assert repr(xx) == repr(bb) + assert repr(xx) == repr(yy) + for b in True, False: + boxed_bool: Any = b + assert type(boxed_bool) is bool + zz: float = boxed_bool + assert zz == int(b) + +def test_unboxing_failure() -> None: + boxed: Any = '1.5' + with assertRaises(TypeError): + x: float = boxed + +def identity(x: float) -> float: + return x + +def test_coerce_from_int_literal() -> None: + assert identity(34) == 34.0 + assert identity(-1) == -1.0 + +def test_coerce_from_short_tagged_int() -> None: + n = int() - 17 + assert identity(n) == -17.0 + for i in range(-300, 300): + assert identity(i) == float(i) + +def test_coerce_from_long_tagged_int() -> None: + n = int() + 2**100 + x = identity(n) + assert repr(x) == '1.2676506002282294e+30' + n = int() - 2**100 + y = identity(n) + assert repr(y) == '-1.2676506002282294e+30' + +def test_coerce_from_very_long_tagged_int() -> None: + n = int() + 10**1000 + with assertRaises(OverflowError, "int too large to convert to float"): + identity(n) + with assertRaises(OverflowError, "int too large to convert to float"): + identity(int(n)) + n = int() - 10**1000 + with assertRaises(OverflowError, "int too large to convert to float"): + identity(n) + with assertRaises(OverflowError, "int too large to convert to float"): + identity(int(n)) + +def test_explicit_conversion_from_int() -> None: + float_any: Any = float + a = [0, 1, 2, 3, -1, -2, 13257, -928745] + for n in range(1, 100): + for delta in -1, 0, 1, 2342345: + a.append(2**n + delta) + a.append(-2**n + delta) + for x in a: + assert repr(float(x)) == repr(float_any(x)) + +def test_explicit_conversion_to_int() -> None: + int_any: Any = int + for x in float_vals: + if math.isinf(x): + with assertRaises(OverflowError, "cannot convert float infinity to integer"): + int(x) + elif math.isnan(x): + with assertRaises(ValueError, "cannot convert float NaN to integer"): + int(x) + else: + assert repr(int(x)) == repr(int_any(x)) + + # Test some edge cases + assert 2**30 == int(2.0**30 + int()) + assert 2**30 - 1 == int(1073741823.9999999 + int()) # math.nextafter(2.0**30, 0)) + assert -2**30 - 1 == int(-2.0**30 - 1 + int()) + assert -2**30 == int(-1073741824.9999998 + int()) # math.nextafter(-2.0**30 - 1, 0) + assert 2**62 == int(2.0**62 + int()) + assert 2**62 == int(2.0**62 - 1 + int()) + assert -2**62 == int(-2.0**62 + int()) + assert -2**62 == int(-2.0**62 - 1 + int()) + def str_to_float(x: str) -> float: return float(x) -[file driver.py] -from native import str_to_float +def test_str_to_float() -> None: + assert str_to_float("1") == 1.0 + assert str_to_float("1.234567") == 1.234567 + assert str_to_float("44324") == 44324.0 + assert str_to_float("23.4") == 23.4 + assert str_to_float("-43.44e-4") == -43.44e-4 + assert str_to_float("-43.44e-4") == -43.44e-4 + assert math.isinf(str_to_float("inf")) + assert math.isinf(str_to_float("-inf")) + assert str_to_float("inf") > 0.0 + assert str_to_float("-inf") < 0.0 + assert math.isnan(str_to_float("nan")) + assert math.isnan(str_to_float("NaN")) + assert repr(str_to_float("-0.0")) == "-0.0" -assert str_to_float("1") == 1.0 -assert str_to_float("1.234567") == 1.234567 -assert str_to_float("44324") == 44324.0 -assert str_to_float("23.4") == 23.4 -assert str_to_float("-43.44e-4") == -43.44e-4 - -[case testFloatArithmetic] def test_abs() -> None: assert abs(0.0) == 0.0 assert abs(-1.234567) == 1.234567 assert abs(44324.732) == 44324.732 assert abs(-23.4) == 23.4 assert abs(-43.44e-4) == 43.44e-4 + abs_any: Any = abs + for x in float_vals: + assert repr(abs(x)) == repr(abs_any(x)) def test_float_min_max() -> None: - x: float = 20.0 - y: float = 30.0 - assert min(x, y) == 20.0 - assert min(y, x) == 20.0 - assert max(x, y) == 30.0 - assert max(y, x) == 30.0 + for x in float_vals: + for y in float_vals: + min_any: Any = min + assert repr(min(x, y)) == repr(min_any(x, y)) + max_any: Any = max + assert repr(max(x, y)) == repr(max_any(x, y)) + +def default(x: float = 2) -> float: + return x + 1 + +def test_float_default_value() -> None: + assert default(1.2) == 2.2 + for i in range(-200, 200): + assert default(float(i)) == i + 1 + assert default() == 3.0 + +def test_float_default_value_wrapper() -> None: + f: Any = default + assert f(1.2) == 2.2 + for i in range(-200, 200): + assert f(float(i)) == i + 1 + assert f() == 3.0 + +class C: + def __init__(self, x: float) -> None: + self.x = x + +def test_float_attr() -> None: + for i in range(-200, 200): + f = float(i) + c = C(f) + assert c.x == f + a: Any = c + assert a.x == f + c.x = FLOAT_MAGIC + assert c.x == FLOAT_MAGIC + assert a.x == FLOAT_MAGIC + a.x = 1.0 + assert a.x == 1.0 + a.x = FLOAT_MAGIC + assert a.x == FLOAT_MAGIC + +class D: + def __init__(self, x: float) -> None: + if x: + self.x = x + +def test_float_attr_maybe_undefned() -> None: + for i in range(-200, 200): + if i == 0: + d = D(0.0) + with assertRaises(AttributeError): + d.x + a: Any = d + with assertRaises(AttributeError): + a.x + d.x = FLOAT_MAGIC + assert d.x == FLOAT_MAGIC + assert a.x == FLOAT_MAGIC + d.x = 0.0 + assert d.x == 0.0 + assert a.x == 0.0 + a.x = FLOAT_MAGIC + assert a.x == FLOAT_MAGIC + d = D(0.0) + a = cast(Any, d) + a.x = FLOAT_MAGIC + assert d.x == FLOAT_MAGIC + else: + f = float(i) + d = D(f) + assert d.x == f + a2: Any = d + assert a2.x == f + +def f(x: float) -> float: + return x + 1 + +def test_return_values() -> None: + a: Any = f + for i in range(-200, 200): + x = float(i) + assert f(x) == x + 1 + assert a(x) == x + 1 + for x in float_vals: + if not math.isnan(x): + assert f(x) == x + 1 + else: + assert math.isnan(f(x)) + +def exc() -> float: + raise IndexError('x') + +def test_exception() -> None: + with assertRaises(IndexError): + exc() + a: Any = exc + with assertRaises(IndexError): + a() + +def test_undefined_local_var() -> None: + if not int(): + x = -113.0 + assert x == -113.0 + if int(): + y = -113.0 + with assertRaises(UnboundLocalError, 'local variable "y" referenced before assignment'): + print(y) + if not int(): + x2 = -1.0 + assert x2 == -1.0 + if int(): + y2 = -1.0 + with assertRaises(UnboundLocalError, 'local variable "y2" referenced before assignment'): + print(y2) + +def test_tuples() -> None: + t1: tuple[float, float] = (1.5, 2.5) + assert t1 == tuple([1.5, 2.5]) + n = int() + 5 + t2: tuple[float, float, float, float] = (n, 1.5, -7, -113) + assert t2 == tuple([5.0, 1.5, -7.0, -113.0]) + +[case testFloatGlueMethodsAndInheritance] +from typing import Any +from typing_extensions import Final + +from mypy_extensions import trait + +from testutil import assertRaises + +MAGIC: Final = -113.0 + +class Base: + def foo(self) -> float: + return 5.0 + + def bar(self, x: float = 2.0) -> float: + return x + 1 + + def hoho(self, x: float) -> float: + return x - 1 + +class Derived(Base): + def foo(self, x: float = 5.0) -> float: + return x + 10 + + def bar(self, x: float = 3, y: float = 20) -> float: + return x + y + 2 + + def hoho(self, x: float = 7) -> float: + return x - 2 + +def test_derived_adds_bitmap() -> None: + b: Base = Derived() + assert b.foo() == 15 + +def test_derived_adds_another_default_arg() -> None: + b: Base = Derived() + assert b.bar() == 25 + assert b.bar(1) == 23 + assert b.bar(MAGIC) == MAGIC + 22 + +def test_derived_switches_arg_to_have_default() -> None: + b: Base = Derived() + assert b.hoho(5) == 3 + assert b.hoho(MAGIC) == MAGIC - 2 + +@trait +class T: + @property + def x(self) -> float: ... + @property + def y(self) -> float: ... + +class C(T): + x: float = 1.0 + y: float = 4 + +def test_read_only_property_in_trait_implemented_as_attribute() -> None: + c = C() + c.x = 5.5 + assert c.x == 5.5 + c.x = MAGIC + assert c.x == MAGIC + assert c.y == 4 + c.y = 6.5 + assert c.y == 6.5 + t: T = C() + assert t.y == 4 + t = c + assert t.x == MAGIC + c.x = 55.5 + assert t.x == 55.5 + assert t.y == 6.5 + a: Any = c + assert a.x == 55.5 + assert a.y == 6.5 + a.x = 7.0 + a.y = 8.0 + assert a.x == 7 + assert a.y == 8 + +class D(T): + xx: float + + @property + def x(self) -> float: + return self.xx + + @property + def y(self) -> float: + raise TypeError + +def test_read_only_property_in_trait_implemented_as_property() -> None: + d = D() + d.xx = 5.0 + assert d.x == 5 + d.xx = MAGIC + assert d.x == MAGIC + with assertRaises(TypeError): + d.y + t: T = d + assert t.x == MAGIC + d.xx = 6.0 + assert t.x == 6 + with assertRaises(TypeError): + t.y + +@trait +class T2: + x: float + y: float + +class C2(T2): + pass + +def test_inherit_trait_attribute() -> None: + c = C2() + c.x = 5.0 + assert c.x == 5 + c.x = MAGIC + assert c.x == MAGIC + with assertRaises(AttributeError): + c.y + c.y = 6.0 + assert c.y == 6.0 + t: T2 = C2() + with assertRaises(AttributeError): + t.y + t = c + assert t.x == MAGIC + c.x = 55.0 + assert t.x == 55 + assert t.y == 6 + a: Any = c + assert a.x == 55 + assert a.y == 6 + a.x = 7.0 + a.y = 8.0 + assert a.x == 7 + assert a.y == 8 + +class D2(T2): + x: float + y: float = 4 + +def test_implement_trait_attribute() -> None: + d = D2() + d.x = 5.0 + assert d.x == 5 + d.x = MAGIC + assert d.x == MAGIC + assert d.y == 4 + d.y = 6.0 + assert d.y == 6 + t: T2 = D2() + assert t.y == 4 + t = d + assert t.x == MAGIC + d.x = 55.0 + assert t.x == 55 + assert t.y == 6 + a: Any = d + assert a.x == 55 + assert a.y == 6 + a.x = 7.0 + a.y = 8.0 + assert a.x == 7 + assert a.y == 8 diff --git a/mypyc/test-data/run-functions.test b/mypyc/test-data/run-functions.test index 21993891c4e3..cf519f30dad8 100644 --- a/mypyc/test-data/run-functions.test +++ b/mypyc/test-data/run-functions.test @@ -1256,3 +1256,55 @@ def foo(**kwargs: Unpack[Person]) -> None: foo(name='Jennifer', age=38) [out] Jennifer + +[case testNestedFunctionDunderDict312] +import sys + +def foo() -> None: + def inner() -> str: return "bar" + print(inner.__dict__) # type: ignore[attr-defined] + inner.__dict__.update({"x": 1}) # type: ignore[attr-defined] + print(inner.__dict__) # type: ignore[attr-defined] + print(inner.x) # type: ignore[attr-defined] + +if sys.version_info >= (3, 12): # type: ignore + foo() +[out] +[out version>=3.12] +{} +{'x': 1} +1 + +[case testFunctoolsUpdateWrapper] +import functools + +def bar() -> None: + def inner() -> str: return "bar" + functools.update_wrapper(inner, bar) # type: ignore + print(inner.__dict__) # type: ignore + +bar() +[out] +{'__module__': 'native', '__name__': 'bar', '__qualname__': 'bar', '__doc__': None, '__wrapped__': } + +[case testCallNestedFunctionWithNamed] +def f() -> None: + def a() -> None: + pass + def b() -> None: + a() + b() +[file driver.py] +from native import f +f() + +[case testCallNestedFunctionWithLambda] +def f(x: int) -> int: + def inc(x: int) -> int: + return x + 1 + return (lambda x: inc(x))(1) +[file driver.py] +from native import f +print(f(1)) +[out] +2 diff --git a/mypyc/test-data/run-generators.test b/mypyc/test-data/run-generators.test index bcf9da1846ae..7e9804c49582 100644 --- a/mypyc/test-data/run-generators.test +++ b/mypyc/test-data/run-generators.test @@ -246,12 +246,12 @@ assert run_generator(another_triple()()) == ((1,), None) assert run_generator(outer()) == ((0, 1, 2, 3, 4), None) [case testYieldThrow] -from typing import Generator, Iterable, Any +from typing import Generator, Iterable, Any, Union from traceback import print_tb from contextlib import contextmanager import wrapsys -def generator() -> Iterable[int]: +def generator() -> Generator[int, None, Union[int, None]]: try: yield 1 yield 2 @@ -264,6 +264,7 @@ def generator() -> Iterable[int]: else: print('caught exception without value') return 0 + return None def no_except() -> Iterable[int]: yield 1 @@ -355,11 +356,11 @@ with ctx_manager() as c: raise Exception File "native.py", line 10, in generator yield 3 - File "native.py", line 30, in wrapper + File "native.py", line 31, in wrapper return (yield from x) File "native.py", line 9, in generator yield 2 - File "native.py", line 30, in wrapper + File "native.py", line 31, in wrapper return (yield from x) caught exception without value caught exception with value some string diff --git a/mypyc/test-data/run-i16.test b/mypyc/test-data/run-i16.test new file mode 100644 index 000000000000..fbb0c15220bc --- /dev/null +++ b/mypyc/test-data/run-i16.test @@ -0,0 +1,338 @@ +[case testI16BasicOps] +from typing import Any, Tuple + +from mypy_extensions import i16, i32, i64 + +from testutil import assertRaises + +def test_box_and_unbox() -> None: + values = (list(range(-2**15, -2**15 + 100)) + + list(range(-1000, 1000)) + + list(range(2**15 - 100, 2**15))) + for i in values: + o: Any = i + x: i16 = o + o2: Any = x + assert o == o2 + assert x == i + with assertRaises(OverflowError, "int too large to convert to i16"): + o = 2**15 + x2: i16 = o + with assertRaises(OverflowError, "int too large to convert to i16"): + o = -2**15 - 1 + x3: i16 = o + +def div_by_7(x: i16) -> i16: + return x // 7 +def div_by_neg_7(x: i16) -> i16: + return x // -7 + +def div(x: i16, y: i16) -> i16: + return x // y + +def test_divide_by_constant() -> None: + for i in range(-1000, 1000): + assert div_by_7(i) == i // 7 + for i in range(-2**15, -2**15 + 1000): + assert div_by_7(i) == i // 7 + for i in range(2**15 - 1000, 2**15): + assert div_by_7(i) == i // 7 + +def test_divide_by_negative_constant() -> None: + for i in range(-1000, 1000): + assert div_by_neg_7(i) == i // -7 + for i in range(-2**15, -2**15 + 1000): + assert div_by_neg_7(i) == i // -7 + for i in range(2**15 - 1000, 2**15): + assert div_by_neg_7(i) == i // -7 + +def test_divide_by_variable() -> None: + values = (list(range(-50, 50)) + + list(range(-2**15, -2**15 + 10)) + + list(range(2**15 - 10, 2**15))) + for x in values: + for y in values: + if y != 0: + if x // y == 2**15: + with assertRaises(OverflowError, "integer division overflow"): + div(x, y) + else: + assert div(x, y) == x // y + else: + with assertRaises(ZeroDivisionError, "integer division or modulo by zero"): + div(x, y) + +def mod_by_7(x: i16) -> i16: + return x % 7 + +def mod_by_neg_7(x: i16) -> i16: + return x // -7 + +def mod(x: i16, y: i16) -> i16: + return x % y + +def test_mod_by_constant() -> None: + for i in range(-1000, 1000): + assert mod_by_7(i) == i % 7 + for i in range(-2**15, -2**15 + 1000): + assert mod_by_7(i) == i % 7 + for i in range(2**15 - 1000, 2**15): + assert mod_by_7(i) == i % 7 + +def test_mod_by_negative_constant() -> None: + for i in range(-1000, 1000): + assert mod_by_neg_7(i) == i // -7 + for i in range(-2**15, -2**15 + 1000): + assert mod_by_neg_7(i) == i // -7 + for i in range(2**15 - 1000, 2**15): + assert mod_by_neg_7(i) == i // -7 + +def test_mod_by_variable() -> None: + values = (list(range(-50, 50)) + + list(range(-2**15, -2**15 + 10)) + + list(range(2**15 - 10, 2**15))) + for x in values: + for y in values: + if y != 0: + assert mod(x, y) == x % y + else: + with assertRaises(ZeroDivisionError, "integer division or modulo by zero"): + mod(x, y) + +def test_simple_arithmetic_ops() -> None: + zero: i16 = int() + one: i16 = zero + 1 + two: i16 = one + 1 + neg_one: i16 = -one + assert one + one == 2 + assert one + two == 3 + assert one + neg_one == 0 + assert one - one == 0 + assert one - two == -1 + assert one * one == 1 + assert one * two == 2 + assert two * two == 4 + assert two * neg_one == -2 + assert neg_one * one == -1 + assert neg_one * neg_one == 1 + assert two * 0 == 0 + assert 0 * two == 0 + assert -one == -1 + assert -two == -2 + assert -neg_one == 1 + assert -zero == 0 + +def test_bitwise_ops() -> None: + x: i16 = 13855 + int() + y: i16 = 367 + int() + z: i16 = -11091 + int() + zero: i16 = int() + one: i16 = zero + 1 + two: i16 = zero + 2 + neg_one: i16 = -one + + assert x & y == 15 + assert x & z == 5133 + assert z & z == z + assert x & zero == 0 + + assert x | y == 14207 + assert x | z == -2369 + assert z | z == z + assert x | 0 == x + + assert x ^ y == 14192 + assert x ^ z == -7502 + assert z ^ z == 0 + assert z ^ 0 == z + + assert x << one == 27710 + assert x << two == -10116 + assert z << two == 21172 + assert z << 0 == z + + assert x >> one == 6927 + assert x >> two == 3463 + assert z >> two == -2773 + assert z >> 0 == z + + assert ~x == -13856 + assert ~z == 11090 + assert ~zero == -1 + assert ~neg_one == 0 + +def eq(x: i16, y: i16) -> bool: + return x == y + +def test_eq() -> None: + assert eq(int(), int()) + assert eq(5 + int(), 5 + int()) + assert eq(-5 + int(), -5 + int()) + assert not eq(int(), 1 + int()) + assert not eq(5 + int(), 6 + int()) + assert not eq(-5 + int(), -6 + int()) + assert not eq(-5 + int(), 5 + int()) + +def test_comparisons() -> None: + one: i16 = 1 + int() + one2: i16 = 1 + int() + two: i16 = 2 + int() + assert one < two + assert not (one < one2) + assert not (two < one) + assert two > one + assert not (one > one2) + assert not (one > two) + assert one <= two + assert one <= one2 + assert not (two <= one) + assert two >= one + assert one >= one2 + assert not (one >= two) + assert one == one2 + assert not (one == two) + assert one != two + assert not (one != one2) + +def test_mixed_comparisons() -> None: + i16_3: i16 = int() + 3 + int_5 = int() + 5 + assert i16_3 < int_5 + assert int_5 > i16_3 + b = i16_3 > int_5 + assert not b + + int_largest = int() + (1 << 15) - 1 + assert int_largest > i16_3 + int_smallest = int() - (1 << 15) + assert i16_3 > int_smallest + + int_too_big = int() + (1 << 15) + int_too_small = int() - (1 << 15) - 1 + with assertRaises(OverflowError): + assert i16_3 < int_too_big + with assertRaises(OverflowError): + assert int_too_big < i16_3 + with assertRaises(OverflowError): + assert i16_3 > int_too_small + with assertRaises(OverflowError): + assert int_too_small < i16_3 + +def test_mixed_arithmetic_and_bitwise_ops() -> None: + i16_3: i16 = int() + 3 + int_5 = int() + 5 + assert i16_3 + int_5 == 8 + assert int_5 - i16_3 == 2 + assert i16_3 << int_5 == 96 + assert int_5 << i16_3 == 40 + assert i16_3 ^ int_5 == 6 + assert int_5 | i16_3 == 7 + + int_largest = int() + (1 << 15) - 1 + assert int_largest - i16_3 == 32764 + int_smallest = int() - (1 << 15) + assert int_smallest + i16_3 == -32765 + + int_too_big = int() + (1 << 15) + int_too_small = int() - (1 << 15) - 1 + with assertRaises(OverflowError): + assert i16_3 & int_too_big + with assertRaises(OverflowError): + assert int_too_small & i16_3 + +def test_coerce_to_and_from_int() -> None: + for shift in range(0, 16): + for sign in 1, -1: + for delta in range(-5, 5): + n = sign * (1 << shift) + delta + if -(1 << 15) <= n < (1 << 15): + x: i16 = n + m: int = x + assert m == n + +def test_explicit_conversion_to_i16() -> None: + x = i16(5) + assert x == 5 + y = int() - 113 + x = i16(y) + assert x == -113 + n64: i64 = 1733 + x = i16(n64) + assert x == 1733 + n32: i32 = -1733 + x = i16(n32) + assert x == -1733 + z = i16(x) + assert z == -1733 + +def test_explicit_conversion_overflow() -> None: + max_i16 = int() + 2**15 - 1 + x = i16(max_i16) + assert x == 2**15 - 1 + assert int(x) == max_i16 + + min_i16 = int() - 2**15 + y = i16(min_i16) + assert y == -2**15 + assert int(y) == min_i16 + + too_big = int() + 2**15 + with assertRaises(OverflowError): + x = i16(too_big) + + too_small = int() - 2**15 - 1 + with assertRaises(OverflowError): + x = i16(too_small) + +def test_i16_from_large_small_literal() -> None: + x = i16(2**15 - 1) + assert x == 2**15 - 1 + x = i16(-2**15) + assert x == -2**15 + +def test_i16_truncate_from_i64() -> None: + large = i64(2**32 + 65536 + 157 + int()) + x = i16(large) + assert x == 157 + small = i64(-2**32 - 65536 - 157 + int()) + x = i16(small) + assert x == -157 + large2 = i64(2**15 + int()) + x = i16(large2) + assert x == -2**15 + small2 = i64(-2**15 - 1 - int()) + x = i16(small2) + assert x == 2**15 - 1 + +def test_i16_truncate_from_i32() -> None: + large = i32(2**16 + 2**30 + 5 + int()) + assert i16(large) == 5 + small = i32(-2**16 - 2**30 - 1 + int()) + assert i16(small) == -1 + +def from_float(x: float) -> i16: + return i16(x) + +def test_explicit_conversion_from_float() -> None: + assert from_float(0.0) == 0 + assert from_float(1.456) == 1 + assert from_float(-1234.567) == -1234 + assert from_float(2**15 - 1) == 2**15 - 1 + assert from_float(-2**15) == -2**15 + # The error message could be better, but this is acceptable + with assertRaises(OverflowError, "int too large to convert to i16"): + assert from_float(float(2**15)) + with assertRaises(OverflowError, "int too large to convert to i16"): + # One ulp below the lowest valid i64 value + from_float(float(-2**15 - 1)) + +def test_tuple_i16() -> None: + a: i16 = 1 + b: i16 = 2 + t = (a, b) + a, b = t + assert a == 1 + assert b == 2 + x: Any = t + tt: Tuple[i16, i16] = x + assert tt == (1, 2) diff --git a/mypyc/test-data/run-i32.test b/mypyc/test-data/run-i32.test index af99fb79d35e..bb1fa43bb9fd 100644 --- a/mypyc/test-data/run-i32.test +++ b/mypyc/test-data/run-i32.test @@ -1,7 +1,7 @@ [case testI32BasicOps] from typing import Any, Tuple -from mypy_extensions import i32, i64 +from mypy_extensions import i16, i32, i64 from testutil import assertRaises @@ -259,11 +259,15 @@ def test_explicit_conversion_to_i32() -> None: n64: i64 = 1733 x = i32(n64) assert x == 1733 - n32 = -1733 + n32: i32 = -1733 x = i32(n32) assert x == -1733 z = i32(x) assert z == -1733 + a: i16 = int() + 19764 + assert i32(a) == 19764 + a = int() - 1 + assert i32(a) == -1 def test_explicit_conversion_overflow() -> None: max_i32 = int() + 2**31 - 1 diff --git a/mypyc/test-data/run-i64.test b/mypyc/test-data/run-i64.test index cd4ac19532d2..1a82ac3e2dd1 100644 --- a/mypyc/test-data/run-i64.test +++ b/mypyc/test-data/run-i64.test @@ -1,7 +1,7 @@ [case testI64BasicOps] from typing import List, Any, Tuple, Union -from mypy_extensions import i64, i32 +from mypy_extensions import i64, i32, i16 from testutil import assertRaises @@ -282,6 +282,10 @@ def test_explicit_conversion_to_i64() -> None: assert x == -1733 z = i64(x) assert z == -1733 + a: i16 = int() + 19764 + assert i64(a) == 19764 + a = int() - 1 + assert i64(a) == -1 def test_explicit_conversion_overflow() -> None: max_i64 = int() + 2**63 - 1 @@ -315,7 +319,8 @@ def test_explicit_conversion_from_float() -> None: assert from_float(0.0) == 0 assert from_float(1.456) == 1 assert from_float(-1234.567) == -1234 - assert from_float(2**63 - 1) == 2**63 - 1 + # Subtract 1024 due to limited precision of 64-bit floats + assert from_float(2**63 - 1024) == 2**63 - 1024 assert from_float(-2**63) == -2**63 # The error message could be better, but this is acceptable with assertRaises(OverflowError, "int too large to convert to i64"): @@ -1499,3 +1504,16 @@ def test_implement_trait_attribute() -> None: a.y = 8 assert a.x == 7 assert a.y == 8 + +class DunderErr: + def __contains__(self, i: i64) -> bool: + raise IndexError() + +def test_dunder_arg_check() -> None: + o: Any = DunderErr() + with assertRaises(TypeError): + 'x' in o + with assertRaises(TypeError): + 2**63 in o + with assertRaises(IndexError): + 1 in o diff --git a/mypyc/test-data/run-imports.test b/mypyc/test-data/run-imports.test index c6d5bdb3d864..c5839d57820e 100644 --- a/mypyc/test-data/run-imports.test +++ b/mypyc/test-data/run-imports.test @@ -2,6 +2,8 @@ [case testImports] import testmodule +import pkg2.mod +import pkg2.mod2 as mm2 def f(x: int) -> int: return testmodule.factorial(5) @@ -13,15 +15,21 @@ def g(x: int) -> int: def test_import_basics() -> None: assert f(5) == 120 assert g(5) == 5 + assert "pkg2.mod" not in globals(), "the root module should be in globals!" + assert pkg2.mod.x == 1 + assert "mod2" not in globals(), "pkg2.mod2 is aliased to mm2!" + assert mm2.y == 2 def test_import_submodule_within_function() -> None: import pkg.mod assert pkg.x == 1 assert pkg.mod.y == 2 + assert "pkg.mod" not in globals(), "the root module should be in globals!" def test_import_as_submodule_within_function() -> None: import pkg.mod as mm assert mm.y == 2 + assert "pkg.mod" not in globals(), "the root module should be in globals!" # TODO: Don't add local imports to globals() # @@ -57,6 +65,11 @@ def foo(x: int) -> int: x = 1 [file pkg/mod.py] y = 2 +[file pkg2/__init__.py] +[file pkg2/mod.py] +x = 1 +[file pkg2/mod2.py] +y = 2 [file nob.py] z = 3 @@ -192,3 +205,61 @@ a.x = 10 x = 20 [file driver.py] import native + +[case testLazyImport] +import shared + +def do_import() -> None: + import a + +assert shared.counter == 0 +do_import() +assert shared.counter == 1 + +[file a.py] +import shared +shared.counter += 1 + +[file shared.py] +counter = 0 + +[case testDelayedImport] +import a +print("inbetween") +import b + +[file a.py] +print("first") + +[file b.py] +print("last") + +[out] +first +inbetween +last + +[case testImportErrorLineNumber] +try: + import enum + import dataclasses, missing # type: ignore[import] +except ImportError as e: + line = e.__traceback__.tb_lineno # type: ignore[attr-defined] + assert line == 3, f"traceback's line number is {line}, expected 3" + +[case testImportGroupIsolation] +def func() -> None: + import second + +import first +func() + +[file first.py] +print("first") + +[file second.py] +print("second") + +[out] +first +second diff --git a/mypyc/test-data/run-integers.test b/mypyc/test-data/run-integers.test index c65f36110b46..d575e141b567 100644 --- a/mypyc/test-data/run-integers.test +++ b/mypyc/test-data/run-integers.test @@ -173,6 +173,7 @@ assert test_isinstance_int_and_not_bool(1) == True [case testIntOps] from typing import Any +from testutil import assertRaises def check_and(x: int, y: int) -> None: # eval() can be trusted to calculate expected result @@ -390,7 +391,7 @@ def test_no_op_conversion() -> None: for x in 1, 55, -1, -7, 1 << 50, 1 << 101, -(1 << 50), -(1 << 101): assert no_op_conversion(x) == x -def test_divide() -> None: +def test_floor_divide() -> None: for x in range(-100, 100): for y in range(-100, 100): if y != 0: @@ -470,6 +471,25 @@ def test_floor_divide_by_literal() -> None: assert div_by_3(i) == i_boxed // int('3') assert div_by_4(i) == i_boxed // int('4') +def test_true_divide() -> None: + for x in range(-150, 100): + for y in range(-150, 100): + if y != 0: + assert x / y == getattr(x, "__truediv__")(y) + large1 = (123 + int())**123 + large2 = (121 + int())**121 + assert large1 / large2 == getattr(large1, "__truediv__")(large2) + assert large1 / 135 == getattr(large1, "__truediv__")(135) + assert large1 / -2 == getattr(large1, "__truediv__")(-2) + assert 17 / large2 == getattr(17, "__truediv__")(large2) + + huge = 10**1000 + int() + with assertRaises(OverflowError, "integer division result too large for a float"): + huge / 2 + with assertRaises(OverflowError, "integer division result too large for a float"): + huge / -2 + assert 1 / huge == 0.0 + [case testIntMinMax] def test_int_min_max() -> None: x: int = 200 diff --git a/mypyc/test-data/run-math.test b/mypyc/test-data/run-math.test new file mode 100644 index 000000000000..266b4851575f --- /dev/null +++ b/mypyc/test-data/run-math.test @@ -0,0 +1,106 @@ +# Test cases for the math module (compile and run) + +[case testMathOps] +from typing import Any, Callable +from typing_extensions import Final +import math +from math import pi, e, tau, inf, nan +from testutil import assertRaises, float_vals, assertDomainError, assertMathRangeError + +pymath: Any = math + +def validate_one_arg(test: Callable[[float], float], validate: Callable[[float], float]) -> None: + """Ensure that test and validate behave the same for various float args.""" + for x in float_vals: + try: + expected = validate(x) + except Exception as e: + try: + test(x) + assert False, f"no exception raised for {x!r}, expected {e!r}" + except Exception as e2: + assert repr(e) == repr(e2), f"actual for {x!r}: {e2!r}, expected: {e!r}" + continue + actual = test(x) + assert repr(actual) == repr(expected), ( + f"actual for {x!r}: {actual!r}, expected {expected!r}") + +def validate_two_arg(test: Callable[[float, float], float], + validate: Callable[[float, float], float]) -> None: + """Ensure that test and validate behave the same for various float args.""" + for x in float_vals: + for y in float_vals: + args = f"({x!r}, {y!r})" + try: + expected = validate(x, y) + except Exception as e: + try: + test(x, y) + assert False, f"no exception raised for {args}, expected {e!r}" + except Exception as e2: + assert repr(e) == repr(e2), f"actual for {args}: {e2!r}, expected: {e!r}" + continue + try: + actual = test(x, y) + except Exception as e: + assert False, f"no exception expected for {args}, got {e!r}" + assert repr(actual) == repr(expected), ( + f"actual for {args}: {actual!r}, expected {expected!r}") + +def test_sqrt() -> None: + validate_one_arg(lambda x: math.sqrt(x), pymath.sqrt) + +def test_sin() -> None: + validate_one_arg(lambda x: math.sin(x), pymath.sin) + +def test_cos() -> None: + validate_one_arg(lambda x: math.cos(x), pymath.cos) + +def test_tan() -> None: + validate_one_arg(lambda x: math.tan(x), pymath.tan) + +def test_exp() -> None: + validate_one_arg(lambda x: math.exp(x), pymath.exp) + +def test_log() -> None: + validate_one_arg(lambda x: math.log(x), pymath.log) + +def test_floor() -> None: + validate_one_arg(lambda x: math.floor(x), pymath.floor) + +def test_ceil() -> None: + validate_one_arg(lambda x: math.ceil(x), pymath.ceil) + +def test_fabs() -> None: + validate_one_arg(lambda x: math.fabs(x), pymath.fabs) + +def test_pow() -> None: + validate_two_arg(lambda x, y: math.pow(x, y), pymath.pow) + +def test_copysign() -> None: + validate_two_arg(lambda x, y: math.copysign(x, y), pymath.copysign) + +def test_isinf() -> None: + for x in float_vals: + assert repr(math.isinf(x)) == repr(pymath.isinf(x)) + +def test_isnan() -> None: + for x in float_vals: + assert repr(math.isnan(x)) == repr(pymath.isnan(x)) + + +def test_pi_is_inlined_correctly() -> None: + assert math.pi == pi == 3.141592653589793 + +def test_e_is_inlined_correctly() -> None: + assert math.e == e == 2.718281828459045 + +def test_tau_is_inlined_correctly() -> None: + assert math.tau == tau == 6.283185307179586 + +def test_inf_is_inlined_correctly() -> None: + assert math.inf == inf == float("inf") + +def test_nan_is_inlined_correctly() -> None: + assert math.isnan(math.nan) + assert math.isnan(nan) diff --git a/mypyc/test-data/run-misc.test b/mypyc/test-data/run-misc.test index 267a3441808f..14bb5be979ae 100644 --- a/mypyc/test-data/run-misc.test +++ b/mypyc/test-data/run-misc.test @@ -84,6 +84,36 @@ assert f(a) is a assert g(None) == 1 assert g(a) == 2 +[case testInferredOptionalAssignment] +from typing import Any, Generator + +def f(b: bool) -> Any: + if b: + x = None + else: + x = 1 + + if b: + y = 1 + else: + y = None + + m = 1 if b else None + n = None if b else 1 + return ((x, y), (m, n)) + +def gen(b: bool) -> Generator[Any, None, None]: + if b: + y = 1 + else: + y = None + yield y + +assert f(False) == ((1, None), (None, 1)) +assert f(True) == ((None, 1), (1, None)) +assert next(gen(False)) is None +assert next(gen(True)) == 1 + [case testWith] from typing import Any class Thing: @@ -158,7 +188,7 @@ exit! a ohno caught [case testDisplays] -from typing import List, Set, Tuple, Sequence, Dict, Any +from typing import List, Set, Tuple, Sequence, Dict, Any, Mapping def listDisplay(x: List[int], y: List[int]) -> List[int]: return [1, 2, *x, *y, 3] @@ -172,12 +202,17 @@ def tupleDisplay(x: Sequence[str], y: Sequence[str]) -> Tuple[str, ...]: def dictDisplay(x: str, y1: Dict[str, int], y2: Dict[str, int]) -> Dict[str, int]: return {x: 2, **y1, 'z': 3, **y2} +def dictDisplayUnpackMapping(obj: Mapping[str, str]) -> Dict[str, str]: + return {**obj, "env": "value"} + [file driver.py] -from native import listDisplay, setDisplay, tupleDisplay, dictDisplay +import os +from native import listDisplay, setDisplay, tupleDisplay, dictDisplay, dictDisplayUnpackMapping assert listDisplay([4], [5, 6]) == [1, 2, 4, 5, 6, 3] assert setDisplay({4}, {5}) == {1, 2, 3, 4, 5} assert tupleDisplay(['4', '5'], ['6']) == ('1', '2', '4', '5', '6', '3') assert dictDisplay('x', {'y1': 1}, {'y2': 2, 'z': 5}) == {'x': 2, 'y1': 1, 'y2': 2, 'z': 5} +assert dictDisplayUnpackMapping(os.environ) == {**os.environ, "env": "value"} [case testArbitraryLvalues] from typing import List, Dict, Any @@ -1062,8 +1097,10 @@ B = sys.platform == 'x' and sys.foobar C = sys.platform == 'x' and f(a, -b, 'y') > [c + e, g(y=2)] C = sys.platform == 'x' and cast(a, b[c]) C = sys.platform == 'x' and (lambda x: y + x) -# TODO: This still doesn't work -# C = sys.platform == 'x' and (x for y in z) +C = sys.platform == 'x' and (x for y in z) +C = sys.platform == 'x' and [x for y in z] +C = sys.platform == 'x' and {x: x for y in z} +C = sys.platform == 'x' and {x for y in z} assert not A assert not B @@ -1073,29 +1110,14 @@ assert not C # make the initial import fail assert False -class C: - def __init__(self): - self.x = 1 - self.y = 2 -def test() -> None: - a = C() [file driver.py] # load native, cause PyInit to be run, create the module but don't finish initializing the globals -try: - import native -except: - pass -try: - # try accessing those globals that were never properly initialized - import native - native.test() -# should fail with AssertionError due to `assert False` in other function -except AssertionError: - pass - -[case testRepeatedUnderscoreFunctions] -def _(arg): pass -def _(arg): pass +for _ in range(2): + try: + import native + raise RuntimeError('exception expected') + except AssertionError: + pass [case testUnderscoreFunctionsInMethods] diff --git a/mypyc/test-data/run-multimodule.test b/mypyc/test-data/run-multimodule.test index 418af66ba060..70c73dc2088b 100644 --- a/mypyc/test-data/run-multimodule.test +++ b/mypyc/test-data/run-multimodule.test @@ -11,21 +11,23 @@ -- about how this is specified (e.g. .2 file name suffixes). [case testMultiModulePackage] -from p.other import g +from p.other import g, _i as i def f(x: int) -> int: from p.other import h - return h(g(x + 1)) + return i(h(g(x + 1))) [file p/__init__.py] [file p/other.py] def g(x: int) -> int: return x + 2 def h(x: int) -> int: return x + 1 +def _i(x: int) -> int: + return x + 3 [file driver.py] import native from native import f from p.other import g -assert f(3) == 7 +assert f(3) == 10 assert g(2) == 4 try: f(1.1) diff --git a/mypyc/test-data/run-sets.test b/mypyc/test-data/run-sets.test index 56c946933fac..8d178d03a75b 100644 --- a/mypyc/test-data/run-sets.test +++ b/mypyc/test-data/run-sets.test @@ -141,8 +141,8 @@ def test_in_set() -> None: assert main_set(item), f"{item!r} should be in set_main" assert not main_negated_set(item), item - assert non_final_name_set(non_const) global non_const + assert non_final_name_set(non_const) non_const = "updated" assert non_final_name_set("updated") diff --git a/mypyc/test-data/run-strings.test b/mypyc/test-data/run-strings.test index 4a20c13ce789..be668435d073 100644 --- a/mypyc/test-data/run-strings.test +++ b/mypyc/test-data/run-strings.test @@ -232,8 +232,7 @@ def test_fstring_basics() -> None: x = bytes([1, 2, 3, 4]) # assert f'bytes: {x}' == "bytes: b'\\x01\\x02\\x03\\x04'" - # error: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; - # use "{!r}" if this is desired behavior behavior + # error: If x = b'abc' then f"{x}" or "{}".format(x) produces "b'abc'", not "abc". If this is desired behavior, use f"{x!r}" or "{!r}".format(x). Otherwise, decode the bytes float_num = 123.4 assert f'{float_num}' == '123.4' diff --git a/mypyc/test-data/run-tuples.test b/mypyc/test-data/run-tuples.test index f6c92b9c720f..0851c15e57fd 100644 --- a/mypyc/test-data/run-tuples.test +++ b/mypyc/test-data/run-tuples.test @@ -98,6 +98,7 @@ assert f(Sub(3, 2)) == 3 -- Ref: https://github.com/mypyc/mypyc/issues/924 [case testNamedTupleClassSyntax] from typing import Dict, List, NamedTuple, Optional, Tuple, Union +from typing_extensions import final class FuncIR: pass @@ -121,6 +122,11 @@ class Record(NamedTuple): # Ref: https://github.com/mypyc/mypyc/issues/938 class ClassIR: pass +# Ref: https://github.com/mypyc/mypyc/issues/927 +@final +class Inextensible(NamedTuple): + x: int + [file driver.py] from typing import ForwardRef, Optional from native import ClassIR, FuncIR, Record diff --git a/mypyc/test-data/run-u8.test b/mypyc/test-data/run-u8.test new file mode 100644 index 000000000000..cddb031e3352 --- /dev/null +++ b/mypyc/test-data/run-u8.test @@ -0,0 +1,303 @@ +[case testU8BasicOps] +from typing import Any, Tuple + +from mypy_extensions import u8, i16, i32, i64 +from typing_extensions import Final + +from testutil import assertRaises + +ERROR: Final = 239 + +def test_box_and_unbox() -> None: + for i in range(0, 256): + o: Any = i + x: u8 = o + o2: Any = x + assert o == o2 + assert x == i + with assertRaises(OverflowError, "int too large or small to convert to u8"): + o = 256 + x2: u8 = o + with assertRaises(OverflowError, "int too large or small to convert to u8"): + o = -1 + x3: u8 = o + +def div_by_7(x: u8) -> u8: + return x // 7 + +def div(x: u8, y: u8) -> u8: + return x // y + +def test_divide_by_constant() -> None: + for i in range(0, 256): + assert div_by_7(i) == i // 7 + +def test_divide_by_variable() -> None: + for x in range(0, 256): + for y in range(0, 256): + if y != 0: + assert div(x, y) == x // y + else: + with assertRaises(ZeroDivisionError, "integer division or modulo by zero"): + div(x, y) + +def mod_by_7(x: u8) -> u8: + return x % 7 + +def mod(x: u8, y: u8) -> u8: + return x % y + +def test_mod_by_constant() -> None: + for i in range(0, 256): + assert mod_by_7(i) == i % 7 + +def test_mod_by_variable() -> None: + for x in range(0, 256): + for y in range(0, 256): + if y != 0: + assert mod(x, y) == x % y + else: + with assertRaises(ZeroDivisionError, "integer division or modulo by zero"): + mod(x, y) + +def test_simple_arithmetic_ops() -> None: + zero: u8 = int() + one: u8 = zero + 1 + two: u8 = one + 1 + neg_one: u8 = -one + assert neg_one == 255 + assert one + one == 2 + assert one + two == 3 + assert one + neg_one == 0 + assert one - one == 0 + assert one - two == 255 + assert one * one == 1 + assert one * two == 2 + assert two * two == 4 + assert two * neg_one == 254 + assert neg_one * one == 255 + assert neg_one * neg_one == 1 + assert two * 0 == 0 + assert 0 * two == 0 + assert -one == 255 + assert -two == 254 + assert -neg_one == 1 + assert -zero == 0 + +def test_bitwise_ops() -> None: + x: u8 = 184 + int() + y: u8 = 79 + int() + z: u8 = 113 + int() + zero: u8 = int() + one: u8 = zero + 1 + two: u8 = zero + 2 + neg_one: u8 = -one + + assert x & y == 8 + assert x & z == 48 + assert z & z == z + assert x & zero == 0 + + assert x | y == 255 + assert x | z == 249 + assert z | z == z + assert x | 0 == x + + assert x ^ y == 247 + assert x ^ z == 201 + assert z ^ z == 0 + assert z ^ 0 == z + + assert x << one == 112 + assert x << two == 224 + assert z << two == 196 + assert z << 0 == z + + assert x >> one == 92 + assert x >> two == 46 + assert z >> two == 28 + assert z >> 0 == z + + for i in range(256): + t: u8 = i + assert ~t == (~(i + int()) & 0xff) + +def eq(x: u8, y: u8) -> bool: + return x == y + +def test_eq() -> None: + assert eq(int(), int()) + assert eq(5 + int(), 5 + int()) + assert not eq(int(), 1 + int()) + assert not eq(5 + int(), 6 + int()) + +def test_comparisons() -> None: + one: u8 = 1 + int() + one2: u8 = 1 + int() + two: u8 = 2 + int() + assert one < two + assert not (one < one2) + assert not (two < one) + assert two > one + assert not (one > one2) + assert not (one > two) + assert one <= two + assert one <= one2 + assert not (two <= one) + assert two >= one + assert one >= one2 + assert not (one >= two) + assert one == one2 + assert not (one == two) + assert one != two + assert not (one != one2) + +def test_mixed_comparisons() -> None: + u8_3: u8 = int() + 3 + int_5 = int() + 5 + assert u8_3 < int_5 + assert int_5 > u8_3 + b = u8_3 > int_5 + assert not b + + int_largest = int() + 255 + assert int_largest > u8_3 + int_smallest = int() + assert u8_3 > int_smallest + + int_too_big = int() + 256 + int_too_small = int() -1 + with assertRaises(OverflowError): + assert u8_3 < int_too_big + with assertRaises(OverflowError): + assert int_too_big < u8_3 + with assertRaises(OverflowError): + assert u8_3 > int_too_small + with assertRaises(OverflowError): + assert int_too_small < u8_3 + +def test_mixed_arithmetic_and_bitwise_ops() -> None: + u8_3: u8 = int() + 3 + int_5 = int() + 5 + assert u8_3 + int_5 == 8 + assert int_5 - u8_3 == 2 + assert u8_3 << int_5 == 96 + assert int_5 << u8_3 == 40 + assert u8_3 ^ int_5 == 6 + assert int_5 | u8_3 == 7 + + int_largest = int() + 255 + assert int_largest - u8_3 == 252 + int_smallest = int() + assert int_smallest + u8_3 == 3 + + int_too_big = int() + 256 + int_too_small = int() - 1 + with assertRaises(OverflowError): + assert u8_3 & int_too_big + with assertRaises(OverflowError): + assert int_too_small & u8_3 + +def test_coerce_to_and_from_int() -> None: + for n in range(0, 256): + x: u8 = n + m: int = x + assert m == n + +def test_explicit_conversion_to_u8() -> None: + x = u8(5) + assert x == 5 + y = int() + ERROR + x = u8(y) + assert x == ERROR + n64: i64 = 233 + x = u8(n64) + assert x == 233 + n32: i32 = 234 + x = u8(n32) + assert x == 234 + z = u8(x) + assert z == 234 + n16: i16 = 231 + x = u8(n16) + assert x == 231 + +def test_explicit_conversion_overflow() -> None: + max_u8 = int() + 255 + x = u8(max_u8) + assert x == 255 + assert int(x) == max_u8 + + min_u8 = int() + y = u8(min_u8) + assert y == 0 + assert int(y) == min_u8 + + too_big = int() + 256 + with assertRaises(OverflowError): + x = u8(too_big) + + too_small = int() - 1 + with assertRaises(OverflowError): + x = u8(too_small) + +def test_u8_from_large_small_literal() -> None: + x = u8(255) # XXX u8(2**15 - 1) + assert x == 255 + x = u8(0) + assert x == 0 + +def test_u8_truncate_from_i64() -> None: + large = i64(2**32 + 256 + 157 + int()) + x = u8(large) + assert x == 157 + small = i64(-2**32 - 256 - 157 + int()) + x = u8(small) + assert x == 256 - 157 + large2 = i64(2**8 + int()) + x = u8(large2) + assert x == 0 + small2 = i64(-2**8 - 1 - int()) + x = u8(small2) + assert x == 255 + +def test_u8_truncate_from_i32() -> None: + large = i32(2**16 + 2**8 + 5 + int()) + assert u8(large) == 5 + small = i32(-2**16 - 2**8 - 1 + int()) + assert u8(small) == 255 + +def from_float(x: float) -> u8: + return u8(x) + +def test_explicit_conversion_from_float() -> None: + assert from_float(0.0) == 0 + assert from_float(1.456) == 1 + assert from_float(234.567) == 234 + assert from_float(255) == 255 + assert from_float(0) == 0 + assert from_float(-0.999) == 0 + # The error message could be better, but this is acceptable + with assertRaises(OverflowError, "int too large or small to convert to u8"): + assert from_float(float(256)) + with assertRaises(OverflowError, "int too large or small to convert to u8"): + # One ulp below the lowest valid i64 value + from_float(float(-1.0)) + +def test_tuple_u8() -> None: + a: u8 = 1 + b: u8 = 2 + t = (a, b) + a, b = t + assert a == 1 + assert b == 2 + x: Any = t + tt: Tuple[u8, u8] = x + assert tt == (1, 2) + +def test_convert_u8_to_native_int() -> None: + for i in range(256): + x: u8 = i + assert i16(x) == i + assert i32(x) == i + assert i64(x) == i diff --git a/mypyc/test/test_cheader.py b/mypyc/test/test_cheader.py index cc0fd9df2b34..f2af41c22ea9 100644 --- a/mypyc/test/test_cheader.py +++ b/mypyc/test/test_cheader.py @@ -7,6 +7,7 @@ import re import unittest +from mypyc.ir.ops import PrimitiveDescription from mypyc.primitives import registry from mypyc.primitives.registry import CFunctionDescription @@ -25,17 +26,24 @@ def check_name(name: str) -> None: rf"\b{name}\b", header ), f'"{name}" is used in mypyc.primitives but not declared in CPy.h' - for values in [ + for old_values in [ registry.method_call_ops.values(), registry.function_ops.values(), - registry.binary_ops.values(), registry.unary_ops.values(), ]: + for old_ops in old_values: + if isinstance(old_ops, CFunctionDescription): + old_ops = [old_ops] + for old_op in old_ops: + check_name(old_op.c_function_name) + + for values in [registry.binary_ops.values()]: for ops in values: - if isinstance(ops, CFunctionDescription): + if isinstance(ops, PrimitiveDescription): ops = [ops] for op in ops: - check_name(op.c_function_name) + if op.c_function_name is not None: + check_name(op.c_function_name) primitives_path = os.path.join(os.path.dirname(__file__), "..", "primitives") for fnam in glob.glob(f"{primitives_path}/*.py"): diff --git a/mypyc/test/test_emit.py b/mypyc/test/test_emit.py index 7351cd7fb13e..e4ace3ec01f0 100644 --- a/mypyc/test/test_emit.py +++ b/mypyc/test/test_emit.py @@ -4,7 +4,7 @@ from mypyc.codegen.emit import Emitter, EmitterContext from mypyc.ir.ops import BasicBlock, Register, Value -from mypyc.ir.rtypes import int_rprimitive +from mypyc.ir.rtypes import RTuple, bool_rprimitive, int_rprimitive, str_rprimitive from mypyc.namegen import NameGenerator @@ -22,6 +22,16 @@ def test_reg(self) -> None: emitter = Emitter(self.context, names) assert emitter.reg(self.n) == "cpy_r_n" + def test_object_annotation(self) -> None: + emitter = Emitter(self.context, {}) + assert emitter.object_annotation("hello, world", "line;") == " /* 'hello, world' */" + assert ( + emitter.object_annotation(list(range(30)), "line;") + == """\ + /* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29] */""" + ) + def test_emit_line(self) -> None: emitter = Emitter(self.context, {}) emitter.emit_line("line;") @@ -29,3 +39,31 @@ def test_emit_line(self) -> None: emitter.emit_line("f();") emitter.emit_line("}") assert emitter.fragments == ["line;\n", "a {\n", " f();\n", "}\n"] + emitter = Emitter(self.context, {}) + emitter.emit_line("CPyStatics[0];", ann="hello, world") + emitter.emit_line("CPyStatics[1];", ann=list(range(30))) + assert emitter.fragments[0] == "CPyStatics[0]; /* 'hello, world' */\n" + assert ( + emitter.fragments[1] + == """\ +CPyStatics[1]; /* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29] */\n""" + ) + + def test_emit_undefined_value_for_simple_type(self) -> None: + emitter = Emitter(self.context, {}) + assert emitter.c_undefined_value(int_rprimitive) == "CPY_INT_TAG" + assert emitter.c_undefined_value(str_rprimitive) == "NULL" + assert emitter.c_undefined_value(bool_rprimitive) == "2" + + def test_emit_undefined_value_for_tuple(self) -> None: + emitter = Emitter(self.context, {}) + assert ( + emitter.c_undefined_value(RTuple([str_rprimitive, int_rprimitive, bool_rprimitive])) + == "(tuple_T3OIC) { NULL, CPY_INT_TAG, 2 }" + ) + assert emitter.c_undefined_value(RTuple([str_rprimitive])) == "(tuple_T1O) { NULL }" + assert ( + emitter.c_undefined_value(RTuple([RTuple([str_rprimitive]), bool_rprimitive])) + == "(tuple_T2T1OC) { { NULL }, 2 }" + ) diff --git a/mypyc/test/test_emitfunc.py b/mypyc/test/test_emitfunc.py index d7dcf3be532b..317427afac5a 100644 --- a/mypyc/test/test_emitfunc.py +++ b/mypyc/test/test_emitfunc.py @@ -841,7 +841,9 @@ def assert_emit( else: expected_lines = expected.rstrip().split("\n") expected_lines = [line.strip(" ") for line in expected_lines] - assert_string_arrays_equal(expected_lines, actual_lines, msg="Generated code unexpected") + assert_string_arrays_equal( + expected_lines, actual_lines, msg="Generated code unexpected", traceback=True + ) if skip_next: assert visitor.op_index == 1 else: @@ -859,6 +861,8 @@ def assert_emit_binary_op( args = [left, right] if desc.ordering is not None: args = [args[i] for i in desc.ordering] + # This only supports primitives that map to C calls + assert desc.c_function_name is not None self.assert_emit( CallC( desc.c_function_name, @@ -894,12 +898,7 @@ def test_simple(self) -> None: generate_native_function(fn, emitter, "prog.py", "prog") result = emitter.fragments assert_string_arrays_equal( - [ - "CPyTagged CPyDef_myfunc(CPyTagged cpy_r_arg) {\n", - "CPyL0: ;\n", - " return cpy_r_arg;\n", - "}\n", - ], + ["CPyTagged CPyDef_myfunc(CPyTagged cpy_r_arg) {\n", " return cpy_r_arg;\n", "}\n"], result, msg="Generated code invalid", ) @@ -922,7 +921,6 @@ def test_register(self) -> None: [ "PyObject *CPyDef_myfunc(CPyTagged cpy_r_arg) {\n", " CPyTagged cpy_r_r0;\n", - "CPyL0: ;\n", " cpy_r_r0 = 10;\n", " CPy_Unreachable();\n", "}\n", diff --git a/mypyc/test/test_external.py b/mypyc/test/test_external.py index 6deabd81255e..22eb8019133c 100644 --- a/mypyc/test/test_external.py +++ b/mypyc/test/test_external.py @@ -5,6 +5,7 @@ import os import subprocess import sys +import tempfile import unittest base_dir = os.path.join(os.path.dirname(__file__), "..", "..") @@ -16,34 +17,33 @@ class TestExternal(unittest.TestCase): @unittest.skipIf(sys.platform.startswith("win"), "rt tests don't work on windows") def test_c_unit_test(self) -> None: """Run C unit tests in a subprocess.""" - # Build Google Test, the C++ framework we use for testing C code. - # The source code for Google Test is copied to this repository. cppflags: list[str] = [] env = os.environ.copy() if sys.platform == "darwin": cppflags += ["-mmacosx-version-min=10.10", "-stdlib=libc++"] env["CPPFLAGS"] = " ".join(cppflags) - subprocess.check_call( - ["make", "libgtest.a"], - env=env, - cwd=os.path.join(base_dir, "mypyc", "external", "googletest", "make"), - ) # Build Python wrapper for C unit tests. - env = os.environ.copy() - env["CPPFLAGS"] = " ".join(cppflags) - status = subprocess.check_call( - [sys.executable, "setup.py", "build_ext", "--inplace"], - env=env, - cwd=os.path.join(base_dir, "mypyc", "lib-rt"), - ) - # Run C unit tests. - env = os.environ.copy() - if "GTEST_COLOR" not in os.environ: - env["GTEST_COLOR"] = "yes" # Use fancy colors - status = subprocess.call( - [sys.executable, "-c", "import sys, test_capi; sys.exit(test_capi.run_tests())"], - env=env, - cwd=os.path.join(base_dir, "mypyc", "lib-rt"), - ) - if status != 0: - raise AssertionError("make test: C unit test failure") + + with tempfile.TemporaryDirectory() as tmpdir: + status = subprocess.check_call( + [ + sys.executable, + "setup.py", + "build_ext", + f"--build-lib={tmpdir}", + f"--build-temp={tmpdir}", + ], + env=env, + cwd=os.path.join(base_dir, "mypyc", "lib-rt"), + ) + # Run C unit tests. + env = os.environ.copy() + if "GTEST_COLOR" not in os.environ: + env["GTEST_COLOR"] = "yes" # Use fancy colors + status = subprocess.call( + [sys.executable, "-c", "import sys, test_capi; sys.exit(test_capi.run_tests())"], + env=env, + cwd=tmpdir, + ) + if status != 0: + raise AssertionError("make test: C unit test failure") diff --git a/mypyc/test/test_irbuild.py b/mypyc/test/test_irbuild.py index cb5e690eed55..5b3f678d8f17 100644 --- a/mypyc/test/test_irbuild.py +++ b/mypyc/test/test_irbuild.py @@ -31,6 +31,7 @@ "irbuild-set.test", "irbuild-str.test", "irbuild-bytes.test", + "irbuild-float.test", "irbuild-statements.test", "irbuild-nested.test", "irbuild-classes.test", @@ -41,6 +42,8 @@ "irbuild-strip-asserts.test", "irbuild-i64.test", "irbuild-i32.test", + "irbuild-i16.test", + "irbuild-u8.test", "irbuild-vectorcall.test", "irbuild-unreachable.test", "irbuild-isinstance.test", @@ -48,6 +51,7 @@ "irbuild-singledispatch.test", "irbuild-constant-fold.test", "irbuild-glue-methods.test", + "irbuild-math.test", ] if sys.version_info >= (3, 10): diff --git a/mypyc/test/test_ircheck.py b/mypyc/test/test_ircheck.py index 008963642272..7f7063cdc5e6 100644 --- a/mypyc/test/test_ircheck.py +++ b/mypyc/test/test_ircheck.py @@ -117,7 +117,7 @@ def test_invalid_return_type(self) -> None: blocks=[self.basic_block([ret])], ) assert_has_error( - fn, FnError(source=ret, desc="Cannot coerce source type int32 to dest type int64") + fn, FnError(source=ret, desc="Cannot coerce source type i32 to dest type i64") ) def test_invalid_assign(self) -> None: @@ -130,7 +130,7 @@ def test_invalid_assign(self) -> None: blocks=[self.basic_block([assign, ret])], ) assert_has_error( - fn, FnError(source=assign, desc="Cannot coerce source type int32 to dest type int64") + fn, FnError(source=assign, desc="Cannot coerce source type i32 to dest type i64") ) def test_can_coerce_to(self) -> None: diff --git a/mypyc/test/test_lowering.py b/mypyc/test/test_lowering.py new file mode 100644 index 000000000000..50a9a7390855 --- /dev/null +++ b/mypyc/test/test_lowering.py @@ -0,0 +1,56 @@ +"""Runner for lowering transform tests.""" + +from __future__ import annotations + +import os.path + +from mypy.errors import CompileError +from mypy.test.config import test_temp_dir +from mypy.test.data import DataDrivenTestCase +from mypyc.common import TOP_LEVEL_NAME +from mypyc.ir.pprint import format_func +from mypyc.options import CompilerOptions +from mypyc.test.testutil import ( + ICODE_GEN_BUILTINS, + MypycDataSuite, + assert_test_output, + build_ir_for_single_file, + remove_comment_lines, + replace_word_size, + use_custom_builtins, +) +from mypyc.transform.exceptions import insert_exception_handling +from mypyc.transform.flag_elimination import do_flag_elimination +from mypyc.transform.lower import lower_ir +from mypyc.transform.refcount import insert_ref_count_opcodes +from mypyc.transform.uninit import insert_uninit_checks + + +class TestLowering(MypycDataSuite): + files = ["lowering-int.test", "lowering-list.test"] + base_path = test_temp_dir + + def run_case(self, testcase: DataDrivenTestCase) -> None: + with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase): + expected_output = remove_comment_lines(testcase.output) + expected_output = replace_word_size(expected_output) + try: + ir = build_ir_for_single_file(testcase.input) + except CompileError as e: + actual = e.messages + else: + actual = [] + for fn in ir: + if fn.name == TOP_LEVEL_NAME and not testcase.name.endswith("_toplevel"): + continue + options = CompilerOptions() + # Lowering happens after exception handling and ref count opcodes have + # been added. Any changes must maintain reference counting semantics. + insert_uninit_checks(fn) + insert_exception_handling(fn) + insert_ref_count_opcodes(fn) + lower_ir(fn, options) + do_flag_elimination(fn, options) + actual.extend(format_func(fn)) + + assert_test_output(testcase, actual, "Invalid source code output", expected_output) diff --git a/mypyc/test/test_optimizations.py b/mypyc/test/test_optimizations.py new file mode 100644 index 000000000000..3f1f46ac1dd7 --- /dev/null +++ b/mypyc/test/test_optimizations.py @@ -0,0 +1,68 @@ +"""Runner for IR optimization tests.""" + +from __future__ import annotations + +import os.path + +from mypy.errors import CompileError +from mypy.test.config import test_temp_dir +from mypy.test.data import DataDrivenTestCase +from mypyc.common import TOP_LEVEL_NAME +from mypyc.ir.func_ir import FuncIR +from mypyc.ir.pprint import format_func +from mypyc.options import CompilerOptions +from mypyc.test.testutil import ( + ICODE_GEN_BUILTINS, + MypycDataSuite, + assert_test_output, + build_ir_for_single_file, + remove_comment_lines, + use_custom_builtins, +) +from mypyc.transform.copy_propagation import do_copy_propagation +from mypyc.transform.flag_elimination import do_flag_elimination +from mypyc.transform.uninit import insert_uninit_checks + + +class OptimizationSuite(MypycDataSuite): + """Base class for IR optimization test suites. + + To use this, add a base class and define "files" and "do_optimizations". + """ + + base_path = test_temp_dir + + def run_case(self, testcase: DataDrivenTestCase) -> None: + with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase): + expected_output = remove_comment_lines(testcase.output) + try: + ir = build_ir_for_single_file(testcase.input) + except CompileError as e: + actual = e.messages + else: + actual = [] + for fn in ir: + if fn.name == TOP_LEVEL_NAME and not testcase.name.endswith("_toplevel"): + continue + insert_uninit_checks(fn) + self.do_optimizations(fn) + actual.extend(format_func(fn)) + + assert_test_output(testcase, actual, "Invalid source code output", expected_output) + + def do_optimizations(self, fn: FuncIR) -> None: + raise NotImplementedError + + +class TestCopyPropagation(OptimizationSuite): + files = ["opt-copy-propagation.test"] + + def do_optimizations(self, fn: FuncIR) -> None: + do_copy_propagation(fn, CompilerOptions()) + + +class TestFlagElimination(OptimizationSuite): + files = ["opt-flag-elimination.test"] + + def do_optimizations(self, fn: FuncIR) -> None: + do_flag_elimination(fn, CompilerOptions()) diff --git a/mypyc/test/test_run.py b/mypyc/test/test_run.py index 6a5ab87fca49..467ef8b87a92 100644 --- a/mypyc/test/test_run.py +++ b/mypyc/test/test_run.py @@ -11,11 +11,11 @@ import subprocess import sys import time -from typing import Any, Iterator, cast +from typing import Any, Iterator from mypy import build from mypy.errors import CompileError -from mypy.options import TYPE_VAR_TUPLE, UNPACK, Options +from mypy.options import Options from mypy.test.config import test_temp_dir from mypy.test.data import DataDrivenTestCase from mypy.test.helpers import assert_module_equivalence, perform_file_operations @@ -41,7 +41,10 @@ "run-integers.test", "run-i64.test", "run-i32.test", + "run-i16.test", + "run-u8.test", "run-floats.test", + "run-math.test", "run-bools.test", "run-strings.test", "run-bytes.test", @@ -62,12 +65,10 @@ "run-dunders.test", "run-singledispatch.test", "run-attrs.test", + "run-python37.test", + "run-python38.test", ] -files.append("run-python37.test") -if sys.version_info >= (3, 8): - files.append("run-python38.test") - if sys.version_info >= (3, 10): files.append("run-match.test") @@ -108,15 +109,13 @@ def run_setup(script_name: str, script_args: list[str]) -> bool: finally: sys.argv = save_argv except SystemExit as e: - # typeshed reports code as being an int but that is wrong - code = cast(Any, e).code # distutils converts KeyboardInterrupt into a SystemExit with # "interrupted" as the argument. Convert it back so that # pytest will exit instead of just failing the test. - if code == "interrupted": + if e.code == "interrupted": raise KeyboardInterrupt from e - return code == 0 or code is None + return e.code == 0 or e.code is None return True @@ -143,9 +142,9 @@ class TestRun(MypycDataSuite): def run_case(self, testcase: DataDrivenTestCase) -> None: # setup.py wants to be run from the root directory of the package, which we accommodate # by chdiring into tmp/ - with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase), ( - chdir_manager("tmp") - ): + with use_custom_builtins( + os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase + ), chdir_manager("tmp"): self.run_case_inner(testcase) def run_case_inner(self, testcase: DataDrivenTestCase) -> None: @@ -173,12 +172,10 @@ def run_case_inner(self, testcase: DataDrivenTestCase) -> None: # new by distutils, shift the mtime of all of the # generated artifacts back by a second. fudge_dir_mtimes(WORKDIR, -1) - # On Ubuntu, changing the mtime doesn't work reliably. As + # On some OS, changing the mtime doesn't work reliably. As # a workaround, sleep. - # # TODO: Figure out a better approach, since this slows down tests. - if sys.platform == "linux": - time.sleep(1.0) + time.sleep(1.0) step += 1 with chdir_manager(".."): @@ -197,7 +194,6 @@ def run_case_step(self, testcase: DataDrivenTestCase, incremental_step: int) -> options.preserve_asts = True options.allow_empty_bodies = True options.incremental = self.separate - options.enable_incomplete_feature = [TYPE_VAR_TUPLE, UNPACK] # Avoid checking modules/packages named 'unchecked', to provide a way # to test interacting with code we don't have types for. @@ -242,7 +238,7 @@ def run_case_step(self, testcase: DataDrivenTestCase, incremental_step: int) -> groups=groups, alt_lib_path=".", ) - errors = Errors() + errors = Errors(options) ir, cfiles = emitmodule.compile_modules_to_c( result, compiler_options=compiler_options, errors=errors, groups=groups ) diff --git a/mypyc/test/test_struct.py b/mypyc/test/test_struct.py index 2b0298cadeda..82990e6afd82 100644 --- a/mypyc/test/test_struct.py +++ b/mypyc/test/test_struct.py @@ -55,8 +55,8 @@ def test_struct_str(self) -> None: "b:}>" ) r1 = RStruct("Bar", ["c"], [int32_rprimitive]) - assert str(r1) == "Bar{c:int32}" - assert repr(r1) == "}>" + assert str(r1) == "Bar{c:i32}" + assert repr(r1) == "}>" r2 = RStruct("Baz", [], []) assert str(r2) == "Baz{}" assert repr(r2) == "" diff --git a/mypyc/test/test_typeops.py b/mypyc/test/test_typeops.py index f414edd1a2bb..ff2c05ad983e 100644 --- a/mypyc/test/test_typeops.py +++ b/mypyc/test/test_typeops.py @@ -8,6 +8,7 @@ RUnion, bit_rprimitive, bool_rprimitive, + int16_rprimitive, int32_rprimitive, int64_rprimitive, int_rprimitive, @@ -18,31 +19,44 @@ from mypyc.rt_subtype import is_runtime_subtype from mypyc.subtype import is_subtype +native_int_types = [int64_rprimitive, int32_rprimitive, int16_rprimitive] + class TestSubtype(unittest.TestCase): def test_bit(self) -> None: assert is_subtype(bit_rprimitive, bool_rprimitive) assert is_subtype(bit_rprimitive, int_rprimitive) assert is_subtype(bit_rprimitive, short_int_rprimitive) - assert is_subtype(bit_rprimitive, int64_rprimitive) - assert is_subtype(bit_rprimitive, int32_rprimitive) + for rt in native_int_types: + assert is_subtype(bit_rprimitive, rt) def test_bool(self) -> None: assert not is_subtype(bool_rprimitive, bit_rprimitive) assert is_subtype(bool_rprimitive, int_rprimitive) assert is_subtype(bool_rprimitive, short_int_rprimitive) - assert is_subtype(bool_rprimitive, int64_rprimitive) - assert is_subtype(bool_rprimitive, int32_rprimitive) + for rt in native_int_types: + assert is_subtype(bool_rprimitive, rt) def test_int64(self) -> None: + assert is_subtype(int64_rprimitive, int64_rprimitive) assert is_subtype(int64_rprimitive, int_rprimitive) assert not is_subtype(int64_rprimitive, short_int_rprimitive) assert not is_subtype(int64_rprimitive, int32_rprimitive) + assert not is_subtype(int64_rprimitive, int16_rprimitive) def test_int32(self) -> None: + assert is_subtype(int32_rprimitive, int32_rprimitive) assert is_subtype(int32_rprimitive, int_rprimitive) assert not is_subtype(int32_rprimitive, short_int_rprimitive) assert not is_subtype(int32_rprimitive, int64_rprimitive) + assert not is_subtype(int32_rprimitive, int16_rprimitive) + + def test_int16(self) -> None: + assert is_subtype(int16_rprimitive, int16_rprimitive) + assert is_subtype(int16_rprimitive, int_rprimitive) + assert not is_subtype(int16_rprimitive, short_int_rprimitive) + assert not is_subtype(int16_rprimitive, int64_rprimitive) + assert not is_subtype(int16_rprimitive, int32_rprimitive) class TestRuntimeSubtype(unittest.TestCase): @@ -54,6 +68,13 @@ def test_bool(self) -> None: assert not is_runtime_subtype(bool_rprimitive, bit_rprimitive) assert not is_runtime_subtype(bool_rprimitive, int_rprimitive) + def test_union(self) -> None: + bool_int_mix = RUnion([bool_rprimitive, int_rprimitive]) + assert not is_runtime_subtype(bool_int_mix, short_int_rprimitive) + assert not is_runtime_subtype(bool_int_mix, int_rprimitive) + assert not is_runtime_subtype(short_int_rprimitive, bool_int_mix) + assert not is_runtime_subtype(int_rprimitive, bool_int_mix) + class TestUnionSimplification(unittest.TestCase): def test_simple_type_result(self) -> None: diff --git a/mypyc/test/testutil.py b/mypyc/test/testutil.py index 609ffc27385e..6446af3427af 100644 --- a/mypyc/test/testutil.py +++ b/mypyc/test/testutil.py @@ -102,7 +102,7 @@ def build_ir_for_single_file2( # By default generate IR compatible with the earliest supported Python C API. # If a test needs more recent API features, this should be overridden. - compiler_options = compiler_options or CompilerOptions(capi_version=(3, 5)) + compiler_options = compiler_options or CompilerOptions(capi_version=(3, 7)) options = Options() options.show_traceback = True options.hide_error_codes = True @@ -121,7 +121,7 @@ def build_ir_for_single_file2( if result.errors: raise CompileError(result.errors) - errors = Errors() + errors = Errors(options) modules = build_ir( [result.files["__main__"]], result.graph, @@ -272,7 +272,7 @@ def infer_ir_build_options_from_test_name(name: str) -> CompilerOptions | None: return None if "_32bit" in name and not IS_32_BIT_PLATFORM: return None - options = CompilerOptions(strip_asserts="StripAssert" in name, capi_version=(3, 5)) + options = CompilerOptions(strip_asserts="StripAssert" in name, capi_version=(3, 7)) # A suffix like _python3.8 is used to set the target C API version. m = re.search(r"_python([3-9]+)_([0-9]+)(_|\b)", name) if m: diff --git a/mypyc/transform/copy_propagation.py b/mypyc/transform/copy_propagation.py new file mode 100644 index 000000000000..49de616f85a3 --- /dev/null +++ b/mypyc/transform/copy_propagation.py @@ -0,0 +1,94 @@ +"""Simple copy propagation optimization. + +Example input: + + x = f() + y = x + +The register x is redundant and we can directly assign its value to y: + + y = f() + +This can optimize away registers that are assigned to once. +""" + +from __future__ import annotations + +from mypyc.ir.func_ir import FuncIR +from mypyc.ir.ops import Assign, AssignMulti, LoadAddress, LoadErrorValue, Register, Value +from mypyc.irbuild.ll_builder import LowLevelIRBuilder +from mypyc.options import CompilerOptions +from mypyc.sametype import is_same_type +from mypyc.transform.ir_transform import IRTransform + + +def do_copy_propagation(fn: FuncIR, options: CompilerOptions) -> None: + """Perform copy propagation optimization for fn.""" + + # Anything with an assignment count >1 will not be optimized + # here, as it would be require data flow analysis and we want to + # keep this simple and fast, at least until we've made data flow + # analysis much faster. + counts: dict[Value, int] = {} + replacements: dict[Value, Value] = {} + for arg in fn.arg_regs: + # Arguments are always assigned to initially + counts[arg] = 1 + + for block in fn.blocks: + for op in block.ops: + if isinstance(op, Assign): + c = counts.get(op.dest, 0) + counts[op.dest] = c + 1 + # Does this look like a supported assignment? + # TODO: Something needs LoadErrorValue assignments to be preserved? + if ( + c == 0 + and is_same_type(op.dest.type, op.src.type) + and not isinstance(op.src, LoadErrorValue) + ): + replacements[op.dest] = op.src + elif c == 1: + # Too many assignments -- don't replace this one + replacements.pop(op.dest, 0) + elif isinstance(op, AssignMulti): + # Copy propagation not supported for AssignMulti destinations + counts[op.dest] = 2 + replacements.pop(op.dest, 0) + elif isinstance(op, LoadAddress): + # We don't support taking the address of an arbitrary Value, + # so we'll need to preserve the operands of LoadAddress. + if isinstance(op.src, Register): + counts[op.src] = 2 + replacements.pop(op.src, 0) + + # Follow chains of propagation with more than one assignment. + for src, dst in list(replacements.items()): + if counts.get(dst, 0) > 1: + # Not supported + del replacements[src] + else: + while dst in replacements: + dst = replacements[dst] + if counts.get(dst, 0) > 1: + # Not supported + del replacements[src] + if src in replacements: + replacements[src] = dst + + builder = LowLevelIRBuilder(None, options) + transform = CopyPropagationTransform(builder, replacements) + transform.transform_blocks(fn.blocks) + fn.blocks = builder.blocks + + +class CopyPropagationTransform(IRTransform): + def __init__(self, builder: LowLevelIRBuilder, map: dict[Value, Value]) -> None: + super().__init__(builder) + self.op_map.update(map) + self.removed = set(map) + + def visit_assign(self, op: Assign) -> Value | None: + if op.dest in self.removed: + return None + return self.add(op) diff --git a/mypyc/transform/exceptions.py b/mypyc/transform/exceptions.py index 2851955ff38f..33dfeb693cf7 100644 --- a/mypyc/transform/exceptions.py +++ b/mypyc/transform/exceptions.py @@ -23,6 +23,7 @@ Branch, CallC, ComparisonOp, + Float, GetAttr, Integer, LoadErrorValue, @@ -33,7 +34,7 @@ TupleGet, Value, ) -from mypyc.ir.rtypes import RTuple, bool_rprimitive +from mypyc.ir.rtypes import RTuple, bool_rprimitive, is_float_rprimitive from mypyc.primitives.exc_ops import err_occurred_op from mypyc.primitives.registry import CFunctionDescription @@ -42,18 +43,16 @@ def insert_exception_handling(ir: FuncIR) -> None: # Generate error block if any ops may raise an exception. If an op # fails without its own error handler, we'll branch to this # block. The block just returns an error value. - error_label = None + error_label: BasicBlock | None = None for block in ir.blocks: adjust_error_kinds(block) - can_raise = any(op.can_raise() for op in block.ops) - if can_raise: - error_label = add_handler_block(ir) - break + if error_label is None and any(op.can_raise() for op in block.ops): + error_label = add_default_handler_block(ir) if error_label: ir.blocks = split_blocks_at_errors(ir.blocks, error_label, ir.traceback_name) -def add_handler_block(ir: FuncIR) -> BasicBlock: +def add_default_handler_block(ir: FuncIR) -> BasicBlock: block = BasicBlock() ir.blocks.append(block) op = LoadErrorValue(ir.ret_type) @@ -173,7 +172,11 @@ def insert_overlapping_error_value_check(ops: list[Op], target: Value) -> Compar ops.append(item) return insert_overlapping_error_value_check(ops, item) else: - errvalue = Integer(int(typ.c_undefined), rtype=typ) + errvalue: Value + if is_float_rprimitive(target.type): + errvalue = Float(float(typ.c_undefined)) + else: + errvalue = Integer(int(typ.c_undefined), rtype=typ) op = ComparisonOp(target, errvalue, ComparisonOp.EQ) ops.append(op) return op diff --git a/mypyc/transform/flag_elimination.py b/mypyc/transform/flag_elimination.py new file mode 100644 index 000000000000..605e5bc46ae4 --- /dev/null +++ b/mypyc/transform/flag_elimination.py @@ -0,0 +1,108 @@ +"""Bool register elimination optimization. + +Example input: + + L1: + r0 = f() + b = r0 + goto L3 + L2: + r1 = g() + b = r1 + goto L3 + L3: + if b goto L4 else goto L5 + +The register b is redundant and we replace the assignments with two copies of +the branch in L3: + + L1: + r0 = f() + if r0 goto L4 else goto L5 + L2: + r1 = g() + if r1 goto L4 else goto L5 + +This helps generate simpler IR for tagged integers comparisons, for example. +""" + +from __future__ import annotations + +from mypyc.ir.func_ir import FuncIR +from mypyc.ir.ops import Assign, BasicBlock, Branch, Goto, Register, Unreachable +from mypyc.irbuild.ll_builder import LowLevelIRBuilder +from mypyc.options import CompilerOptions +from mypyc.transform.ir_transform import IRTransform + + +def do_flag_elimination(fn: FuncIR, options: CompilerOptions) -> None: + # Find registers that are used exactly once as source, and in a branch. + counts: dict[Register, int] = {} + branches: dict[Register, Branch] = {} + labels: dict[Register, BasicBlock] = {} + for block in fn.blocks: + for i, op in enumerate(block.ops): + for src in op.sources(): + if isinstance(src, Register): + counts[src] = counts.get(src, 0) + 1 + if i == 0 and isinstance(op, Branch) and isinstance(op.value, Register): + branches[op.value] = op + labels[op.value] = block + + # Based on these we can find the candidate registers. + candidates: set[Register] = { + r for r in branches if counts.get(r, 0) == 1 and r not in fn.arg_regs + } + + # Remove candidates with invalid assignments. + for block in fn.blocks: + for i, op in enumerate(block.ops): + if isinstance(op, Assign) and op.dest in candidates: + next_op = block.ops[i + 1] + if not (isinstance(next_op, Goto) and next_op.label is labels[op.dest]): + # Not right + candidates.remove(op.dest) + + builder = LowLevelIRBuilder(None, options) + transform = FlagEliminationTransform( + builder, {x: y for x, y in branches.items() if x in candidates} + ) + transform.transform_blocks(fn.blocks) + fn.blocks = builder.blocks + + +class FlagEliminationTransform(IRTransform): + def __init__(self, builder: LowLevelIRBuilder, branch_map: dict[Register, Branch]) -> None: + super().__init__(builder) + self.branch_map = branch_map + self.branches = set(branch_map.values()) + + def visit_assign(self, op: Assign) -> None: + old_branch = self.branch_map.get(op.dest) + if old_branch: + # Replace assignment with a copy of the old branch, which is in a + # separate basic block. The old branch will be deletecd in visit_branch. + new_branch = Branch( + op.src, + old_branch.true, + old_branch.false, + old_branch.op, + old_branch.line, + rare=old_branch.rare, + ) + new_branch.negated = old_branch.negated + new_branch.traceback_entry = old_branch.traceback_entry + self.add(new_branch) + else: + self.add(op) + + def visit_goto(self, op: Goto) -> None: + # This is a no-op if basic block already terminated + self.builder.goto(op.label) + + def visit_branch(self, op: Branch) -> None: + if op in self.branches: + # This branch is optimized away + self.add(Unreachable()) + else: + self.add(op) diff --git a/mypyc/transform/ir_transform.py b/mypyc/transform/ir_transform.py new file mode 100644 index 000000000000..a631bd7352b5 --- /dev/null +++ b/mypyc/transform/ir_transform.py @@ -0,0 +1,368 @@ +"""Helpers for implementing generic IR to IR transforms.""" + +from __future__ import annotations + +from typing import Final, Optional + +from mypyc.ir.ops import ( + Assign, + AssignMulti, + BasicBlock, + Box, + Branch, + Call, + CallC, + Cast, + ComparisonOp, + DecRef, + Extend, + FloatComparisonOp, + FloatNeg, + FloatOp, + GetAttr, + GetElementPtr, + Goto, + IncRef, + InitStatic, + IntOp, + KeepAlive, + LoadAddress, + LoadErrorValue, + LoadGlobal, + LoadLiteral, + LoadMem, + LoadStatic, + MethodCall, + Op, + OpVisitor, + PrimitiveOp, + RaiseStandardError, + Return, + SetAttr, + SetMem, + Truncate, + TupleGet, + TupleSet, + Unborrow, + Unbox, + Unreachable, + Value, +) +from mypyc.irbuild.ll_builder import LowLevelIRBuilder + + +class IRTransform(OpVisitor[Optional[Value]]): + """Identity transform. + + Subclass and override to perform changes to IR. + + Subclass IRTransform and override any OpVisitor visit_* methods + that perform any IR changes. The default implementations implement + an identity transform. + + A visit method can return None to remove ops. In this case the + transform must ensure that no op uses the original removed op + as a source after the transform. + + You can retain old BasicBlock and op references in ops. The transform + will automatically patch these for you as needed. + """ + + def __init__(self, builder: LowLevelIRBuilder) -> None: + self.builder = builder + # Subclasses add additional op mappings here. A None value indicates + # that the op/register is deleted. + self.op_map: dict[Value, Value | None] = {} + + def transform_blocks(self, blocks: list[BasicBlock]) -> None: + """Transform basic blocks that represent a single function. + + The result of the transform will be collected at self.builder.blocks. + """ + block_map: dict[BasicBlock, BasicBlock] = {} + op_map = self.op_map + empties = set() + for block in blocks: + new_block = BasicBlock() + block_map[block] = new_block + self.builder.activate_block(new_block) + new_block.error_handler = block.error_handler + for op in block.ops: + new_op = op.accept(self) + if new_op is not op: + op_map[op] = new_op + # A transform can produce empty blocks which can be removed. + if is_empty_block(new_block) and not is_empty_block(block): + empties.add(new_block) + self.builder.blocks = [block for block in self.builder.blocks if block not in empties] + # Update all op/block references to point to the transformed ones. + patcher = PatchVisitor(op_map, block_map) + for block in self.builder.blocks: + for op in block.ops: + op.accept(patcher) + if block.error_handler is not None: + block.error_handler = block_map.get(block.error_handler, block.error_handler) + + def add(self, op: Op) -> Value: + return self.builder.add(op) + + def visit_goto(self, op: Goto) -> None: + self.add(op) + + def visit_branch(self, op: Branch) -> None: + self.add(op) + + def visit_return(self, op: Return) -> None: + self.add(op) + + def visit_unreachable(self, op: Unreachable) -> None: + self.add(op) + + def visit_assign(self, op: Assign) -> Value | None: + return self.add(op) + + def visit_assign_multi(self, op: AssignMulti) -> Value | None: + return self.add(op) + + def visit_load_error_value(self, op: LoadErrorValue) -> Value | None: + return self.add(op) + + def visit_load_literal(self, op: LoadLiteral) -> Value | None: + return self.add(op) + + def visit_get_attr(self, op: GetAttr) -> Value | None: + return self.add(op) + + def visit_set_attr(self, op: SetAttr) -> Value | None: + return self.add(op) + + def visit_load_static(self, op: LoadStatic) -> Value | None: + return self.add(op) + + def visit_init_static(self, op: InitStatic) -> Value | None: + return self.add(op) + + def visit_tuple_get(self, op: TupleGet) -> Value | None: + return self.add(op) + + def visit_tuple_set(self, op: TupleSet) -> Value | None: + return self.add(op) + + def visit_inc_ref(self, op: IncRef) -> Value | None: + return self.add(op) + + def visit_dec_ref(self, op: DecRef) -> Value | None: + return self.add(op) + + def visit_call(self, op: Call) -> Value | None: + return self.add(op) + + def visit_method_call(self, op: MethodCall) -> Value | None: + return self.add(op) + + def visit_cast(self, op: Cast) -> Value | None: + return self.add(op) + + def visit_box(self, op: Box) -> Value | None: + return self.add(op) + + def visit_unbox(self, op: Unbox) -> Value | None: + return self.add(op) + + def visit_raise_standard_error(self, op: RaiseStandardError) -> Value | None: + return self.add(op) + + def visit_call_c(self, op: CallC) -> Value | None: + return self.add(op) + + def visit_primitive_op(self, op: PrimitiveOp) -> Value | None: + return self.add(op) + + def visit_truncate(self, op: Truncate) -> Value | None: + return self.add(op) + + def visit_extend(self, op: Extend) -> Value | None: + return self.add(op) + + def visit_load_global(self, op: LoadGlobal) -> Value | None: + return self.add(op) + + def visit_int_op(self, op: IntOp) -> Value | None: + return self.add(op) + + def visit_comparison_op(self, op: ComparisonOp) -> Value | None: + return self.add(op) + + def visit_float_op(self, op: FloatOp) -> Value | None: + return self.add(op) + + def visit_float_neg(self, op: FloatNeg) -> Value | None: + return self.add(op) + + def visit_float_comparison_op(self, op: FloatComparisonOp) -> Value | None: + return self.add(op) + + def visit_load_mem(self, op: LoadMem) -> Value | None: + return self.add(op) + + def visit_set_mem(self, op: SetMem) -> Value | None: + return self.add(op) + + def visit_get_element_ptr(self, op: GetElementPtr) -> Value | None: + return self.add(op) + + def visit_load_address(self, op: LoadAddress) -> Value | None: + return self.add(op) + + def visit_keep_alive(self, op: KeepAlive) -> Value | None: + return self.add(op) + + def visit_unborrow(self, op: Unborrow) -> Value | None: + return self.add(op) + + +class PatchVisitor(OpVisitor[None]): + def __init__( + self, op_map: dict[Value, Value | None], block_map: dict[BasicBlock, BasicBlock] + ) -> None: + self.op_map: Final = op_map + self.block_map: Final = block_map + + def fix_op(self, op: Value) -> Value: + new = self.op_map.get(op, op) + assert new is not None, "use of removed op" + return new + + def fix_block(self, block: BasicBlock) -> BasicBlock: + return self.block_map.get(block, block) + + def visit_goto(self, op: Goto) -> None: + op.label = self.fix_block(op.label) + + def visit_branch(self, op: Branch) -> None: + op.value = self.fix_op(op.value) + op.true = self.fix_block(op.true) + op.false = self.fix_block(op.false) + + def visit_return(self, op: Return) -> None: + op.value = self.fix_op(op.value) + + def visit_unreachable(self, op: Unreachable) -> None: + pass + + def visit_assign(self, op: Assign) -> None: + op.src = self.fix_op(op.src) + + def visit_assign_multi(self, op: AssignMulti) -> None: + op.src = [self.fix_op(s) for s in op.src] + + def visit_load_error_value(self, op: LoadErrorValue) -> None: + pass + + def visit_load_literal(self, op: LoadLiteral) -> None: + pass + + def visit_get_attr(self, op: GetAttr) -> None: + op.obj = self.fix_op(op.obj) + + def visit_set_attr(self, op: SetAttr) -> None: + op.obj = self.fix_op(op.obj) + op.src = self.fix_op(op.src) + + def visit_load_static(self, op: LoadStatic) -> None: + pass + + def visit_init_static(self, op: InitStatic) -> None: + op.value = self.fix_op(op.value) + + def visit_tuple_get(self, op: TupleGet) -> None: + op.src = self.fix_op(op.src) + + def visit_tuple_set(self, op: TupleSet) -> None: + op.items = [self.fix_op(item) for item in op.items] + + def visit_inc_ref(self, op: IncRef) -> None: + op.src = self.fix_op(op.src) + + def visit_dec_ref(self, op: DecRef) -> None: + op.src = self.fix_op(op.src) + + def visit_call(self, op: Call) -> None: + op.args = [self.fix_op(arg) for arg in op.args] + + def visit_method_call(self, op: MethodCall) -> None: + op.obj = self.fix_op(op.obj) + op.args = [self.fix_op(arg) for arg in op.args] + + def visit_cast(self, op: Cast) -> None: + op.src = self.fix_op(op.src) + + def visit_box(self, op: Box) -> None: + op.src = self.fix_op(op.src) + + def visit_unbox(self, op: Unbox) -> None: + op.src = self.fix_op(op.src) + + def visit_raise_standard_error(self, op: RaiseStandardError) -> None: + if isinstance(op.value, Value): + op.value = self.fix_op(op.value) + + def visit_call_c(self, op: CallC) -> None: + op.args = [self.fix_op(arg) for arg in op.args] + + def visit_primitive_op(self, op: PrimitiveOp) -> None: + op.args = [self.fix_op(arg) for arg in op.args] + + def visit_truncate(self, op: Truncate) -> None: + op.src = self.fix_op(op.src) + + def visit_extend(self, op: Extend) -> None: + op.src = self.fix_op(op.src) + + def visit_load_global(self, op: LoadGlobal) -> None: + pass + + def visit_int_op(self, op: IntOp) -> None: + op.lhs = self.fix_op(op.lhs) + op.rhs = self.fix_op(op.rhs) + + def visit_comparison_op(self, op: ComparisonOp) -> None: + op.lhs = self.fix_op(op.lhs) + op.rhs = self.fix_op(op.rhs) + + def visit_float_op(self, op: FloatOp) -> None: + op.lhs = self.fix_op(op.lhs) + op.rhs = self.fix_op(op.rhs) + + def visit_float_neg(self, op: FloatNeg) -> None: + op.src = self.fix_op(op.src) + + def visit_float_comparison_op(self, op: FloatComparisonOp) -> None: + op.lhs = self.fix_op(op.lhs) + op.rhs = self.fix_op(op.rhs) + + def visit_load_mem(self, op: LoadMem) -> None: + op.src = self.fix_op(op.src) + + def visit_set_mem(self, op: SetMem) -> None: + op.dest = self.fix_op(op.dest) + op.src = self.fix_op(op.src) + + def visit_get_element_ptr(self, op: GetElementPtr) -> None: + op.src = self.fix_op(op.src) + + def visit_load_address(self, op: LoadAddress) -> None: + if isinstance(op.src, LoadStatic): + new = self.fix_op(op.src) + assert isinstance(new, LoadStatic) + op.src = new + + def visit_keep_alive(self, op: KeepAlive) -> None: + op.src = [self.fix_op(s) for s in op.src] + + def visit_unborrow(self, op: Unborrow) -> None: + op.src = self.fix_op(op.src) + + +def is_empty_block(block: BasicBlock) -> bool: + return len(block.ops) == 1 and isinstance(block.ops[0], Unreachable) diff --git a/mypyc/transform/lower.py b/mypyc/transform/lower.py new file mode 100644 index 000000000000..b717657095f9 --- /dev/null +++ b/mypyc/transform/lower.py @@ -0,0 +1,33 @@ +"""Transform IR to lower-level ops. + +Higher-level ops are used in earlier compiler passes, as they make +various analyses, optimizations and transforms easier to implement. +Later passes use lower-level ops, as they are easier to generate code +from, and they help with lower-level optimizations. + +Lowering of various primitive ops is implemented in the mypyc.lower +package. +""" + +from mypyc.ir.func_ir import FuncIR +from mypyc.ir.ops import PrimitiveOp, Value +from mypyc.irbuild.ll_builder import LowLevelIRBuilder +from mypyc.lower.registry import lowering_registry +from mypyc.options import CompilerOptions +from mypyc.transform.ir_transform import IRTransform + + +def lower_ir(ir: FuncIR, options: CompilerOptions) -> None: + builder = LowLevelIRBuilder(None, options) + visitor = LoweringVisitor(builder) + visitor.transform_blocks(ir.blocks) + ir.blocks = builder.blocks + + +class LoweringVisitor(IRTransform): + def visit_primitive_op(self, op: PrimitiveOp) -> Value: + # The lowering implementation functions of various primitive ops are stored + # in a registry, which is populated using function decorators. The name + # of op (such as "int_eq") is used as the key. + lower_fn = lowering_registry[op.desc.name] + return lower_fn(self.builder, op.args, op.line) diff --git a/mypyc/transform/refcount.py b/mypyc/transform/refcount.py index 13f6a121e7f1..f2ab438f6576 100644 --- a/mypyc/transform/refcount.py +++ b/mypyc/transform/refcount.py @@ -70,7 +70,7 @@ def insert_ref_count_opcodes(ir: FuncIR) -> None: defined = analyze_must_defined_regs(ir.blocks, cfg, args, values, strict_errors=True) ordering = make_value_ordering(ir) cache: BlockCache = {} - for block in ir.blocks[:]: + for block in ir.blocks.copy(): if isinstance(block.ops[-1], (Branch, Goto)): insert_branch_inc_and_decrefs( block, diff --git a/pyproject.toml b/pyproject.toml index 328b9bf159a1..35f1592ca83c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,20 +6,18 @@ requires = [ "setuptools >= 40.6.2", "wheel >= 0.30.0", # the following is from mypy-requirements.txt - "typing_extensions>=3.10", + "typing_extensions>=4.1.0", "mypy_extensions>=1.0.0", - "typed_ast>=1.4.0,<2; python_version<'3.8'", "tomli>=1.1.0; python_version<'3.11'", # the following is from build-requirements.txt "types-psutil", "types-setuptools", - "types-typed-ast>=1.5.8,<1.6.0", ] build-backend = "setuptools.build_meta" [tool.black] line-length = 99 -target-version = ["py37", "py38", "py39", "py310", "py311"] +target-version = ["py38", "py39", "py310", "py311", "py312"] skip-magic-trailing-comma = true force-exclude = ''' ^/mypy/typeshed| @@ -27,14 +25,109 @@ force-exclude = ''' ^/test-data ''' -[tool.isort] -profile = "black" -line_length = 99 -combine_as_imports = true -skip_gitignore = true -extra_standard_library = ["typing_extensions"] -skip_glob = [ - "mypy/typeshed/*", - "mypyc/test-data/*", - "test-data/*", +[tool.ruff] +line-length = 99 +target-version = "py38" +fix = true + +extend-exclude = [ + "@*", + # Sphinx configuration is irrelevant + "docs/source/conf.py", + "mypyc/doc/conf.py", + # tests have more relaxed styling requirements + # fixtures have their own .pyi-specific configuration + "test-data/*", + "mypyc/test-data/*", + # typeshed has its own .pyi-specific configuration + "mypy/typeshed/*", +] + +[tool.ruff.lint] +select = [ + "E", # pycodestyle (error) + "F", # pyflakes + "W", # pycodestyle (warning) + "B", # flake8-bugbear + "I", # isort + "RUF100", # Unused noqa comments + "PGH004", # blanket noqa comments + "UP", # pyupgrade + "C4", # flake8-comprehensions + "SIM201", "SIM202", # simplify comparisons involving not + "ISC001", # implicitly concatenated string + "RET501", "RET502", # better return None handling +] + +ignore = [ + "B007", # Loop control variable not used within the loop body. + "B011", # Don't use assert False + "B023", # Function definition does not bind loop variable + "E2", # conflicts with black + "E402", # module level import not at top of file + "E501", # conflicts with black + "E731", # Do not assign a `lambda` expression, use a `def` + "E741", # Ambiguous variable name + "UP032", # 'f-string always preferable to format' is controversial + "C416", # There are a few cases where it's nice to have names for the dict items +] + +unfixable = [ + "F841", # unused variable. ruff keeps the call, but mostly we want to get rid of it all + "F601", # automatic fix might obscure issue + "F602", # automatic fix might obscure issue + "B018", # automatic fix might obscure issue + "UP036", # sometimes it's better to just noqa this +] + +[tool.ruff.lint.isort] +combine-as-imports = true +extra-standard-library = ["typing_extensions"] + +[tool.check-manifest] +ignore = ["**/.readthedocs.yaml"] + +[tool.pytest.ini_options] +minversion = "7.0.0" +testpaths = ["mypy/test", "mypyc/test"] +python_files = 'test*.py' + +# Where do the test cases come from? We provide our own collection +# logic by implementing `pytest_pycollect_makeitem` in mypy.test.data; +# the test files import that module, and pytest sees the magic name +# and invokes it at the relevant moment. See +# https://doc.pytest.org/en/latest/how-to/writing_plugins.html#collection-hooks + +# Both our plugin and unittest provide their own collection logic, +# So we can disable the default python collector by giving it empty +# patterns to search for. +# Note that unittest requires that no "Test*" classes exist. +python_classes = [] +python_functions = [] + +# always run in parallel (requires pytest-xdist, see test-requirements.txt) +# and enable strict mode: require all markers +# to be defined and raise on invalid config values +addopts = "-nauto --strict-markers --strict-config" + +# treat xpasses as test failures so they get converted to regular tests as soon as possible +xfail_strict = true + +[tool.coverage.run] +branch = true +source = ["mypy"] +parallel = true + +[tool.coverage.report] +show_missing = true +skip_covered = true +omit = ['mypy/test/*'] +exclude_lines = [ + '\#\s*pragma: no cover', + '^\s*raise AssertionError\b', + '^\s*raise NotImplementedError\b', + '^\s*return NotImplemented\b', + '^\s*raise$', + '^assert False\b', + '''^if __name__ == ['"]__main__['"]:$''', ] diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index b164c14b6414..000000000000 --- a/pytest.ini +++ /dev/null @@ -1,27 +0,0 @@ -[pytest] -minversion = 6.0.0 - -testpaths = mypy/test mypyc/test - -python_files = test*.py - -# Where do the test cases come from? We provide our own collection -# logic by implementing `pytest_pycollect_makeitem` in mypy.test.data; -# the test files import that module, and pytest sees the magic name -# and invokes it at the relevant moment. See -# http://doc.pytest.org/en/latest/writing_plugins.html#collection-hooks - -# Both our plugin and unittest provide their own collection logic, -# So we can disable the default python collector by giving it empty -# patterns to search for. -# Note that unittest requires that no "Test*" classes exist. -python_classes = -python_functions = - -# always run in parallel (requires pytest-xdist, see test-requirements.txt) -# and enable strict mode: require all markers -# to be defined and raise on invalid config values -addopts = -nauto --strict-markers --strict-config - -# treat xpasses as test failures so they get converted to regular tests as soon as possible -xfail_strict = true diff --git a/runtests.py b/runtests.py index ade0a8adee5e..80ef8d814ee1 100755 --- a/runtests.py +++ b/runtests.py @@ -48,11 +48,19 @@ # time to run. cmds = { # Self type check - "self": [executable, "-m", "mypy", "--config-file", "mypy_self_check.ini", "-p", "mypy"], + "self": [ + executable, + "-m", + "mypy", + "--config-file", + "mypy_self_check.ini", + "-p", + "mypy", + "-p", + "mypyc", + ], # Lint - "lint": ["flake8", "-j3"], - "format-black": ["black", "."], - "format-isort": ["isort", "."], + "lint": ["pre-commit", "run", "--all-files"], # Fast test cases only (this is the bulk of the test suite) "pytest-fast": ["pytest", "-q", "-k", f"not ({' or '.join(ALL_NON_FAST)})"], # Test cases that invoke mypy (with small inputs) @@ -129,7 +137,7 @@ def main() -> None: exit(1) if not args: - args = DEFAULT_COMMANDS[:] + args = DEFAULT_COMMANDS.copy() status = 0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 511f794474e7..000000000000 --- a/setup.cfg +++ /dev/null @@ -1,59 +0,0 @@ -[flake8] -max-line-length = 99 -noqa-require-code = True -# typeshed and unit test fixtures have .pyi-specific flake8 configuration -exclude = - # from .gitignore: directories, and file patterns that intersect with *.py - build, - bin, - lib, - include, - @*, - env, - docs/build, - out, - .venv, - .mypy_cache, - .git, - .cache, - # Sphinx configuration is irrelevant - docs/source/conf.py, - mypyc/doc/conf.py, - # tests have more relaxed styling requirements - # fixtures have their own .pyi-specific configuration - test-data/*, - mypyc/test-data/*, - # typeshed has its own .pyi-specific configuration - mypy/typeshed/*, - .tox - .eggs - .Python - -# Things to ignore: -# E203: conflicts with black -# E501: conflicts with black -# W601: has_key() deprecated (false positives) -# E402: module level import not at top of file -# B006: use of mutable defaults in function signatures -# B007: Loop control variable not used within the loop body. -# B011: Don't use assert False -# B023: Function definition does not bind loop variable -# E741: Ambiguous variable name -extend-ignore = E203,E501,W601,E402,B006,B007,B011,B023,E741 - -[coverage:run] -branch = true -source = mypy -parallel = true - -[coverage:report] -show_missing = true -skip_covered = True -omit = mypy/test/* -exclude_lines = - \#\s*pragma: no cover - ^\s*raise AssertionError\b - ^\s*raise NotImplementedError\b - ^\s*return NotImplemented\b - ^\s*raise$ - ^if __name__ == ['"]__main__['"]:$ diff --git a/setup.py b/setup.py index 516a639f3bb2..a17ee562eb39 100644 --- a/setup.py +++ b/setup.py @@ -6,9 +6,10 @@ import os import os.path import sys +from typing import TYPE_CHECKING, Any -if sys.version_info < (3, 7, 0): - sys.stderr.write("ERROR: You need Python 3.7 or later to use mypy.\n") +if sys.version_info < (3, 8, 0): # noqa: UP036 + sys.stderr.write("ERROR: You need Python 3.8 or later to use mypy.\n") exit(1) # we'll import stuff from the source tree, let's ensure is on the sys path @@ -17,11 +18,14 @@ # This requires setuptools when building; setuptools is not needed # when installing from a wheel file (though it is still needed for # alternative forms of installing, as suggested by README.md). -from setuptools import find_packages, setup +from setuptools import Extension, find_packages, setup from setuptools.command.build_py import build_py from mypy.version import __version__ as version +if TYPE_CHECKING: + from typing_extensions import TypeGuard + description = "Optional static typing for Python" long_description = """ Mypy -- Optional Static Typing for Python @@ -36,6 +40,10 @@ """.lstrip() +def is_list_of_setuptools_extension(items: list[Any]) -> TypeGuard[list[Extension]]: + return all(isinstance(item, Extension) for item in items) + + def find_package_data(base, globs, root="mypy"): """Find all interesting data files, for setup(package_data=) @@ -104,7 +112,6 @@ def run(self): "stubtest.py", "stubgenc.py", "stubdoc.py", - "stubutil.py", ) ) + ( # Don't want to grab this accidentally @@ -166,6 +173,8 @@ def run(self): # our Appveyor builds run out of memory sometimes. multi_file=sys.platform == "win32" or force_multifile, ) + assert is_list_of_setuptools_extension(ext_modules), "Expected mypycify to use setuptools" + else: ext_modules = [] @@ -176,11 +185,11 @@ def run(self): "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Software Development", "Typing :: Typed", ] @@ -192,8 +201,8 @@ def run(self): long_description=long_description, author="Jukka Lehtosalo", author_email="jukka.lehtosalo@iki.fi", - url="http://www.mypy-lang.org/", - license="MIT License", + url="https://www.mypy-lang.org/", + license="MIT", py_modules=[], ext_modules=ext_modules, packages=find_packages(), @@ -211,23 +220,24 @@ def run(self): cmdclass=cmdclass, # When changing this, also update mypy-requirements.txt. install_requires=[ - "typed_ast >= 1.4.0, < 2; python_version<'3.8'", - "typing_extensions>=3.10", + "typing_extensions>=4.1.0", "mypy_extensions >= 1.0.0", "tomli>=1.1.0; python_version<'3.11'", ], # Same here. extras_require={ "dmypy": "psutil >= 4.0", - "python2": "typed_ast >= 1.4.0, < 2", + "mypyc": "setuptools >= 50", + "python2": "", "reports": "lxml", "install-types": "pip", }, - python_requires=">=3.7", + python_requires=">=3.8", include_package_data=True, project_urls={ - "News": "http://mypy-lang.org/news.html", "Documentation": "https://mypy.readthedocs.io/en/stable/index.html", "Repository": "https://github.com/python/mypy", + "Changelog": "https://github.com/python/mypy/blob/master/CHANGELOG.md", + "Issues": "https://github.com/python/mypy/issues", }, ) diff --git a/test-data/.flake8 b/test-data/.flake8 deleted file mode 100644 index df2f9caf8c94..000000000000 --- a/test-data/.flake8 +++ /dev/null @@ -1,22 +0,0 @@ -# Some PEP8 deviations are considered irrelevant to stub files: -# (error counts as of 2016-12-19) -# 17381 E704 multiple statements on one line (def) -# 11840 E301 expected 1 blank line -# 7467 E302 expected 2 blank lines -# 1772 E501 line too long -# 1487 F401 imported but unused -# 1248 E701 multiple statements on one line (colon) -# 427 F811 redefinition -# 356 E305 expected 2 blank lines - -# Nice-to-haves ignored for now -# 152 E128 continuation line under-indented for visual indent -# 43 E127 continuation line over-indented for visual indent - -[flake8] -ignore = F401, F811, E127, E128, E301, E302, E305, E501, E701, E704, B303 -# We are checking with Python 3 but many of the stubs are Python 2 stubs. -# A nice future improvement would be to provide separate .flake8 -# configurations for Python 2 and Python 3 files. -builtins = StandardError,apply,basestring,buffer,cmp,coerce,execfile,file,intern,long,raw_input,reduce,reload,unichr,unicode,xrange -exclude = .venv*,@* diff --git a/test-data/packages/modulefinder-site-packages/foo-stubs/bar.pyi b/test-data/packages/modulefinder-site-packages/foo-stubs/bar.pyi index bf896e8cdfa3..833a52007f57 100644 --- a/test-data/packages/modulefinder-site-packages/foo-stubs/bar.pyi +++ b/test-data/packages/modulefinder-site-packages/foo-stubs/bar.pyi @@ -1 +1 @@ -bar_var: str \ No newline at end of file +bar_var: str diff --git a/test-data/packages/modulefinder-site-packages/foo/bar.py b/test-data/packages/modulefinder-site-packages/foo/bar.py index a1c3b50eeeab..7782aba46492 100644 --- a/test-data/packages/modulefinder-site-packages/foo/bar.py +++ b/test-data/packages/modulefinder-site-packages/foo/bar.py @@ -1 +1 @@ -bar_var = "bar" \ No newline at end of file +bar_var = "bar" diff --git a/test-data/packages/modulefinder-site-packages/ns_pkg_typed/a.py b/test-data/packages/modulefinder-site-packages/ns_pkg_typed/a.py index 9d71311c4d82..c0cca79b8552 100644 --- a/test-data/packages/modulefinder-site-packages/ns_pkg_typed/a.py +++ b/test-data/packages/modulefinder-site-packages/ns_pkg_typed/a.py @@ -1 +1 @@ -a_var = "a" \ No newline at end of file +a_var = "a" diff --git a/test-data/packages/modulefinder-site-packages/ns_pkg_typed/b/c.py b/test-data/packages/modulefinder-site-packages/ns_pkg_typed/b/c.py index 003a29a2ef67..0ed729e24e43 100644 --- a/test-data/packages/modulefinder-site-packages/ns_pkg_typed/b/c.py +++ b/test-data/packages/modulefinder-site-packages/ns_pkg_typed/b/c.py @@ -1 +1 @@ -c_var = "c" \ No newline at end of file +c_var = "c" diff --git a/test-data/packages/modulefinder-site-packages/ns_pkg_untyped/a.py b/test-data/packages/modulefinder-site-packages/ns_pkg_untyped/a.py index 9d71311c4d82..c0cca79b8552 100644 --- a/test-data/packages/modulefinder-site-packages/ns_pkg_untyped/a.py +++ b/test-data/packages/modulefinder-site-packages/ns_pkg_untyped/a.py @@ -1 +1 @@ -a_var = "a" \ No newline at end of file +a_var = "a" diff --git a/test-data/packages/modulefinder-site-packages/ns_pkg_untyped/b/c.py b/test-data/packages/modulefinder-site-packages/ns_pkg_untyped/b/c.py index 003a29a2ef67..0ed729e24e43 100644 --- a/test-data/packages/modulefinder-site-packages/ns_pkg_untyped/b/c.py +++ b/test-data/packages/modulefinder-site-packages/ns_pkg_untyped/b/c.py @@ -1 +1 @@ -c_var = "c" \ No newline at end of file +c_var = "c" diff --git a/test-data/packages/modulefinder-site-packages/pkg_typed/__init__.py b/test-data/packages/modulefinder-site-packages/pkg_typed/__init__.py index 88ed99fb525e..f49ab244c27e 100644 --- a/test-data/packages/modulefinder-site-packages/pkg_typed/__init__.py +++ b/test-data/packages/modulefinder-site-packages/pkg_typed/__init__.py @@ -1 +1 @@ -pkg_typed_var = "pkg_typed" \ No newline at end of file +pkg_typed_var = "pkg_typed" diff --git a/test-data/packages/modulefinder-site-packages/pkg_typed/a.py b/test-data/packages/modulefinder-site-packages/pkg_typed/a.py index 9d71311c4d82..c0cca79b8552 100644 --- a/test-data/packages/modulefinder-site-packages/pkg_typed/a.py +++ b/test-data/packages/modulefinder-site-packages/pkg_typed/a.py @@ -1 +1 @@ -a_var = "a" \ No newline at end of file +a_var = "a" diff --git a/test-data/packages/modulefinder-site-packages/pkg_typed/b/__init__.py b/test-data/packages/modulefinder-site-packages/pkg_typed/b/__init__.py index de0052886c57..6cea6ed4292a 100644 --- a/test-data/packages/modulefinder-site-packages/pkg_typed/b/__init__.py +++ b/test-data/packages/modulefinder-site-packages/pkg_typed/b/__init__.py @@ -1 +1 @@ -b_var = "b" \ No newline at end of file +b_var = "b" diff --git a/test-data/packages/modulefinder-site-packages/pkg_typed/b/c.py b/test-data/packages/modulefinder-site-packages/pkg_typed/b/c.py index 003a29a2ef67..0ed729e24e43 100644 --- a/test-data/packages/modulefinder-site-packages/pkg_typed/b/c.py +++ b/test-data/packages/modulefinder-site-packages/pkg_typed/b/c.py @@ -1 +1 @@ -c_var = "c" \ No newline at end of file +c_var = "c" diff --git a/test-data/packages/modulefinder-site-packages/pkg_untyped/__init__.py b/test-data/packages/modulefinder-site-packages/pkg_untyped/__init__.py index c7ff39c11179..4960ea0a9555 100644 --- a/test-data/packages/modulefinder-site-packages/pkg_untyped/__init__.py +++ b/test-data/packages/modulefinder-site-packages/pkg_untyped/__init__.py @@ -1 +1 @@ -pkg_untyped_var = "pkg_untyped" \ No newline at end of file +pkg_untyped_var = "pkg_untyped" diff --git a/test-data/packages/modulefinder-site-packages/pkg_untyped/a.py b/test-data/packages/modulefinder-site-packages/pkg_untyped/a.py index 9d71311c4d82..c0cca79b8552 100644 --- a/test-data/packages/modulefinder-site-packages/pkg_untyped/a.py +++ b/test-data/packages/modulefinder-site-packages/pkg_untyped/a.py @@ -1 +1 @@ -a_var = "a" \ No newline at end of file +a_var = "a" diff --git a/test-data/packages/modulefinder-site-packages/pkg_untyped/b/__init__.py b/test-data/packages/modulefinder-site-packages/pkg_untyped/b/__init__.py index de0052886c57..6cea6ed4292a 100644 --- a/test-data/packages/modulefinder-site-packages/pkg_untyped/b/__init__.py +++ b/test-data/packages/modulefinder-site-packages/pkg_untyped/b/__init__.py @@ -1 +1 @@ -b_var = "b" \ No newline at end of file +b_var = "b" diff --git a/test-data/packages/modulefinder-site-packages/pkg_untyped/b/c.py b/test-data/packages/modulefinder-site-packages/pkg_untyped/b/c.py index 003a29a2ef67..0ed729e24e43 100644 --- a/test-data/packages/modulefinder-site-packages/pkg_untyped/b/c.py +++ b/test-data/packages/modulefinder-site-packages/pkg_untyped/b/c.py @@ -1 +1 @@ -c_var = "c" \ No newline at end of file +c_var = "c" diff --git a/test-data/packages/modulefinder-site-packages/standalone.py b/test-data/packages/modulefinder-site-packages/standalone.py index 35b38168f25e..ce436beefe85 100644 --- a/test-data/packages/modulefinder-site-packages/standalone.py +++ b/test-data/packages/modulefinder-site-packages/standalone.py @@ -1 +1 @@ -standalone_var = "standalone" \ No newline at end of file +standalone_var = "standalone" diff --git a/test-data/packages/typedpkg-stubs/pyproject.toml b/test-data/packages/typedpkg-stubs/pyproject.toml new file mode 100644 index 000000000000..c984c5d91e0a --- /dev/null +++ b/test-data/packages/typedpkg-stubs/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = 'typedpkg-stubs' +version = '0.1' +description = 'test' + +[tool.hatch.build] +include = ["**/*.pyi"] + +[build-system] +requires = ["hatchling==1.18"] +build-backend = "hatchling.build" diff --git a/test-data/packages/typedpkg-stubs/setup.py b/test-data/packages/typedpkg-stubs/setup.py deleted file mode 100644 index 4948dc6a01df..000000000000 --- a/test-data/packages/typedpkg-stubs/setup.py +++ /dev/null @@ -1,13 +0,0 @@ -""" -This setup file installs packages to test mypy's PEP 561 implementation -""" - -from setuptools import setup - -setup( - name='typedpkg-stubs', - author="The mypy team", - version='0.1', - package_data={'typedpkg-stubs': ['sample.pyi', '__init__.pyi', 'py.typed']}, - packages=['typedpkg-stubs'], -) diff --git a/test-data/packages/typedpkg/pyproject.toml b/test-data/packages/typedpkg/pyproject.toml new file mode 100644 index 000000000000..6b55d4b3df60 --- /dev/null +++ b/test-data/packages/typedpkg/pyproject.toml @@ -0,0 +1,8 @@ +[project] +name = 'typedpkg' +version = '0.1' +description = 'test' + +[build-system] +requires = ["hatchling==1.18"] +build-backend = "hatchling.build" diff --git a/test-data/packages/typedpkg/setup.py b/test-data/packages/typedpkg/setup.py deleted file mode 100644 index 11bcfb11a104..000000000000 --- a/test-data/packages/typedpkg/setup.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -This setup file installs packages to test mypy's PEP 561 implementation -""" - -from setuptools import setup - -setup( - name='typedpkg', - author="The mypy team", - version='0.1', - package_data={'typedpkg': ['py.typed']}, - packages=['typedpkg', 'typedpkg.pkg'], - include_package_data=True, - zip_safe=False, -) diff --git a/test-data/packages/typedpkg_ns_a/pyproject.toml b/test-data/packages/typedpkg_ns_a/pyproject.toml new file mode 100644 index 000000000000..f41ad16b5bc2 --- /dev/null +++ b/test-data/packages/typedpkg_ns_a/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = 'typedpkg_namespace.alpha' +version = '0.1' +description = 'test' + +[tool.hatch.build] +include = ["**/*.py", "**/*.pyi", "**/py.typed"] + +[build-system] +requires = ["hatchling==1.18"] +build-backend = "hatchling.build" diff --git a/test-data/packages/typedpkg_ns_a/setup.py b/test-data/packages/typedpkg_ns_a/setup.py deleted file mode 100644 index 3dab731cada9..000000000000 --- a/test-data/packages/typedpkg_ns_a/setup.py +++ /dev/null @@ -1,10 +0,0 @@ -from setuptools import setup - -setup( - name='typedpkg_namespace.alpha', - version='1.0.0', - namespace_packages=['typedpkg_ns'], - zip_safe=False, - package_data={'typedpkg_ns.a': ['py.typed']}, - packages=['typedpkg_ns.a'], -) diff --git a/test-data/packages/typedpkg_ns_b-stubs/pyproject.toml b/test-data/packages/typedpkg_ns_b-stubs/pyproject.toml new file mode 100644 index 000000000000..2c1c206c361d --- /dev/null +++ b/test-data/packages/typedpkg_ns_b-stubs/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = 'typedpkg_ns-stubs' +version = '0.1' +description = 'test' + +[tool.hatch.build] +include = ["**/*.pyi"] + +[build-system] +requires = ["hatchling==1.18"] +build-backend = "hatchling.build" diff --git a/test-data/packages/typedpkg_ns_b-stubs/setup.py b/test-data/packages/typedpkg_ns_b-stubs/setup.py deleted file mode 100644 index a5d7df83eeea..000000000000 --- a/test-data/packages/typedpkg_ns_b-stubs/setup.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -This setup file installs packages to test mypy's PEP 561 implementation -""" - -from distutils.core import setup - -setup( - name='typedpkg_ns_b-stubs', - author="The mypy team", - version='0.1', - namespace_packages=['typedpkg_ns-stubs'], - package_data={'typedpkg_ns-stubs.b': ['__init__.pyi', 'bbb.pyi']}, - packages=['typedpkg_ns-stubs.b'], -) diff --git a/test-data/packages/typedpkg_ns_b/pyproject.toml b/test-data/packages/typedpkg_ns_b/pyproject.toml new file mode 100644 index 000000000000..b8ae0d59072e --- /dev/null +++ b/test-data/packages/typedpkg_ns_b/pyproject.toml @@ -0,0 +1,8 @@ +[project] +name = 'typedpkg_namespace.beta' +version = '0.1' +description = 'test' + +[build-system] +requires = ["hatchling==1.18"] +build-backend = "hatchling.build" diff --git a/test-data/packages/typedpkg_ns_b/setup.py b/test-data/packages/typedpkg_ns_b/setup.py deleted file mode 100644 index 4f0d0d954a73..000000000000 --- a/test-data/packages/typedpkg_ns_b/setup.py +++ /dev/null @@ -1,10 +0,0 @@ -from setuptools import setup - -setup( - name='typedpkg_namespace.beta', - version='1.0.0', - namespace_packages=['typedpkg_ns'], - zip_safe=False, - package_data={'typedpkg_ns.b': []}, - packages=['typedpkg_ns.b'], -) diff --git a/test-data/pybind11_fixtures/expected_stubs_no_docs/pybind11_fixtures/__init__.pyi b/test-data/pybind11_fixtures/expected_stubs_no_docs/pybind11_fixtures/__init__.pyi new file mode 100644 index 000000000000..90afb46d6d94 --- /dev/null +++ b/test-data/pybind11_fixtures/expected_stubs_no_docs/pybind11_fixtures/__init__.pyi @@ -0,0 +1,27 @@ +import os +from . import demo as demo +from typing import overload + +class StaticMethods: + def __init__(self, *args, **kwargs) -> None: ... + @overload + @staticmethod + def overloaded_static_method(value: int) -> int: ... + @overload + @staticmethod + def overloaded_static_method(value: float) -> float: ... + @staticmethod + def some_static_method(a: int, b: int) -> int: ... + +class TestStruct: + field_readwrite: int + field_readwrite_docstring: int + def __init__(self, *args, **kwargs) -> None: ... + @property + def field_readonly(self) -> int: ... + +def func_incomplete_signature(*args, **kwargs): ... +def func_returning_optional() -> int | None: ... +def func_returning_pair() -> tuple[int, float]: ... +def func_returning_path() -> os.PathLike: ... +def func_returning_vector() -> list[float]: ... diff --git a/test-data/pybind11_mypy_demo/stubgen/pybind11_mypy_demo/basics.pyi b/test-data/pybind11_fixtures/expected_stubs_no_docs/pybind11_fixtures/demo.pyi similarity index 88% rename from test-data/pybind11_mypy_demo/stubgen/pybind11_mypy_demo/basics.pyi rename to test-data/pybind11_fixtures/expected_stubs_no_docs/pybind11_fixtures/demo.pyi index ab5a4f4e78d2..87b8ec0e4ad6 100644 --- a/test-data/pybind11_mypy_demo/stubgen/pybind11_mypy_demo/basics.pyi +++ b/test-data/pybind11_fixtures/expected_stubs_no_docs/pybind11_fixtures/demo.pyi @@ -1,7 +1,7 @@ -from typing import ClassVar +from typing import ClassVar, overload -from typing import overload PI: float +__version__: str class Point: class AngleUnit: @@ -11,12 +11,10 @@ class Point: radian: ClassVar[Point.AngleUnit] = ... def __init__(self, value: int) -> None: ... def __eq__(self, other: object) -> bool: ... - def __getstate__(self) -> int: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... - def __setstate__(self, state: int) -> None: ... @property def name(self) -> str: ... @property @@ -30,12 +28,10 @@ class Point: pixel: ClassVar[Point.LengthUnit] = ... def __init__(self, value: int) -> None: ... def __eq__(self, other: object) -> bool: ... - def __getstate__(self) -> int: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... - def __setstate__(self, state: int) -> None: ... @property def name(self) -> str: ... @property @@ -51,6 +47,7 @@ class Point: def __init__(self) -> None: ... @overload def __init__(self, x: float, y: float) -> None: ... + def as_list(self) -> list[float]: ... @overload def distance_to(self, x: float, y: float) -> float: ... @overload diff --git a/test-data/pybind11_fixtures/expected_stubs_with_docs/pybind11_fixtures/__init__.pyi b/test-data/pybind11_fixtures/expected_stubs_with_docs/pybind11_fixtures/__init__.pyi new file mode 100644 index 000000000000..db04bccab028 --- /dev/null +++ b/test-data/pybind11_fixtures/expected_stubs_with_docs/pybind11_fixtures/__init__.pyi @@ -0,0 +1,52 @@ +import os +from . import demo as demo +from typing import overload + +class StaticMethods: + def __init__(self, *args, **kwargs) -> None: + """Initialize self. See help(type(self)) for accurate signature.""" + @overload + @staticmethod + def overloaded_static_method(value: int) -> int: + """overloaded_static_method(*args, **kwargs) + Overloaded function. + + 1. overloaded_static_method(value: int) -> int + + 2. overloaded_static_method(value: float) -> float + """ + @overload + @staticmethod + def overloaded_static_method(value: float) -> float: + """overloaded_static_method(*args, **kwargs) + Overloaded function. + + 1. overloaded_static_method(value: int) -> int + + 2. overloaded_static_method(value: float) -> float + """ + @staticmethod + def some_static_method(a: int, b: int) -> int: + """some_static_method(a: int, b: int) -> int + + None + """ + +class TestStruct: + field_readwrite: int + field_readwrite_docstring: int + def __init__(self, *args, **kwargs) -> None: + """Initialize self. See help(type(self)) for accurate signature.""" + @property + def field_readonly(self) -> int: ... + +def func_incomplete_signature(*args, **kwargs): + """func_incomplete_signature() -> dummy_sub_namespace::HasNoBinding""" +def func_returning_optional() -> int | None: + """func_returning_optional() -> Optional[int]""" +def func_returning_pair() -> tuple[int, float]: + """func_returning_pair() -> Tuple[int, float]""" +def func_returning_path() -> os.PathLike: + """func_returning_path() -> os.PathLike""" +def func_returning_vector() -> list[float]: + """func_returning_vector() -> List[float]""" diff --git a/test-data/pybind11_fixtures/expected_stubs_with_docs/pybind11_fixtures/demo.pyi b/test-data/pybind11_fixtures/expected_stubs_with_docs/pybind11_fixtures/demo.pyi new file mode 100644 index 000000000000..1be0bc905a43 --- /dev/null +++ b/test-data/pybind11_fixtures/expected_stubs_with_docs/pybind11_fixtures/demo.pyi @@ -0,0 +1,112 @@ +from typing import ClassVar, overload + +PI: float +__version__: str + +class Point: + class AngleUnit: + __members__: ClassVar[dict] = ... # read-only + __entries: ClassVar[dict] = ... + degree: ClassVar[Point.AngleUnit] = ... + radian: ClassVar[Point.AngleUnit] = ... + def __init__(self, value: int) -> None: + """__init__(self: pybind11_fixtures.demo.Point.AngleUnit, value: int) -> None""" + def __eq__(self, other: object) -> bool: + """__eq__(self: object, other: object) -> bool""" + def __hash__(self) -> int: + """__hash__(self: object) -> int""" + def __index__(self) -> int: + """__index__(self: pybind11_fixtures.demo.Point.AngleUnit) -> int""" + def __int__(self) -> int: + """__int__(self: pybind11_fixtures.demo.Point.AngleUnit) -> int""" + def __ne__(self, other: object) -> bool: + """__ne__(self: object, other: object) -> bool""" + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + + class LengthUnit: + __members__: ClassVar[dict] = ... # read-only + __entries: ClassVar[dict] = ... + inch: ClassVar[Point.LengthUnit] = ... + mm: ClassVar[Point.LengthUnit] = ... + pixel: ClassVar[Point.LengthUnit] = ... + def __init__(self, value: int) -> None: + """__init__(self: pybind11_fixtures.demo.Point.LengthUnit, value: int) -> None""" + def __eq__(self, other: object) -> bool: + """__eq__(self: object, other: object) -> bool""" + def __hash__(self) -> int: + """__hash__(self: object) -> int""" + def __index__(self) -> int: + """__index__(self: pybind11_fixtures.demo.Point.LengthUnit) -> int""" + def __int__(self) -> int: + """__int__(self: pybind11_fixtures.demo.Point.LengthUnit) -> int""" + def __ne__(self, other: object) -> bool: + """__ne__(self: object, other: object) -> bool""" + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + angle_unit: ClassVar[Point.AngleUnit] = ... + length_unit: ClassVar[Point.LengthUnit] = ... + x_axis: ClassVar[Point] = ... # read-only + y_axis: ClassVar[Point] = ... # read-only + origin: ClassVar[Point] = ... + x: float + y: float + @overload + def __init__(self) -> None: + """__init__(*args, **kwargs) + Overloaded function. + + 1. __init__(self: pybind11_fixtures.demo.Point) -> None + + 2. __init__(self: pybind11_fixtures.demo.Point, x: float, y: float) -> None + """ + @overload + def __init__(self, x: float, y: float) -> None: + """__init__(*args, **kwargs) + Overloaded function. + + 1. __init__(self: pybind11_fixtures.demo.Point) -> None + + 2. __init__(self: pybind11_fixtures.demo.Point, x: float, y: float) -> None + """ + def as_list(self) -> list[float]: + """as_list(self: pybind11_fixtures.demo.Point) -> List[float]""" + @overload + def distance_to(self, x: float, y: float) -> float: + """distance_to(*args, **kwargs) + Overloaded function. + + 1. distance_to(self: pybind11_fixtures.demo.Point, x: float, y: float) -> float + + 2. distance_to(self: pybind11_fixtures.demo.Point, other: pybind11_fixtures.demo.Point) -> float + """ + @overload + def distance_to(self, other: Point) -> float: + """distance_to(*args, **kwargs) + Overloaded function. + + 1. distance_to(self: pybind11_fixtures.demo.Point, x: float, y: float) -> float + + 2. distance_to(self: pybind11_fixtures.demo.Point, other: pybind11_fixtures.demo.Point) -> float + """ + @property + def length(self) -> float: ... + +def answer() -> int: + '''answer() -> int + + answer docstring, with end quote" + ''' +def midpoint(left: float, right: float) -> float: + """midpoint(left: float, right: float) -> float""" +def sum(arg0: int, arg1: int) -> int: + '''sum(arg0: int, arg1: int) -> int + + multiline docstring test, edge case quotes """\'\'\' + ''' +def weighted_midpoint(left: float, right: float, alpha: float = ...) -> float: + """weighted_midpoint(left: float, right: float, alpha: float = 0.5) -> float""" diff --git a/test-data/pybind11_mypy_demo/pyproject.toml b/test-data/pybind11_fixtures/pyproject.toml similarity index 86% rename from test-data/pybind11_mypy_demo/pyproject.toml rename to test-data/pybind11_fixtures/pyproject.toml index 878abe731b1b..773d036e62f5 100644 --- a/test-data/pybind11_mypy_demo/pyproject.toml +++ b/test-data/pybind11_fixtures/pyproject.toml @@ -7,4 +7,4 @@ requires = [ "pybind11==2.9.2", ] -build-backend = "setuptools.build_meta" \ No newline at end of file +build-backend = "setuptools.build_meta" diff --git a/test-data/pybind11_mypy_demo/setup.py b/test-data/pybind11_fixtures/setup.py similarity index 85% rename from test-data/pybind11_mypy_demo/setup.py rename to test-data/pybind11_fixtures/setup.py index 0da1cfbcef19..e227b49935ea 100644 --- a/test-data/pybind11_mypy_demo/setup.py +++ b/test-data/pybind11_fixtures/setup.py @@ -5,14 +5,14 @@ # Documentation: https://pybind11.readthedocs.io/en/stable/compiling.html ext_modules = [ Pybind11Extension( - "pybind11_mypy_demo", + "pybind11_fixtures", ["src/main.cpp"], cxx_std=17, ), ] setup( - name="pybind11-mypy-demo", + name="pybind11_fixtures", version="0.0.1", ext_modules=ext_modules, ) diff --git a/test-data/pybind11_mypy_demo/src/main.cpp b/test-data/pybind11_fixtures/src/main.cpp similarity index 57% rename from test-data/pybind11_mypy_demo/src/main.cpp rename to test-data/pybind11_fixtures/src/main.cpp index 5cedef391b2d..4d275ab1fd70 100644 --- a/test-data/pybind11_mypy_demo/src/main.cpp +++ b/test-data/pybind11_fixtures/src/main.cpp @@ -43,11 +43,106 @@ */ #include +#include +#include +#include +#include + #include +#include +#include namespace py = pybind11; -namespace basics { +// ---------------------------------------------------------------------------- +// Dedicated test cases +// ---------------------------------------------------------------------------- + +std::vector funcReturningVector() +{ + return std::vector{1.0, 2.0, 3.0}; +} + +std::pair funcReturningPair() +{ + return std::pair{42, 1.0}; +} + +std::optional funcReturningOptional() +{ + return std::nullopt; +} + +std::filesystem::path funcReturningPath() +{ + return std::filesystem::path{"foobar"}; +} + +namespace dummy_sub_namespace { + struct HasNoBinding{}; +} + +// We can enforce the case of an incomplete signature by referring to a type in +// some namespace that doesn't have a pybind11 binding. +dummy_sub_namespace::HasNoBinding funcIncompleteSignature() +{ + return dummy_sub_namespace::HasNoBinding{}; +} + +struct TestStruct +{ + int field_readwrite; + int field_readwrite_docstring; + int field_readonly; +}; + +struct StaticMethods +{ + static int some_static_method(int a, int b) { return 42; } + static int overloaded_static_method(int value) { return 42; } + static double overloaded_static_method(double value) { return 1.0; } +}; + +// Bindings + +void bind_test_cases(py::module& m) { + m.def("func_returning_vector", &funcReturningVector); + m.def("func_returning_pair", &funcReturningPair); + m.def("func_returning_optional", &funcReturningOptional); + m.def("func_returning_path", &funcReturningPath); + + m.def("func_incomplete_signature", &funcIncompleteSignature); + + py::class_(m, "TestStruct") + .def_readwrite("field_readwrite", &TestStruct::field_readwrite) + .def_readwrite("field_readwrite_docstring", &TestStruct::field_readwrite_docstring, "some docstring") + .def_property_readonly( + "field_readonly", + [](const TestStruct& x) { + return x.field_readonly; + }, + "some docstring"); + + // Static methods + py::class_ pyStaticMethods(m, "StaticMethods"); + + pyStaticMethods + .def_static( + "some_static_method", + &StaticMethods::some_static_method, R"#(None)#", py::arg("a"), py::arg("b")) + .def_static( + "overloaded_static_method", + py::overload_cast(&StaticMethods::overloaded_static_method), py::arg("value")) + .def_static( + "overloaded_static_method", + py::overload_cast(&StaticMethods::overloaded_static_method), py::arg("value")); +} + +// ---------------------------------------------------------------------------- +// Original demo +// ---------------------------------------------------------------------------- + +namespace demo { int answer() { return 42; @@ -102,6 +197,11 @@ struct Point { return distance_to(other.x, other.y); } + std::vector as_vector() + { + return std::vector{x, y}; + } + double x, y; }; @@ -112,20 +212,22 @@ const Point Point::y_axis = Point(0, 1); Point::LengthUnit Point::length_unit = Point::LengthUnit::mm; Point::AngleUnit Point::angle_unit = Point::AngleUnit::radian; -} // namespace: basics +} // namespace: demo -void bind_basics(py::module& basics) { +// Bindings - using namespace basics; +void bind_demo(py::module& m) { + + using namespace demo; // Functions - basics.def("answer", &answer); - basics.def("sum", &sum); - basics.def("midpoint", &midpoint, py::arg("left"), py::arg("right")); - basics.def("weighted_midpoint", weighted_midpoint, py::arg("left"), py::arg("right"), py::arg("alpha")=0.5); + m.def("answer", &answer, "answer docstring, with end quote\""); // tests explicit docstrings + m.def("sum", &sum, "multiline docstring test, edge case quotes \"\"\"'''"); + m.def("midpoint", &midpoint, py::arg("left"), py::arg("right")); + m.def("weighted_midpoint", weighted_midpoint, py::arg("left"), py::arg("right"), py::arg("alpha")=0.5); // Classes - py::class_ pyPoint(basics, "Point"); + py::class_ pyPoint(m, "Point"); py::enum_ pyLengthUnit(pyPoint, "LengthUnit"); py::enum_ pyAngleUnit(pyPoint, "AngleUnit"); @@ -134,14 +236,15 @@ void bind_basics(py::module& basics) { .def(py::init(), py::arg("x"), py::arg("y")) .def("distance_to", py::overload_cast(&Point::distance_to, py::const_), py::arg("x"), py::arg("y")) .def("distance_to", py::overload_cast(&Point::distance_to, py::const_), py::arg("other")) - .def_readwrite("x", &Point::x) + .def("as_list", &Point::as_vector) + .def_readwrite("x", &Point::x, "some docstring") .def_property("y", [](Point& self){ return self.y; }, [](Point& self, double value){ self.y = value; } ) .def_property_readonly("length", &Point::length) .def_property_readonly_static("x_axis", [](py::object cls){return Point::x_axis;}) - .def_property_readonly_static("y_axis", [](py::object cls){return Point::y_axis;}) + .def_property_readonly_static("y_axis", [](py::object cls){return Point::y_axis;}, "another docstring") .def_readwrite_static("length_unit", &Point::length_unit) .def_property_static("angle_unit", [](py::object& /*cls*/){ return Point::angle_unit; }, @@ -160,11 +263,17 @@ void bind_basics(py::module& basics) { .value("degree", Point::AngleUnit::degree); // Module-level attributes - basics.attr("PI") = std::acos(-1); - basics.attr("__version__") = "0.0.1"; + m.attr("PI") = std::acos(-1); + m.attr("__version__") = "0.0.1"; } -PYBIND11_MODULE(pybind11_mypy_demo, m) { - auto basics = m.def_submodule("basics"); - bind_basics(basics); -} \ No newline at end of file +// ---------------------------------------------------------------------------- +// Module entry point +// ---------------------------------------------------------------------------- + +PYBIND11_MODULE(pybind11_fixtures, m) { + bind_test_cases(m); + + auto demo = m.def_submodule("demo"); + bind_demo(demo); +} diff --git a/test-data/unit/README.md b/test-data/unit/README.md index 6cf0b1bb26cf..5a9416603541 100644 --- a/test-data/unit/README.md +++ b/test-data/unit/README.md @@ -12,7 +12,7 @@ feature you added. If you added a new `check-*.test` file, it will be autodiscov Add the test in this format anywhere in the file: [case testNewSyntaxBasics] - # flags: --python-version 3.6 + # flags: --python-version 3.10 x: int x = 5 y: int = 5 @@ -36,7 +36,7 @@ the error message - `W: ...` and `N: ...` works exactly like `E: ...`, but report a warning and a note respectively - lines that don't contain the above should cause no type check errors - optional `[builtins fixtures/...]` tells the type checker to use -stubs from the indicated file (see Fixtures section below) +`builtins` stubs from the indicated file (see Fixtures section below) - optional `[out]` is an alternative to the `# E: ` notation: it indicates that any text after it contains the expected type checking error messages. Usually, `# E: ` is preferred because it makes it easier to associate the @@ -65,7 +65,7 @@ Where the stubs for builtins come from for a given test: - The builtins used by default in unit tests live in `test-data/unit/lib-stub`. -- Individual test cases can override the builtins stubs by using +- Individual test cases can override the `builtins` stubs by using `[builtins fixtures/foo.pyi]`; this targets files in `test-data/unit/fixtures`. Feel free to modify existing files there or create new ones as you deem fit. @@ -77,6 +77,21 @@ Where the stubs for builtins come from for a given test: addition with other mypy developers, as additions could slow down the test suite. +- Some tests choose to customize the standard library in a way that's local to the test: + ``` + [case testFoo] + ... + [file builtins.py] + class int: + def next_fibonacci() -> int: pass + ``` + Another possible syntax is: + ``` + [fixture builtins.py] + ``` + Whether you use `[file ...]` or `[fixture ...]` depends on whether you want + the file to be part of the tested corpus (e.g. contribute to `[out]` section) + or only support the test. Running tests and linting ------------------------- @@ -144,7 +159,7 @@ To run mypy on itself: To run the linter: - flake8 + ruff . You can also run all of the above tests using `runtests.py` (this includes type checking mypy and linting): diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test index 566bb92d6e18..3b0b9c520b75 100644 --- a/test-data/unit/check-abstract.test +++ b/test-data/unit/check-abstract.test @@ -9,11 +9,11 @@ from abc import abstractmethod, ABCMeta -i = None # type: I -j = None # type: J -a = None # type: A -b = None # type: B -c = None # type: C +i: I +j: J +a: A +b: B +c: C def f(): i, j, a, b, c # Prevent redefinition @@ -44,10 +44,10 @@ class C(I): pass from abc import abstractmethod, ABCMeta -i = None # type: I -j = None # type: J -a = None # type: A -o = None # type: object +i: I +j: J +a: A +o: object def f(): i, j, a, o # Prevent redefinition @@ -73,9 +73,9 @@ class A(J): pass [case testInheritingAbstractClassInSubclass] from abc import abstractmethod, ABCMeta -i = None # type: I -a = None # type: A -b = None # type: B +i: I +a: A +b: B if int(): i = a # E: Incompatible types in assignment (expression has type "A", variable has type "I") @@ -106,8 +106,8 @@ class I(metaclass=ABCMeta): @abstractmethod def f(self): pass -o = None # type: object -t = None # type: type +o: object +t: type o = I t = I @@ -122,8 +122,10 @@ class I(metaclass=ABCMeta): class A(I): pass class B: pass -i, a, b = None, None, None # type: (I, A, B) -o = None # type: object +i: I +a: A +b: B +o: object if int(): a = cast(I, o) # E: Incompatible types in assignment (expression has type "I", variable has type "A") @@ -196,6 +198,24 @@ x: Type[B] f(x) # OK [out] +[case testAbstractTypeInADict] +from typing import Dict, Type +from abc import abstractmethod + +class Class: + @abstractmethod + def method(self) -> None: + pass + +my_dict_init: Dict[int, Type[Class]] = {0: Class} # E: Only concrete class can be given where "Tuple[int, Type[Class]]" is expected + +class Child(Class): + def method(self) -> None: ... + +other_dict_init: Dict[int, Type[Class]] = {0: Child} # ok +[builtins fixtures/dict.pyi] +[out] + [case testInstantiationAbstractsInTypeForAliases] from typing import Type from abc import abstractmethod @@ -220,7 +240,8 @@ f(GoodAlias) [out] [case testInstantiationAbstractsInTypeForVariables] -from typing import Type +# flags: --no-strict-optional +from typing import Type, overload from abc import abstractmethod class A: @@ -248,6 +269,15 @@ if int(): var_old = B # E: Can only assign concrete classes to a variable of type "Type[A]" if int(): var_old = C # OK + +class D(A): + @overload + def __new__(cls, a) -> "D": ... + @overload + def __new__(cls) -> "D": ... + def __new__(cls, a=None) -> "D": ... +if int(): + var = D # E: Can only assign concrete classes to a variable of type "Type[A]" [out] [case testInstantiationAbstractsInTypeForClassMethods] @@ -399,7 +429,9 @@ class I(metaclass=ABCMeta): @abstractmethod def f(self, a: int) -> str: pass -i, a, b = None, None, None # type: (I, int, str) +i: I +a: int +b: str if int(): a = i.f(a) # E: Incompatible types in assignment (expression has type "str", variable has type "int") @@ -419,7 +451,9 @@ class J(metaclass=ABCMeta): def f(self, a: int) -> str: pass class I(J): pass -i, a, b = None, None, None # type: (I, int, str) +i: I +a: int +b: str if int(): a = i.f(1) # E: Incompatible types in assignment (expression has type "str", variable has type "int") @@ -505,7 +539,7 @@ class B(metaclass=ABCMeta): @abstractmethod def g(self) -> None: pass class C(A, B): pass -x = None # type: C +x: C x.f() x.g() x.f(x) # E: Too many arguments for "f" of "A" @@ -735,7 +769,45 @@ class A(metaclass=ABCMeta): def x(self) -> int: pass @x.setter def x(self, x: int) -> None: pass -[out] + +[case testReadWriteDeleteAbstractProperty] +# flags: --no-strict-optional +from abc import ABC, abstractmethod +class Abstract(ABC): + @property + @abstractmethod + def prop(self) -> str: ... + + @prop.setter + @abstractmethod + def prop(self, code: str) -> None: ... + + @prop.deleter + @abstractmethod + def prop(self) -> None: ... + +class Good(Abstract): + @property + def prop(self) -> str: ... + @prop.setter + def prop(self, code: str) -> None: ... + @prop.deleter + def prop(self) -> None: ... + +class Bad1(Abstract): + @property # E: Read-only property cannot override read-write property + def prop(self) -> str: ... + +class ThisShouldProbablyError(Abstract): + @property + def prop(self) -> str: ... + @prop.setter + def prop(self, code: str) -> None: ... + +a = Good() +reveal_type(a.prop) # N: Revealed type is "builtins.str" +a.prop = 123 # E: Incompatible types in assignment (expression has type "int", variable has type "str") +[builtins fixtures/property.pyi] [case testInstantiateClassWithReadOnlyAbstractProperty] from abc import abstractproperty, ABCMeta @@ -767,7 +839,7 @@ b = B() b.x() # E: "int" not callable [builtins fixtures/property.pyi] -[case testImplementReradWriteAbstractPropertyViaProperty] +[case testImplementReadWriteAbstractPropertyViaProperty] from abc import abstractproperty, ABCMeta class A(metaclass=ABCMeta): @abstractproperty @@ -790,7 +862,11 @@ class A(metaclass=ABCMeta): def x(self) -> int: pass class B(A): @property - def x(self) -> str: return "no" # E: Signature of "x" incompatible with supertype "A" + def x(self) -> str: return "no" # E: Signature of "x" incompatible with supertype "A" \ + # N: Superclass: \ + # N: int \ + # N: Subclass: \ + # N: str b = B() b.x() # E: "str" not callable [builtins fixtures/property.pyi] @@ -812,6 +888,7 @@ main:8: error: Cannot instantiate abstract class "B" with abstract attribute "x" main:9: error: "int" has no attribute "y" [case testSuperWithAbstractProperty] +# flags: --no-strict-optional from abc import abstractproperty, ABCMeta class A(metaclass=ABCMeta): @abstractproperty @@ -819,9 +896,9 @@ class A(metaclass=ABCMeta): class B(A): @property def x(self) -> int: - return super().x.y # E: "int" has no attribute "y" + return super().x.y # E: Call to abstract method "x" of "A" with trivial body via super() is unsafe \ + # E: "int" has no attribute "y" [builtins fixtures/property.pyi] -[out] [case testSuperWithReadWriteAbstractProperty] from abc import abstractproperty, ABCMeta @@ -1057,7 +1134,6 @@ b.y = 1 -- ----------------------------------------------- [case testEmptyBodyProhibitedFunction] -# flags: --strict-optional from typing import overload, Union def func1(x: str) -> int: pass # E: Missing return statement @@ -1080,7 +1156,6 @@ def func5(x: Union[int, str]) -> Union[int, str]: # E: Missing return statement """Some function.""" [case testEmptyBodyProhibitedMethodNonAbstract] -# flags: --strict-optional from typing import overload, Union class A: @@ -1115,7 +1190,6 @@ class C: [builtins fixtures/classmethod.pyi] [case testEmptyBodyProhibitedPropertyNonAbstract] -# flags: --strict-optional class A: @property def x(self) -> int: ... # E: Missing return statement @@ -1144,7 +1218,6 @@ class C: [builtins fixtures/property.pyi] [case testEmptyBodyNoteABCMeta] -# flags: --strict-optional from abc import ABC class A(ABC): @@ -1153,7 +1226,6 @@ class A(ABC): ... [case testEmptyBodyAllowedFunctionStub] -# flags: --strict-optional import stub [file stub.pyi] from typing import overload, Union @@ -1164,7 +1236,6 @@ def func3(x: str) -> int: """Some function.""" [case testEmptyBodyAllowedMethodNonAbstractStub] -# flags: --strict-optional import stub [file stub.pyi] from typing import overload, Union @@ -1186,7 +1257,6 @@ class B: [builtins fixtures/classmethod.pyi] [case testEmptyBodyAllowedPropertyNonAbstractStub] -# flags: --strict-optional import stub [file stub.pyi] class A: @@ -1217,7 +1287,6 @@ class C: [builtins fixtures/property.pyi] [case testEmptyBodyAllowedMethodAbstract] -# flags: --strict-optional from typing import overload, Union from abc import abstractmethod @@ -1265,7 +1334,6 @@ class C: [builtins fixtures/classmethod.pyi] [case testEmptyBodyAllowedPropertyAbstract] -# flags: --strict-optional from abc import abstractmethod class A: @property @@ -1304,7 +1372,6 @@ class C: [builtins fixtures/property.pyi] [case testEmptyBodyImplicitlyAbstractProtocol] -# flags: --strict-optional from typing import Protocol, overload, Union class P1(Protocol): @@ -1345,7 +1412,6 @@ C3() [builtins fixtures/classmethod.pyi] [case testEmptyBodyImplicitlyAbstractProtocolProperty] -# flags: --strict-optional from typing import Protocol class P1(Protocol): @@ -1375,7 +1441,6 @@ C2() [builtins fixtures/property.pyi] [case testEmptyBodyImplicitlyAbstractProtocolStub] -# flags: --strict-optional from stub import P1, P2, P3, P4 class B1(P1): ... @@ -1411,7 +1476,6 @@ class P4(Protocol): [builtins fixtures/classmethod.pyi] [case testEmptyBodyUnsafeAbstractSuper] -# flags: --strict-optional from stub import StubProto, StubAbstract from typing import Protocol from abc import abstractmethod @@ -1460,7 +1524,6 @@ class StubAbstract: def meth(self) -> int: ... [case testEmptyBodyUnsafeAbstractSuperProperty] -# flags: --strict-optional from stub import StubProto, StubAbstract from typing import Protocol from abc import abstractmethod @@ -1518,7 +1581,6 @@ class StubAbstract: [builtins fixtures/property.pyi] [case testEmptyBodyUnsafeAbstractSuperOverloads] -# flags: --strict-optional from stub import StubProto from typing import Protocol, overload, Union @@ -1597,13 +1659,12 @@ class Abstract: class SubProto(Proto): def meth(self) -> int: - return super().meth() + return super().meth() # E: Call to abstract method "meth" of "Proto" with trivial body via super() is unsafe class SubAbstract(Abstract): def meth(self) -> int: - return super().meth() + return super().meth() # E: Call to abstract method "meth" of "Abstract" with trivial body via super() is unsafe [case testEmptyBodyNoSuperWarningOptionalReturn] -# flags: --strict-optional from typing import Protocol, Optional from abc import abstractmethod @@ -1615,13 +1676,12 @@ class Abstract: class SubProto(Proto): def meth(self) -> Optional[int]: - return super().meth() + return super().meth() # E: Call to abstract method "meth" of "Proto" with trivial body via super() is unsafe class SubAbstract(Abstract): def meth(self) -> Optional[int]: - return super().meth() + return super().meth() # E: Call to abstract method "meth" of "Abstract" with trivial body via super() is unsafe [case testEmptyBodyTypeCheckingOnly] -# flags: --strict-optional from typing import TYPE_CHECKING class C: diff --git a/test-data/unit/check-assert-type-fail.test b/test-data/unit/check-assert-type-fail.test new file mode 100644 index 000000000000..89b3a863f8c7 --- /dev/null +++ b/test-data/unit/check-assert-type-fail.test @@ -0,0 +1,33 @@ +[case testAssertTypeFail1] +import typing +import array as arr +class array: + pass +def f(si: arr.array[int]): + typing.assert_type(si, array) # E: Expression is of type "array.array[int]", not "__main__.array" +[builtins fixtures/tuple.pyi] + +[case testAssertTypeFail2] +import typing +import array as arr +class array: + class array: + i = 1 +def f(si: arr.array[int]): + typing.assert_type(si, array.array) # E: Expression is of type "array.array[int]", not "__main__.array.array" +[builtins fixtures/tuple.pyi] + +[case testAssertTypeFail3] +import typing +import array as arr +class array: + class array: + i = 1 +def f(si: arr.array[int]): + typing.assert_type(si, int) # E: Expression is of type "array[int]", not "int" +[builtins fixtures/tuple.pyi] + +[case testAssertTypeFailCallableArgKind] +from typing import assert_type, Callable +def myfunc(arg: int) -> None: pass +assert_type(myfunc, Callable[[int], None]) # E: Expression is of type "Callable[[Arg(int, 'arg')], None]", not "Callable[[int], None]" diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test index 40efe2d2cece..713c82c752ce 100644 --- a/test-data/unit/check-async-await.test +++ b/test-data/unit/check-async-await.test @@ -165,6 +165,33 @@ async def f() -> None: [out] main:4: error: "List[int]" has no attribute "__aiter__" (not async iterable) +[case testAsyncForErrorNote] + +from typing import AsyncIterator, AsyncGenerator +async def g() -> AsyncGenerator[str, None]: + pass + +async def f() -> None: + async for x in g(): + pass +[builtins fixtures/async_await.pyi] +[typing fixtures/typing-async.pyi] +[out] +main:7: error: "Coroutine[Any, Any, AsyncGenerator[str, None]]" has no attribute "__aiter__" (not async iterable) +main:7: note: Maybe you forgot to use "await"? + +[case testAsyncForErrorCanBeIgnored] + +from typing import AsyncIterator, AsyncGenerator +async def g() -> AsyncGenerator[str, None]: + pass + +async def f() -> None: + async for x in g(): # type: ignore[attr-defined] + pass +[builtins fixtures/async_await.pyi] +[typing fixtures/typing-async.pyi] + [case testAsyncForTypeComments] from typing import AsyncIterator, Union @@ -183,7 +210,6 @@ async def f() -> None: [typing fixtures/typing-async.pyi] [case testAsyncForComprehension] -# flags: --python-version 3.6 from typing import Generic, Iterable, TypeVar, AsyncIterator, Tuple T = TypeVar('T') @@ -223,7 +249,6 @@ async def generatorexp(obj: Iterable[int]): [typing fixtures/typing-async.pyi] [case testAsyncForComprehensionErrors] -# flags: --python-version 3.6 from typing import Generic, Iterable, TypeVar, AsyncIterator, Tuple T = TypeVar('T') @@ -240,16 +265,10 @@ class asyncify(Generic[T], AsyncIterator[T]): raise StopAsyncIteration async def wrong_iterable(obj: Iterable[int]): - [i async for i in obj] - [i for i in asyncify(obj)] - {i: i async for i in obj} - {i: i for i in asyncify(obj)} - -[out] -main:18: error: "Iterable[int]" has no attribute "__aiter__" (not async iterable) -main:19: error: "asyncify[int]" has no attribute "__iter__"; maybe "__aiter__"? (not iterable) -main:20: error: "Iterable[int]" has no attribute "__aiter__" (not async iterable) -main:21: error: "asyncify[int]" has no attribute "__iter__"; maybe "__aiter__"? (not iterable) + [i async for i in obj] # E: "Iterable[int]" has no attribute "__aiter__" (not async iterable) + [i for i in asyncify(obj)] # E: "asyncify[int]" has no attribute "__iter__"; maybe "__aiter__"? (not iterable) + {i: i async for i in obj} # E: "Iterable[int]" has no attribute "__aiter__" (not async iterable) + {i: i for i in asyncify(obj)} # E: "asyncify[int]" has no attribute "__iter__"; maybe "__aiter__"? (not iterable) [builtins fixtures/async_await.pyi] [typing fixtures/typing-async.pyi] @@ -291,7 +310,7 @@ async def f() -> None: [typing fixtures/typing-async.pyi] [case testAsyncWithErrorBadAenter2] - +# flags: --no-strict-optional class C: def __aenter__(self) -> None: pass async def __aexit__(self, x, y, z) -> None: pass @@ -313,7 +332,7 @@ async def f() -> None: [typing fixtures/typing-async.pyi] [case testAsyncWithErrorBadAexit2] - +# flags: --no-strict-optional class C: async def __aenter__(self) -> int: pass def __aexit__(self, x, y, z) -> None: pass @@ -340,17 +359,6 @@ async def f() -> None: [builtins fixtures/async_await.pyi] [typing fixtures/typing-async.pyi] -[case testNoYieldInAsyncDef] -# flags: --python-version 3.5 - -async def f(): - yield None # E: "yield" in async function -async def g(): - yield # E: "yield" in async function -async def h(): - x = yield # E: "yield" in async function -[builtins fixtures/async_await.pyi] - [case testNoYieldFromInAsyncDef] async def f(): @@ -422,7 +430,6 @@ def f() -> Generator[int, str, int]: -- --------------------------------------------------------------------- [case testAsyncGenerator] -# flags: --python-version 3.6 from typing import AsyncGenerator, Generator async def f() -> int: @@ -450,7 +457,6 @@ async def wrong_return() -> Generator[int, None, None]: # E: The return type of [typing fixtures/typing-async.pyi] [case testAsyncGeneratorReturnIterator] -# flags: --python-version 3.6 from typing import AsyncIterator async def gen() -> AsyncIterator[int]: @@ -466,7 +472,6 @@ async def use_gen() -> None: [typing fixtures/typing-async.pyi] [case testAsyncGeneratorManualIter] -# flags: --python-version 3.6 from typing import AsyncGenerator async def genfunc() -> AsyncGenerator[int, None]: @@ -484,7 +489,6 @@ async def user() -> None: [typing fixtures/typing-async.pyi] [case testAsyncGeneratorAsend] -# flags: --python-version 3.6 from typing import AsyncGenerator async def f() -> None: @@ -498,14 +502,13 @@ async def gen() -> AsyncGenerator[int, str]: async def h() -> None: g = gen() - await g.asend(()) # E: Argument 1 to "asend" of "AsyncGenerator" has incompatible type "Tuple[]"; expected "str" + await g.asend(()) # E: Argument 1 to "asend" of "AsyncGenerator" has incompatible type "Tuple[()]"; expected "str" reveal_type(await g.asend('hello')) # N: Revealed type is "builtins.int" [builtins fixtures/dict.pyi] [typing fixtures/typing-async.pyi] [case testAsyncGeneratorAthrow] -# flags: --python-version 3.6 from typing import AsyncGenerator async def gen() -> AsyncGenerator[str, int]: @@ -524,7 +527,6 @@ async def h() -> None: [typing fixtures/typing-async.pyi] [case testAsyncGeneratorNoSyncIteration] -# flags: --python-version 3.6 from typing import AsyncGenerator async def gen() -> AsyncGenerator[int, None]: @@ -532,17 +534,13 @@ async def gen() -> AsyncGenerator[int, None]: yield i def h() -> None: - for i in gen(): + for i in gen(): # E: "AsyncGenerator[int, None]" has no attribute "__iter__"; maybe "__aiter__"? (not iterable) pass [builtins fixtures/dict.pyi] [typing fixtures/typing-async.pyi] -[out] -main:9: error: "AsyncGenerator[int, None]" has no attribute "__iter__"; maybe "__aiter__"? (not iterable) - [case testAsyncGeneratorNoYieldFrom] -# flags: --python-version 3.6 from typing import AsyncGenerator async def f() -> AsyncGenerator[int, None]: @@ -555,7 +553,6 @@ async def gen() -> AsyncGenerator[int, None]: [typing fixtures/typing-async.pyi] [case testAsyncGeneratorNoReturnWithValue] -# flags: --python-version 3.6 from typing import AsyncGenerator async def return_int() -> AsyncGenerator[int, None]: @@ -822,8 +819,6 @@ def bar() -> None: [typing fixtures/typing-async.pyi] [case testAsyncForOutsideCoroutine] -# flags: --python-version 3.7 - async def g(): yield 0 @@ -844,8 +839,6 @@ async for x in g(): ... # E: "async for" outside async function [typing fixtures/typing-async.pyi] [case testAsyncWithOutsideCoroutine] -# flags: --python-version 3.7 - class C: async def __aenter__(self): pass async def __aexit__(self, x, y, z): pass @@ -861,7 +854,6 @@ async with C() as x: # E: "async with" outside async function [typing fixtures/typing-async.pyi] [case testAwaitMissingNote] -# flags: --python-version 3.7 from typing import Generic, TypeVar, Generator, Any, Awaitable, Type class C: @@ -944,14 +936,126 @@ async def bar(x: Union[A, B]) -> None: [builtins fixtures/async_await.pyi] [typing fixtures/typing-async.pyi] +[case testAsyncIteratorWithIgnoredErrors] +import m + +async def func(l: m.L) -> None: + reveal_type(l.get_iterator) # N: Revealed type is "def () -> typing.AsyncIterator[builtins.str]" + reveal_type(l.get_iterator2) # N: Revealed type is "def () -> typing.AsyncIterator[builtins.str]" + async for i in l.get_iterator(): + reveal_type(i) # N: Revealed type is "builtins.str" + + reveal_type(m.get_generator) # N: Revealed type is "def () -> typing.AsyncGenerator[builtins.int, None]" + async for i2 in m.get_generator(): + reveal_type(i2) # N: Revealed type is "builtins.int" + +[file m.py] +# mypy: ignore-errors=True +from typing import AsyncIterator, AsyncGenerator + +class L: + async def some_func(self, i: int) -> str: + return 'x' + + async def get_iterator(self) -> AsyncIterator[str]: + yield await self.some_func(0) + + async def get_iterator2(self) -> AsyncIterator[str]: + if self: + a = (yield 'x') + +async def get_generator() -> AsyncGenerator[int, None]: + yield 1 + +[builtins fixtures/async_await.pyi] +[typing fixtures/typing-async.pyi] + +[case testAsyncIteratorWithIgnoredErrorsAndYieldFrom] +from m import L + +async def func(l: L) -> None: + reveal_type(l.get_iterator) + +[file m.py] +# mypy: ignore-errors=True +from typing import AsyncIterator + +class L: + async def get_iterator(self) -> AsyncIterator[str]: + yield from ['x'] # E: "yield from" in async function +[builtins fixtures/async_await.pyi] +[typing fixtures/typing-async.pyi] + [case testInvalidComprehensionNoCrash] +# flags: --show-error-codes async def foo(x: int) -> int: ... -crasher = [await foo(x) for x in [1, 2, 3]] # E: "await" outside function +# These are allowed in some cases: +top_level = await foo(1) # E: "await" outside function [top-level-await] +crasher = [await foo(x) for x in [1, 2, 3]] # E: "await" outside function [top-level-await] def bad() -> None: - y = [await foo(x) for x in [1, 2, 3]] # E: "await" outside coroutine ("async def") + # These are always critical / syntax issues: + y = [await foo(x) for x in [1, 2, 3]] # E: "await" outside coroutine ("async def") [await-not-async] async def good() -> None: y = [await foo(x) for x in [1, 2, 3]] # OK [builtins fixtures/async_await.pyi] [typing fixtures/typing-async.pyi] + +[case testNestedAsyncFunctionAndTypeVarAvalues] +from typing import TypeVar + +T = TypeVar('T', int, str) + +def f(x: T) -> None: + async def g() -> T: + return x +[builtins fixtures/async_await.pyi] +[typing fixtures/typing-async.pyi] + +[case testNestedAsyncGeneratorAndTypeVarAvalues] +from typing import AsyncGenerator, TypeVar + +T = TypeVar('T', int, str) + +def f(x: T) -> None: + async def g() -> AsyncGenerator[T, None]: + yield x +[builtins fixtures/async_await.pyi] +[typing fixtures/typing-async.pyi] + +[case testNestedDecoratedCoroutineAndTypeVarValues] +from typing import Generator, TypeVar +from types import coroutine + +T = TypeVar('T', int, str) + +def f(x: T) -> None: + @coroutine + def inner() -> Generator[T, None, None]: + yield x + reveal_type(inner) # N: Revealed type is "def () -> typing.AwaitableGenerator[builtins.int, None, None, typing.Generator[builtins.int, None, None]]" \ + # N: Revealed type is "def () -> typing.AwaitableGenerator[builtins.str, None, None, typing.Generator[builtins.str, None, None]]" + +@coroutine +def coro() -> Generator[int, None, None]: + yield 1 +reveal_type(coro) # N: Revealed type is "def () -> typing.AwaitableGenerator[builtins.int, None, None, typing.Generator[builtins.int, None, None]]" +[builtins fixtures/async_await.pyi] +[typing fixtures/typing-async.pyi] + +[case asyncIteratorInProtocol] +from typing import AsyncIterator, Protocol + +class P(Protocol): + async def launch(self) -> AsyncIterator[int]: + raise BaseException + +class Launcher(P): + def launch(self) -> AsyncIterator[int]: # E: Return type "AsyncIterator[int]" of "launch" incompatible with return type "Coroutine[Any, Any, AsyncIterator[int]]" in supertype "P" \ + # N: Consider declaring "launch" in supertype "P" without "async" \ + # N: See https://mypy.readthedocs.io/en/stable/more_types.html#asynchronous-iterators + raise BaseException + +[builtins fixtures/async_await.pyi] +[typing fixtures/typing-async.pyi] diff --git a/test-data/unit/check-basic.test b/test-data/unit/check-basic.test index b81f9573b347..959d80cb2104 100644 --- a/test-data/unit/check-basic.test +++ b/test-data/unit/check-basic.test @@ -2,8 +2,8 @@ [out] [case testAssignmentAndVarDef] -a = None # type: A -b = None # type: B +a: A +b: B if int(): a = a if int(): @@ -17,14 +17,14 @@ class A: class B: def __init__(self): pass -x = None # type: A +x: A x = A() if int(): x = B() # E: Incompatible types in assignment (expression has type "B", variable has type "A") [case testInheritInitFromObject] class A(object): pass class B(object): pass -x = None # type: A +x: A if int(): x = A() if int(): @@ -32,8 +32,8 @@ if int(): [case testImplicitInheritInitFromObject] class A: pass class B: pass -x = None # type: A -o = None # type: object +x: A +o: object if int(): x = o # E: Incompatible types in assignment (expression has type "object", variable has type "A") if int(): @@ -59,7 +59,7 @@ x = B() # type: A y = A() # type: B # E: Incompatible types in assignment (expression has type "A", variable has type "B") [case testDeclaredVariableInParentheses] -(x) = None # type: int +(x) = 2 # type: int if int(): x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") if int(): @@ -135,8 +135,8 @@ main:6: error: Missing positional arguments "baz", "bas" in call to "foo" [case testLocalVariables] def f() -> None: - x = None # type: A - y = None # type: B + x: A + y: B if int(): x = x x = y # E: Incompatible types in assignment (expression has type "B", variable has type "A") @@ -237,12 +237,12 @@ reveal_type(module.__spec__) # N: Revealed type is "builtins.object" [case testLocalVariableShadowing] class A: pass class B: pass -a = None # type: A +a: A if int(): a = B() # E: Incompatible types in assignment (expression has type "B", variable has type "A") a = A() def f() -> None: - a = None # type: B + a: B if int(): a = A() # E: Incompatible types in assignment (expression has type "A", variable has type "B") a = B() @@ -250,8 +250,8 @@ a = B() # E: Incompatible types in assignment (expression has type "B", va a = A() [case testGlobalDefinedInBlockWithType] class A: pass -while A: - a = None # type: A +while 1: + a: A if int(): a = A() a = object() # E: Incompatible types in assignment (expression has type "object", variable has type "A") @@ -393,7 +393,6 @@ y = x # E: Incompatible types in assignment (expression has type "Dict[str, int] [builtins fixtures/dict.pyi] [case testDistinctTypes] -# flags: --strict-optional import b [file a.py] @@ -501,3 +500,29 @@ class A: [file test.py] def foo(s: str) -> None: ... + +[case testInlineAssertions] +import a, b +s1: str = 42 # E: Incompatible types in assignment (expression has type "int", variable has type "str") +[file a.py] +s2: str = 42 # E: Incompatible types in assignment (expression has type "int", variable has type "str") +[file b.py] +s3: str = 42 # E: Incompatible types in assignment (expression has type "int", variable has type "str") +[file c.py] +s3: str = 'foo' + +[case testMultilineQuotedAnnotation] +x: """ + + int | + str + +""" +reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str]" + +y: """( + int | + str +) +""" +reveal_type(y) # N: Revealed type is "Union[builtins.int, builtins.str]" diff --git a/test-data/unit/check-bound.test b/test-data/unit/check-bound.test index eb97bde32e1f..1c713fd77c38 100644 --- a/test-data/unit/check-bound.test +++ b/test-data/unit/check-bound.test @@ -37,15 +37,16 @@ T = TypeVar('T', bound=A) class G(Generic[T]): def __init__(self, x: T) -> None: pass -v = None # type: G[A] -w = None # type: G[B] -x = None # type: G[str] # E: Type argument "str" of "G" must be a subtype of "A" +v: G[A] +w: G[B] +x: G[str] # E: Type argument "str" of "G" must be a subtype of "A" y = G('a') # E: Value of type variable "T" of "G" cannot be "str" z = G(A()) z = G(B()) [case testBoundVoid] +# flags: --no-strict-optional from typing import TypeVar, Generic T = TypeVar('T', bound=int) class C(Generic[T]): @@ -70,10 +71,11 @@ def g(): pass f(g()) C(g()) -z = None # type: C +z: C [case testBoundHigherOrderWithVoid] +# flags: --no-strict-optional from typing import TypeVar, Callable class A: pass T = TypeVar('T', bound=A) diff --git a/test-data/unit/check-callable.test b/test-data/unit/check-callable.test index 7d25eb271f53..39e6c4fa3ff1 100644 --- a/test-data/unit/check-callable.test +++ b/test-data/unit/check-callable.test @@ -525,7 +525,6 @@ else: [builtins fixtures/callable.pyi] [case testBuiltinsTypeAsCallable] -# flags: --python-version 3.7 from __future__ import annotations reveal_type(type) # N: Revealed type is "def (x: Any) -> builtins.type" @@ -587,3 +586,45 @@ class C(B): def f(self, x: int) -> C: ... class B: ... [builtins fixtures/classmethod.pyi] + +[case testCallableUnionCallback] +from typing import Union, Callable, TypeVar + +TA = TypeVar("TA", bound="A") +class A: + def __call__(self: TA, other: Union[Callable, TA]) -> TA: ... +a: A +a() # E: Missing positional argument "other" in call to "__call__" of "A" +a(a) +a(lambda: None) + +[case testCallableSubtypingTrivialSuffix] +from typing import Any, Protocol + +class Call(Protocol): + def __call__(self, x: int, *args: Any, **kwargs: Any) -> None: ... + +def f1() -> None: ... +a1: Call = f1 # E: Incompatible types in assignment (expression has type "Callable[[], None]", variable has type "Call") \ + # N: "Call.__call__" has type "Callable[[Arg(int, 'x'), VarArg(Any), KwArg(Any)], None]" +def f2(x: str) -> None: ... +a2: Call = f2 # E: Incompatible types in assignment (expression has type "Callable[[str], None]", variable has type "Call") \ + # N: "Call.__call__" has type "Callable[[Arg(int, 'x'), VarArg(Any), KwArg(Any)], None]" +def f3(y: int) -> None: ... +a3: Call = f3 # E: Incompatible types in assignment (expression has type "Callable[[int], None]", variable has type "Call") \ + # N: "Call.__call__" has type "Callable[[Arg(int, 'x'), VarArg(Any), KwArg(Any)], None]" +def f4(x: int) -> None: ... +a4: Call = f4 + +def f5(x: int, y: int) -> None: ... +a5: Call = f5 + +def f6(x: int, y: int = 0) -> None: ... +a6: Call = f6 + +def f7(x: int, *, y: int) -> None: ... +a7: Call = f7 + +def f8(x: int, *args: int, **kwargs: str) -> None: ... +a8: Call = f8 +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-class-namedtuple.test b/test-data/unit/check-class-namedtuple.test index 8ae7f6555f9d..f334b9011645 100644 --- a/test-data/unit/check-class-namedtuple.test +++ b/test-data/unit/check-class-namedtuple.test @@ -1,13 +1,4 @@ -[case testNewNamedTupleOldPythonVersion] -# flags: --python-version 3.5 -from typing import NamedTuple - -class E(NamedTuple): # E: NamedTuple class syntax is only supported in Python 3.6 - pass -[builtins fixtures/tuple.pyi] - [case testNewNamedTupleNoUnderscoreFields] -# flags: --python-version 3.6 from typing import NamedTuple class X(NamedTuple): @@ -17,7 +8,6 @@ class X(NamedTuple): [builtins fixtures/tuple.pyi] [case testNewNamedTupleAccessingAttributes] -# flags: --python-version 3.6 from typing import NamedTuple class X(NamedTuple): @@ -31,7 +21,6 @@ x.z # E: "X" has no attribute "z" [builtins fixtures/tuple.pyi] [case testNewNamedTupleAttributesAreReadOnly] -# flags: --python-version 3.6 from typing import NamedTuple class X(NamedTuple): @@ -47,7 +36,6 @@ a.x = 5 # E: Property "x" defined in "X" is read-only [builtins fixtures/tuple.pyi] [case testNewNamedTupleCreateWithPositionalArguments] -# flags: --python-version 3.6 from typing import NamedTuple class X(NamedTuple): @@ -62,7 +50,6 @@ x = X(1, '2', 3) # E: Too many arguments for "X" [builtins fixtures/tuple.pyi] [case testNewNamedTupleShouldBeSingleBase] -# flags: --python-version 3.6 from typing import NamedTuple class A: ... @@ -71,7 +58,6 @@ class X(NamedTuple, A): # E: NamedTuple should be a single base [builtins fixtures/tuple.pyi] [case testCreateNewNamedTupleWithKeywordArguments] -# flags: --python-version 3.6 from typing import NamedTuple class X(NamedTuple): @@ -85,7 +71,6 @@ x = X(y='x') # E: Missing positional argument "x" in call to "X" [builtins fixtures/tuple.pyi] [case testNewNamedTupleCreateAndUseAsTuple] -# flags: --python-version 3.6 from typing import NamedTuple class X(NamedTuple): @@ -98,7 +83,6 @@ a, b, c = x # E: Need more than 2 values to unpack (3 expected) [builtins fixtures/tuple.pyi] [case testNewNamedTupleWithItemTypes] -# flags: --python-version 3.6 from typing import NamedTuple class N(NamedTuple): @@ -116,7 +100,6 @@ if int(): [builtins fixtures/tuple.pyi] [case testNewNamedTupleConstructorArgumentTypes] -# flags: --python-version 3.6 from typing import NamedTuple class N(NamedTuple): @@ -130,7 +113,6 @@ N(b='x', a=1) [builtins fixtures/tuple.pyi] [case testNewNamedTupleAsBaseClass] -# flags: --python-version 3.6 from typing import NamedTuple class N(NamedTuple): @@ -151,7 +133,6 @@ if int(): [builtins fixtures/tuple.pyi] [case testNewNamedTupleSelfTypeWithNamedTupleAsBase] -# flags: --python-version 3.6 from typing import NamedTuple class A(NamedTuple): @@ -172,7 +153,6 @@ class B(A): [out] [case testNewNamedTupleTypeReferenceToClassDerivedFrom] -# flags: --python-version 3.6 from typing import NamedTuple class A(NamedTuple): @@ -194,7 +174,6 @@ class B(A): [builtins fixtures/tuple.pyi] [case testNewNamedTupleSubtyping] -# flags: --python-version 3.6 from typing import NamedTuple, Tuple class A(NamedTuple): @@ -222,7 +201,6 @@ if int(): [builtins fixtures/tuple.pyi] [case testNewNamedTupleSimpleTypeInference] -# flags: --python-version 3.6 from typing import NamedTuple, Tuple class A(NamedTuple): @@ -239,7 +217,6 @@ a = (1,) # E: Incompatible types in assignment (expression has type "Tuple[int] [builtins fixtures/list.pyi] [case testNewNamedTupleMissingClassAttribute] -# flags: --python-version 3.6 from typing import NamedTuple class MyNamedTuple(NamedTuple): @@ -250,7 +227,6 @@ MyNamedTuple.x # E: "Type[MyNamedTuple]" has no attribute "x" [builtins fixtures/tuple.pyi] [case testNewNamedTupleEmptyItems] -# flags: --python-version 3.6 from typing import NamedTuple class A(NamedTuple): @@ -258,7 +234,6 @@ class A(NamedTuple): [builtins fixtures/tuple.pyi] [case testNewNamedTupleForwardRef] -# flags: --python-version 3.6 from typing import NamedTuple class A(NamedTuple): @@ -271,7 +246,6 @@ a = A(1) # E: Argument 1 to "A" has incompatible type "int"; expected "B" [builtins fixtures/tuple.pyi] [case testNewNamedTupleProperty36] -# flags: --python-version 3.6 from typing import NamedTuple class A(NamedTuple): @@ -288,7 +262,6 @@ C(2).b [builtins fixtures/property.pyi] [case testNewNamedTupleAsDict] -# flags: --python-version 3.6 from typing import NamedTuple, Any class X(NamedTuple): @@ -301,7 +274,6 @@ reveal_type(x._asdict()) # N: Revealed type is "builtins.dict[builtins.str, Any [builtins fixtures/dict.pyi] [case testNewNamedTupleReplaceTyped] -# flags: --python-version 3.6 from typing import NamedTuple class X(NamedTuple): @@ -315,7 +287,6 @@ x._replace(y=5) # E: Argument "y" to "_replace" of "X" has incompatible type "i [builtins fixtures/tuple.pyi] [case testNewNamedTupleFields] -# flags: --python-version 3.6 from typing import NamedTuple class X(NamedTuple): @@ -325,12 +296,14 @@ class X(NamedTuple): reveal_type(X._fields) # N: Revealed type is "Tuple[builtins.str, builtins.str]" reveal_type(X._field_types) # N: Revealed type is "builtins.dict[builtins.str, Any]" reveal_type(X._field_defaults) # N: Revealed type is "builtins.dict[builtins.str, Any]" -reveal_type(X.__annotations__) # N: Revealed type is "builtins.dict[builtins.str, Any]" -[builtins fixtures/dict.pyi] +# In typeshed's stub for builtins.pyi, __annotations__ is `dict[str, Any]`, +# but it's inferred as `Mapping[str, object]` here due to the fixture we're using +reveal_type(X.__annotations__) # N: Revealed type is "typing.Mapping[builtins.str, builtins.object]" + +[builtins fixtures/dict-full.pyi] [case testNewNamedTupleUnit] -# flags: --python-version 3.6 from typing import NamedTuple class X(NamedTuple): @@ -342,7 +315,6 @@ x._fields[0] # E: Tuple index out of range [builtins fixtures/tuple.pyi] [case testNewNamedTupleJoinNamedTuple] -# flags: --python-version 3.6 from typing import NamedTuple class X(NamedTuple): @@ -357,7 +329,6 @@ reveal_type([X(3, 'b'), Y(1, 'a')]) # N: Revealed type is "builtins.list[Tuple[ [builtins fixtures/list.pyi] [case testNewNamedTupleJoinTuple] -# flags: --python-version 3.6 from typing import NamedTuple class X(NamedTuple): @@ -370,7 +341,6 @@ reveal_type([X(1, 'a'), (3, 'b')]) # N: Revealed type is "builtins.list[Tuple[b [builtins fixtures/list.pyi] [case testNewNamedTupleWithTooManyArguments] -# flags: --python-version 3.6 from typing import NamedTuple class X(NamedTuple): @@ -380,25 +350,17 @@ class X(NamedTuple): [builtins fixtures/tuple.pyi] [case testNewNamedTupleWithInvalidItems2] -# flags: --python-version 3.6 import typing class X(typing.NamedTuple): x: int - y = 1 - x.x: int + y = 1 # E: Invalid statement in NamedTuple definition; expected "field_name: field_type [= default]" + x.x: int # E: Invalid statement in NamedTuple definition; expected "field_name: field_type [= default]" z: str = 'z' - aa: int - -[out] -main:6: error: Invalid statement in NamedTuple definition; expected "field_name: field_type [= default]" -main:7: error: Invalid statement in NamedTuple definition; expected "field_name: field_type [= default]" -main:9: error: Non-default NamedTuple fields cannot follow default fields - + aa: int # E: Non-default NamedTuple fields cannot follow default fields [builtins fixtures/list.pyi] [case testNewNamedTupleWithoutTypesSpecified] -# flags: --python-version 3.6 from typing import NamedTuple class X(NamedTuple): @@ -407,7 +369,6 @@ class X(NamedTuple): [builtins fixtures/tuple.pyi] [case testTypeUsingTypeCNamedTuple] -# flags: --python-version 3.6 from typing import NamedTuple, Type class N(NamedTuple): @@ -415,13 +376,10 @@ class N(NamedTuple): y: str def f(a: Type[N]): - a() + a() # E: Missing positional arguments "x", "y" in call to "N" [builtins fixtures/list.pyi] -[out] -main:9: error: Missing positional arguments "x", "y" in call to "N" [case testNewNamedTupleWithDefaults] -# flags: --python-version 3.6 from typing import List, NamedTuple, Optional class X(NamedTuple): @@ -461,7 +419,6 @@ UserDefined(1) # E: Argument 1 to "UserDefined" has incompatible type "int"; ex [builtins fixtures/list.pyi] [case testNewNamedTupleWithDefaultsStrictOptional] -# flags: --strict-optional --python-version 3.6 from typing import List, NamedTuple, Optional class HasNone(NamedTuple): @@ -480,7 +437,6 @@ class CannotBeNone(NamedTuple): [builtins fixtures/list.pyi] [case testNewNamedTupleWrongType] -# flags: --python-version 3.6 from typing import NamedTuple class X(NamedTuple): @@ -489,7 +445,6 @@ class X(NamedTuple): [builtins fixtures/tuple.pyi] [case testNewNamedTupleErrorInDefault] -# flags: --python-version 3.6 from typing import NamedTuple class X(NamedTuple): @@ -497,7 +452,6 @@ class X(NamedTuple): [builtins fixtures/tuple.pyi] [case testNewNamedTupleInheritance] -# flags: --python-version 3.6 from typing import NamedTuple class X(NamedTuple): diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index d5fb830487e8..983cb8454a05 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -8,8 +8,8 @@ class A: class B: def bar(self, x: 'B', y: A) -> None: pass -a = None # type: A -b = None # type: B +a: A +b: B a.foo(B()) # E: Argument 1 to "foo" of "A" has incompatible type "B"; expected "A" a.bar(B(), A()) # E: "A" has no attribute "bar" @@ -23,7 +23,7 @@ class A: def bar(self, x: 'B') -> None: pass class B(A): pass -a = None # type: A +a: A a.foo(A()) a.foo(B()) a.bar(A()) # E: Argument 1 to "bar" of "A" has incompatible type "A"; expected "B" @@ -34,7 +34,7 @@ class A: def foo(self, x: 'B') -> None: pass class B(A): pass -a = None # type: B +a: B a.foo(A()) # Fail a.foo(B()) @@ -46,7 +46,7 @@ main:6: error: Argument 1 to "foo" of "A" has incompatible type "A"; expected "B class A: def foo(self, x: 'A') -> None: pass -a = None # type: A +a: A a.foo() # Fail a.foo(object(), A()) # Fail [out] @@ -103,7 +103,8 @@ main:5: error: "A" has no attribute "g" import typing class A: def f(self): pass -A().f = None # E: Cannot assign to a method +A().f = None # E: Cannot assign to a method \ + # E: Incompatible types in assignment (expression has type "None", variable has type "Callable[[], Any]") [case testOverrideAttributeWithMethod] @@ -115,7 +116,11 @@ class Base: __hash__ = None class Derived(Base): - def __hash__(self) -> int: # E: Signature of "__hash__" incompatible with supertype "Base" + def __hash__(self) -> int: # E: Signature of "__hash__" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: None \ + # N: Subclass: \ + # N: def __hash__(self) -> int pass # Correct: @@ -157,7 +162,11 @@ class Base: class Derived(Base): - def partial_type(self) -> int: # E: Signature of "partial_type" incompatible with supertype "Base" + def partial_type(self) -> int: # E: Signature of "partial_type" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: List[Any] \ + # N: Subclass: \ + # N: def partial_type(self) -> int ... @@ -194,8 +203,8 @@ class A: self.a = aa self.b = bb class B: pass -a = None # type: A -b = None # type: B +a: A +b: B a.a = b # Fail a.b = a # Fail b.a # Fail @@ -209,8 +218,8 @@ main:11: error: "B" has no attribute "a" [case testExplicitAttributeInBody] class A: - x = None # type: A -a = None # type: A + x: A +a: A a.x = object() # E: Incompatible types in assignment (expression has type "object", variable has type "A") a.x = A() @@ -387,7 +396,7 @@ main:7: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incom [case testEqMethodsOverridingWithNonObjects] class A: def __eq__(self, other: A) -> bool: pass # Fail -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [out] main:2: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object" main:2: note: This violates the Liskov substitution principle @@ -450,9 +459,10 @@ class A: def g(self) -> 'A': pass class B(A): def f(self) -> A: pass # Fail - def g(self) -> None: pass + def g(self) -> None: pass # Fail [out] main:6: error: Return type "A" of "f" incompatible with return type "None" in supertype "A" +main:7: error: Return type "None" of "g" incompatible with return type "A" in supertype "A" [case testOverride__new__WithDifferentSignature] class A: @@ -567,11 +577,45 @@ class A: class B(A): @dec - def f(self) -> int: pass # E: Signature of "f" incompatible with supertype "A" - def g(self) -> int: pass # E: Signature of "g" incompatible with supertype "A" + def f(self) -> int: pass # E: Signature of "f" incompatible with supertype "A" \ + # N: Superclass: \ + # N: def f(self) -> str \ + # N: Subclass: \ + # N: str + def g(self) -> int: pass # E: Signature of "g" incompatible with supertype "A" \ + # N: Superclass: \ + # N: str \ + # N: Subclass: \ + # N: def g(self) -> int @dec def h(self) -> str: pass +[case testOverrideIncompatibleWithMultipleSupertypes] +class A: + def f(self, *, a: int) -> None: + return + +class B(A): + def f(self, *, b: int) -> None: # E: Signature of "f" incompatible with supertype "A" \ + # N: Superclass: \ + # N: def f(self, *, a: int) -> None \ + # N: Subclass: \ + # N: def f(self, *, b: int) -> None + return + +class C(B): + def f(self, *, c: int) -> None: # E: Signature of "f" incompatible with supertype "B" \ + # N: Superclass: \ + # N: def f(self, *, b: int) -> None \ + # N: Subclass: \ + # N: def f(self, *, c: int) -> None \ + # E: Signature of "f" incompatible with supertype "A" \ + # N: Superclass: \ + # N: def f(self, *, a: int) -> None \ + # N: Subclass: \ + # N: def f(self, *, c: int) -> None + return + [case testOverrideStaticMethodWithStaticMethod] class A: @staticmethod @@ -865,7 +909,7 @@ b = __init__() # type: B # E: Incompatible types in assignment (expression has t from typing import Any, cast class A: def __init__(self, a: 'A') -> None: pass -a = None # type: A +a: A a.__init__(a) # E: Accessing "__init__" on an instance is unsound, since instance.__init__ could be from an incompatible subclass (cast(Any, a)).__init__(a) @@ -891,7 +935,6 @@ if int(): b = D2() [case testConstructorJoinsWithCustomMetaclass] -# flags: --strict-optional from typing import TypeVar import abc @@ -975,13 +1018,14 @@ class A: A.f(A()) A.f(object()) # E: Argument 1 to "f" of "A" has incompatible type "object"; expected "A" A.f() # E: Missing positional argument "self" in call to "f" of "A" -A.f(None, None) # E: Too many arguments for "f" of "A" +A.f(None, None) # E: Too many arguments for "f" of "A" \ + # E: Argument 1 to "f" of "A" has incompatible type "None"; expected "A" [case testAccessAttributeViaClass] import typing class B: pass class A: - x = None # type: A + x: A a = A.x # type: A b = A.x # type: B # E: Incompatible types in assignment (expression has type "A", variable has type "B") @@ -1018,7 +1062,7 @@ A.f() # E: Missing positional argument "self" in call to "f" of "A" import typing class B: pass class A: - x = None # type: B + x: B A.x = B() A.x = object() # E: Incompatible types in assignment (expression has type "object", variable has type "B") @@ -1035,8 +1079,8 @@ A.x = A() # E: Incompatible types in assignment (expression has type "A", vari class B: pass class A: def __init__(self, b: B) -> None: pass -a = None # type: A -b = None # type: B +a: A +b: B A.__init__(a, b) A.__init__(b, b) # E: Argument 1 to "__init__" of "A" has incompatible type "B"; expected "A" A.__init__(a, a) # E: Argument 2 to "__init__" of "A" has incompatible type "A"; expected "B" @@ -1045,13 +1089,15 @@ A.__init__(a, a) # E: Argument 2 to "__init__" of "A" has incompatible type "A"; import typing class A: def f(self): pass -A.f = None # E: Cannot assign to a method +A.f = None # E: Cannot assign to a method \ + # E: Incompatible types in assignment (expression has type "None", variable has type "Callable[[A], Any]") [case testAssignToNestedClassViaClass] import typing class A: class B: pass -A.B = None # E: Cannot assign to a type +A.B = None # E: Cannot assign to a type \ + # E: Incompatible types in assignment (expression has type "None", variable has type "Type[B]") [targets __main__] [case testAccessingClassAttributeWithTypeInferenceIssue] @@ -1097,7 +1143,7 @@ A[int, int].x # E: Access to generic instance variables via class is ambiguous def f() -> None: class A: def g(self) -> None: pass - a = None # type: A + a: A a.g() a.g(a) # E: Too many arguments for "g" of "A" [targets __main__, __main__.f] @@ -1116,7 +1162,7 @@ def test() -> None: reveal_type(x) # N: Revealed type is "T`-1" reveal_type(x.returns_int()) # N: Revealed type is "builtins.int" return foo - reveal_type(Foo.bar) # N: Revealed type is "def [T <: __main__.Foo@5] (self: __main__.Foo@5, foo: T`-1) -> T`-1" + reveal_type(Foo.bar) # N: Revealed type is "def [T <: __main__.Foo@5] (self: __main__.Foo@5, foo: T`1) -> T`1" [case testGenericClassWithInvalidTypevarUseWithinFunction] from typing import TypeVar @@ -1158,7 +1204,7 @@ class A: def f() -> None: class A: pass - a = None # type: A + a: A if int(): a = A() a = object() # E: Incompatible types in assignment (expression has type "object", variable has type "A") @@ -1167,7 +1213,7 @@ def f() -> None: [case testExternalReferenceToClassWithinClass] class A: class B: pass -b = None # type: A.B +b: A.B if int(): b = A.B() if int(): @@ -1214,19 +1260,19 @@ reveal_type(Foo().Meta.name) # N: Revealed type is "builtins.str" class A: def __init__(self): - self.x = None # type: int # N: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs -a = None # type: A + self.x: int # N: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs +a: A a.x = 1 a.x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") [case testAccessAttributeDeclaredInInitBeforeDeclaration] -a = None # type: A +a: A a.x = 1 a.x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") class A: def __init__(self): - self.x = None # type: int # N: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs + self.x: int # N: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs -- Special cases @@ -1395,6 +1441,13 @@ main:8: note: def f(cls) -> None main:8: note: Subclass: main:8: note: def f(self) -> None +[case testClassMethodAndStaticMethod] +class C: + @classmethod # E: Cannot have both classmethod and staticmethod + @staticmethod + def foo(cls) -> None: pass +[builtins fixtures/classmethod.pyi] + -- Properties -- ---------- @@ -1575,7 +1628,6 @@ a = A() reveal_type(a.f) # N: Revealed type is "__main__.D" [case testAccessingDescriptorFromClass] -# flags: --strict-optional from d import D, Base class A(Base): f = D() @@ -1593,7 +1645,6 @@ class D: [builtins fixtures/bool.pyi] [case testAccessingDescriptorFromClassWrongBase] -# flags: --strict-optional from d import D, Base class A: f = D() @@ -1610,13 +1661,13 @@ class D: def __get__(self, inst: Base, own: Type[Base]) -> str: pass [builtins fixtures/bool.pyi] [out] -main:5: error: Argument 2 to "__get__" of "D" has incompatible type "Type[A]"; expected "Type[Base]" -main:5: note: Revealed type is "d.D" -main:6: error: No overload variant of "__get__" of "D" matches argument types "A", "Type[A]" -main:6: note: Possible overload variants: -main:6: note: def __get__(self, inst: None, own: Type[Base]) -> D -main:6: note: def __get__(self, inst: Base, own: Type[Base]) -> str -main:6: note: Revealed type is "Any" +main:4: error: Argument 2 to "__get__" of "D" has incompatible type "Type[A]"; expected "Type[Base]" +main:4: note: Revealed type is "d.D" +main:5: error: No overload variant of "__get__" of "D" matches argument types "A", "Type[A]" +main:5: note: Possible overload variants: +main:5: note: def __get__(self, inst: None, own: Type[Base]) -> D +main:5: note: def __get__(self, inst: Base, own: Type[Base]) -> str +main:5: note: Revealed type is "Any" [case testAccessingGenericNonDataDescriptor] from typing import TypeVar, Type, Generic, Any @@ -1648,7 +1699,6 @@ a.g = '' a.g = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "str") [case testAccessingGenericDescriptorFromClass] -# flags: --strict-optional from d import D class A: f = D(10) # type: D[A, int] @@ -1670,7 +1720,6 @@ class D(Generic[T, V]): [builtins fixtures/bool.pyi] [case testAccessingGenericDescriptorFromInferredClass] -# flags: --strict-optional from typing import Type from d import D class A: @@ -1691,11 +1740,10 @@ class D(Generic[T, V]): def __get__(self, inst: T, own: Type[T]) -> V: pass [builtins fixtures/bool.pyi] [out] -main:8: note: Revealed type is "d.D[__main__.A, builtins.int]" -main:9: note: Revealed type is "d.D[__main__.A, builtins.str]" +main:7: note: Revealed type is "d.D[__main__.A, builtins.int]" +main:8: note: Revealed type is "d.D[__main__.A, builtins.str]" [case testAccessingGenericDescriptorFromClassBadOverload] -# flags: --strict-optional from d import D class A: f = D(10) # type: D[A, int] @@ -1712,11 +1760,11 @@ class D(Generic[T, V]): def __get__(self, inst: T, own: Type[T]) -> V: pass [builtins fixtures/bool.pyi] [out] -main:5: error: No overload variant of "__get__" of "D" matches argument types "None", "Type[A]" -main:5: note: Possible overload variants: -main:5: note: def __get__(self, inst: None, own: None) -> D[A, int] -main:5: note: def __get__(self, inst: A, own: Type[A]) -> int -main:5: note: Revealed type is "Any" +main:4: error: No overload variant of "__get__" of "D" matches argument types "None", "Type[A]" +main:4: note: Possible overload variants: +main:4: note: def __get__(self, inst: None, own: None) -> D[A, int] +main:4: note: def __get__(self, inst: A, own: Type[A]) -> int +main:4: note: Revealed type is "Any" [case testAccessingNonDataDescriptorSubclass] from typing import Any @@ -1911,8 +1959,8 @@ from typing import _promote class A: pass @_promote(A) class B: pass -a = None # type: A -b = None # type: B +a: A +b: B if int(): b = a # E: Incompatible types in assignment (expression has type "A", variable has type "B") a = b @@ -1925,8 +1973,8 @@ class A: pass class B: pass @_promote(B) class C: pass -a = None # type: A -c = None # type: C +a: A +c: C if int(): c = a # E: Incompatible types in assignment (expression has type "A", variable has type "C") a = c @@ -1970,7 +2018,7 @@ tmp/foo.pyi:5: note: @overload tmp/foo.pyi:5: note: def __add__(self, int, /) -> int tmp/foo.pyi:5: note: @overload tmp/foo.pyi:5: note: def __add__(self, str, /) -> str -tmp/foo.pyi:5: note: Overloaded operator methods cannot have wider argument types in overrides +tmp/foo.pyi:5: note: Overloaded operator methods can't have wider argument types in overrides [case testOperatorMethodOverrideWideningArgumentType] import typing @@ -2089,7 +2137,7 @@ tmp/foo.pyi:8: note: @overload tmp/foo.pyi:8: note: def __add__(self, str, /) -> A tmp/foo.pyi:8: note: @overload tmp/foo.pyi:8: note: def __add__(self, type, /) -> A -tmp/foo.pyi:8: note: Overloaded operator methods cannot have wider argument types in overrides +tmp/foo.pyi:8: note: Overloaded operator methods can't have wider argument types in overrides [case testOverloadedOperatorMethodOverrideWithSwitchedItemOrder] from foo import * @@ -2685,8 +2733,8 @@ main:8: note: This violates the Liskov substitution principle main:8: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides [case testGetattribute] - -a, b = None, None # type: A, B +a: A +b: B class A: def __getattribute__(self, x: str) -> A: return A() @@ -2741,8 +2789,8 @@ main:4: error: Invalid signature "Callable[[B, A], B]" for "__getattribute__" main:6: error: Invalid signature "Callable[[C, str, str], C]" for "__getattribute__" [case testGetattr] - -a, b = None, None # type: A, B +a: A +b: B class A: def __getattr__(self, x: str) -> A: return A() @@ -3023,8 +3071,9 @@ C(bar='') # E: Unexpected keyword argument "bar" for "C" [builtins fixtures/__new__.pyi] [case testClassWith__new__AndCompatibilityWithType] +from typing import Optional class C: - def __new__(cls, foo: int = None) -> 'C': + def __new__(cls, foo: Optional[int] = None) -> 'C': obj = object.__new__(cls) return obj def f(x: type) -> None: pass @@ -3464,9 +3513,9 @@ class ProUser(User): pass class BasicUser(User): pass U = TypeVar('U', bound=Union[ProUser, BasicUser]) def process(cls: Type[U]): - cls.foo() # E: "Type[U]" has no attribute "foo" + cls.foo() obj = cls() - cls.bar(obj) # E: "Type[U]" has no attribute "bar" + cls.bar(obj) cls.mro() # Defined in class type cls.error # E: "Type[U]" has no attribute "error" [builtins fixtures/classmethod.pyi] @@ -3547,15 +3596,15 @@ main:7: note: Revealed type is "builtins.list[Type[__main__.B]]" [case testTypeEquivalentTypeAny] from typing import Type, Any -a = None # type: Type[Any] +a: Type[Any] b = a # type: type -x = None # type: type +x: type y = x # type: Type[Any] class C: ... -p = None # type: type +p: type q = p # type: Type[C] [builtins fixtures/list.pyi] @@ -3565,9 +3614,9 @@ q = p # type: Type[C] from typing import Type, Any, TypeVar, Generic class C: ... -x = None # type: type -y = None # type: Type[Any] -z = None # type: Type[C] +x: type +y: Type[Any] +z: Type[C] lst = [x, y, z] reveal_type(lst) # N: Revealed type is "builtins.list[builtins.type]" @@ -4075,12 +4124,31 @@ int.__eq__(3, 4) main:33: error: Too few arguments for "__eq__" of "int" main:33: error: Unsupported operand types for == ("int" and "Type[int]") -[case testMroSetAfterError] -class C(str, str): - foo = 0 - bar = foo -[out] -main:1: error: Duplicate base class "str" +[case testDupBaseClasses] +class A: + def method(self) -> str: ... + +class B(A, A): # E: Duplicate base class "A" + attr: int + +b: B + +reveal_type(b.method()) # N: Revealed type is "Any" +reveal_type(b.missing()) # N: Revealed type is "Any" +reveal_type(b.attr) # N: Revealed type is "builtins.int" + +[case testDupBaseClassesGeneric] +from typing import Generic, TypeVar + +T = TypeVar('T') +class A(Generic[T]): + def method(self) -> T: ... + +class B(A[int], A[str]): # E: Duplicate base class "A" + attr: int + +reveal_type(B().method()) # N: Revealed type is "Any" +reveal_type(B().attr) # N: Revealed type is "builtins.int" [case testCannotDetermineMro] class A: pass @@ -4216,11 +4284,12 @@ class A: def a(self) -> None: pass b = 1 class B(A): - a = 1 - def b(self) -> None: pass -[out] -main:5: error: Incompatible types in assignment (expression has type "int", base class "A" defined the type as "Callable[[A], None]") -main:6: error: Signature of "b" incompatible with supertype "A" + a = 1 # E: Incompatible types in assignment (expression has type "int", base class "A" defined the type as "Callable[[A], None]") + def b(self) -> None: pass # E: Signature of "b" incompatible with supertype "A" \ + # N: Superclass: \ + # N: int \ + # N: Subclass: \ + # N: def b(self) -> None [case testVariableProperty] class A: @@ -4471,6 +4540,39 @@ def f(TA: Type[A]): reveal_type(TA) # N: Revealed type is "Type[__main__.A]" reveal_type(TA.x) # N: Revealed type is "builtins.int" +[case testMetaclassConflictingInstanceVars] +from typing import ClassVar + +class Meta(type): + foo: int + bar: int + eggs: ClassVar[int] = 42 + spam: ClassVar[int] = 42 + +class Foo(metaclass=Meta): + foo: str + bar: ClassVar[str] = 'bar' + eggs: str + spam: ClassVar[str] = 'spam' + +reveal_type(Foo.foo) # N: Revealed type is "builtins.int" +reveal_type(Foo.bar) # N: Revealed type is "builtins.str" +reveal_type(Foo.eggs) # N: Revealed type is "builtins.int" +reveal_type(Foo.spam) # N: Revealed type is "builtins.str" + +class MetaSub(Meta): ... + +class Bar(metaclass=MetaSub): + foo: str + bar: ClassVar[str] = 'bar' + eggs: str + spam: ClassVar[str] = 'spam' + +reveal_type(Bar.foo) # N: Revealed type is "builtins.int" +reveal_type(Bar.bar) # N: Revealed type is "builtins.str" +reveal_type(Bar.eggs) # N: Revealed type is "builtins.int" +reveal_type(Bar.spam) # N: Revealed type is "builtins.str" + [case testSubclassMetaclass] class M1(type): x = 0 @@ -4524,6 +4626,72 @@ WithMeta().a # E: "WithMeta" has no attribute "a" t: Type[WithMeta] t.unknown # OK +[case testUnpackIterableClassWithOverloadedIter] +from typing import Generic, overload, Iterator, TypeVar, Union + +AnyNum = TypeVar('AnyNum', int, float) + +class Foo(Generic[AnyNum]): + @overload + def __iter__(self: Foo[int]) -> Iterator[float]: ... + @overload + def __iter__(self: Foo[float]) -> Iterator[int]: ... + def __iter__(self) -> Iterator[Union[float, int]]: + ... + +a, b, c = Foo[int]() +reveal_type(a) # N: Revealed type is "builtins.float" +reveal_type(b) # N: Revealed type is "builtins.float" +reveal_type(c) # N: Revealed type is "builtins.float" + +x, y = Foo[float]() +reveal_type(x) # N: Revealed type is "builtins.int" +reveal_type(y) # N: Revealed type is "builtins.int" +[builtins fixtures/list.pyi] + +[case testUnpackIterableClassWithOverloadedIter2] +from typing import Union, TypeVar, Generic, overload, Iterator + +X = TypeVar('X') + +class Foo(Generic[X]): + @overload + def __iter__(self: Foo[str]) -> Iterator[int]: ... # type: ignore + @overload + def __iter__(self: Foo[X]) -> Iterator[str]: ... + def __iter__(self) -> Iterator[Union[int, str]]: + ... + +a, b, c = Foo[str]() +reveal_type(a) # N: Revealed type is "builtins.int" +reveal_type(b) # N: Revealed type is "builtins.int" +reveal_type(c) # N: Revealed type is "builtins.int" + +x, y = Foo[float]() +reveal_type(x) # N: Revealed type is "builtins.str" +reveal_type(y) # N: Revealed type is "builtins.str" +[builtins fixtures/list.pyi] + +[case testUnpackIterableRegular] +from typing import TypeVar, Generic, Iterator + +X = TypeVar('X') + +class Foo(Generic[X]): + def __iter__(self) -> Iterator[X]: + ... + +a, b = Foo[int]() +reveal_type(a) # N: Revealed type is "builtins.int" +reveal_type(b) # N: Revealed type is "builtins.int" +[builtins fixtures/list.pyi] + +[case testUnpackNotIterableClass] +class Foo: ... + +a, b, c = Foo() # E: "Foo" object is not iterable +[builtins fixtures/list.pyi] + [case testMetaclassIterable] from typing import Iterable, Iterator @@ -4826,7 +4994,7 @@ a[0]() # E: "int" not callable [file m.py] from typing import Tuple -a = None # type: A +a: A class A(Tuple[int, str]): pass [builtins fixtures/tuple.pyi] @@ -4834,12 +5002,13 @@ class A(Tuple[int, str]): pass -- ----------------------- [case testCrashOnSelfRecursiveNamedTupleVar] -# flags: --disable-recursive-aliases from typing import NamedTuple -N = NamedTuple('N', [('x', N)]) # E: Cannot resolve name "N" (possible cyclic definition) -n: N -reveal_type(n) # N: Revealed type is "Tuple[Any, fallback=__main__.N]" +def test() -> None: + N = NamedTuple('N', [('x', N)]) # E: Cannot resolve name "N" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope + n: N + reveal_type(n) # N: Revealed type is "Tuple[Any, fallback=__main__.N@4]" [builtins fixtures/tuple.pyi] [case testCrashOnSelfRecursiveTypedDictVar] @@ -4864,18 +5033,20 @@ lst = [n, m] [builtins fixtures/isinstancelist.pyi] [case testCorrectJoinOfSelfRecursiveTypedDicts] -# flags: --disable-recursive-aliases from mypy_extensions import TypedDict -class N(TypedDict): - x: N # E: Cannot resolve name "N" (possible cyclic definition) -class M(TypedDict): - x: M # E: Cannot resolve name "M" (possible cyclic definition) - -n: N -m: M -lst = [n, m] -reveal_type(lst[0]['x']) # N: Revealed type is "Any" +def test() -> None: + class N(TypedDict): + x: N # E: Cannot resolve name "N" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope + class M(TypedDict): + x: M # E: Cannot resolve name "M" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope + + n: N + m: M + lst = [n, m] + reveal_type(lst[0]['x']) # N: Revealed type is "Any" [builtins fixtures/isinstancelist.pyi] [case testCrashInForwardRefToNamedTupleWithIsinstance] @@ -4934,6 +5105,7 @@ Foos = NewType('Foos', List[Foo]) # type: ignore def frob(foos: Dict[Any, Foos]) -> None: foo = foos.get(1) + assert foo dict(foo) [builtins fixtures/dict.pyi] [out] @@ -4948,6 +5120,7 @@ x: C class C: def frob(self, foos: Dict[Any, Foos]) -> None: foo = foos.get(1) + assert foo dict(foo) reveal_type(x.frob) # N: Revealed type is "def (foos: builtins.dict[Any, __main__.Foos])" @@ -6093,7 +6266,11 @@ import a [file b.py] import a class Sub(a.Base): - def x(self) -> int: pass # E: Signature of "x" incompatible with supertype "Base" + def x(self) -> int: pass # E: Signature of "x" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: int \ + # N: Subclass: \ + # N: def x(self) -> int [file a.py] import b @@ -6109,7 +6286,11 @@ import a import c class Sub(a.Base): @c.deco - def x(self) -> int: pass # E: Signature of "x" incompatible with supertype "Base" + def x(self) -> int: pass # E: Signature of "x" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: int \ + # N: Subclass: \ + # N: def x(*Any, **Any) -> Tuple[int, int] [file a.py] import b @@ -6131,7 +6312,11 @@ import a import c class Sub(a.Base): @c.deco - def x(self) -> int: pass # E: Signature of "x" incompatible with supertype "Base" + def x(self) -> int: pass # E: Signature of "x" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: int \ + # N: Subclass: \ + # N: def x(*Any, **Any) -> Tuple[int, int] [file a.py] import b @@ -6296,7 +6481,6 @@ def deco(f: Callable[..., T]) -> Callable[..., Tuple[T, int]]: ... [out] [case testOptionalDescriptorsBinder] -# flags: --strict-optional from typing import Type, TypeVar, Optional T = TypeVar('T') @@ -6510,7 +6694,6 @@ class C(Generic[T]): [builtins fixtures/isinstancelist.pyi] [case testIsInstanceTypeSubclass] -# flags: --strict-optional from typing import Type, Optional class Base: ... class One(Base): @@ -7537,10 +7720,14 @@ class D: def __new__(cls) -> NoReturn: ... def __init__(self) -> NoReturn: ... -reveal_type(A()) # N: Revealed type is "" -reveal_type(B()) # N: Revealed type is "" -reveal_type(C()) # N: Revealed type is "" -reveal_type(D()) # N: Revealed type is "" +if object(): + reveal_type(A()) # N: Revealed type is "Never" +if object(): + reveal_type(B()) # N: Revealed type is "Never" +if object(): + reveal_type(C()) # N: Revealed type is "Never" +if object(): + reveal_type(D()) # N: Revealed type is "Never" [case testOverloadedNewAndInitNoReturn] from typing import NoReturn, overload @@ -7579,13 +7766,20 @@ class D: def __init__(self, a: int) -> None: ... def __init__(self, a: int = ...) -> None: ... -reveal_type(A()) # N: Revealed type is "" +if object(): + reveal_type(A()) # N: Revealed type is "Never" reveal_type(A(1)) # N: Revealed type is "__main__.A" -reveal_type(B()) # N: Revealed type is "" + +if object(): + reveal_type(B()) # N: Revealed type is "Never" reveal_type(B(1)) # N: Revealed type is "__main__.B" -reveal_type(C()) # N: Revealed type is "" + +if object(): + reveal_type(C()) # N: Revealed type is "Never" reveal_type(C(1)) # N: Revealed type is "__main__.C" -reveal_type(D()) # N: Revealed type is "" + +if object(): + reveal_type(D()) # N: Revealed type is "Never" reveal_type(D(1)) # N: Revealed type is "__main__.D" [case testClassScopeImportWithWrapperAndError] @@ -7614,13 +7808,29 @@ class Parent: foobar = TypeVar("foobar") class Child(Parent): - def foo(self, val: int) -> int: # E: Signature of "foo" incompatible with supertype "Parent" + def foo(self, val: int) -> int: # E: Signature of "foo" incompatible with supertype "Parent" \ + # N: Superclass: \ + # N: None \ + # N: Subclass: \ + # N: def foo(self, val: int) -> int return val - def bar(self, val: str) -> str: # E: Signature of "bar" incompatible with supertype "Parent" + def bar(self, val: str) -> str: # E: Signature of "bar" incompatible with supertype "Parent" \ + # N: Superclass: \ + # N: None \ + # N: Subclass: \ + # N: def bar(self, val: str) -> str return val - def baz(self, val: float) -> float: # E: Signature of "baz" incompatible with supertype "Parent" + def baz(self, val: float) -> float: # E: Signature of "baz" incompatible with supertype "Parent" \ + # N: Superclass: \ + # N: None \ + # N: Subclass: \ + # N: def baz(self, val: float) -> float return val - def foobar(self) -> bool: # E: Signature of "foobar" incompatible with supertype "Parent" + def foobar(self) -> bool: # E: Signature of "foobar" incompatible with supertype "Parent" \ + # N: Superclass: \ + # N: None \ + # N: Subclass: \ + # N: def foobar(self) -> bool return False x: Parent.foo = lambda: 5 @@ -7688,7 +7898,11 @@ class B: ... class C(B): @property - def foo(self) -> int: # E: Signature of "foo" incompatible with supertype "B" + def foo(self) -> int: # E: Signature of "foo" incompatible with supertype "B" \ + # N: Superclass: \ + # N: def foo(self) -> int \ + # N: Subclass: \ + # N: int ... [builtins fixtures/property.pyi] @@ -7698,7 +7912,11 @@ class B: def foo(self) -> int: ... class C(B): - def foo(self) -> int: # E: Signature of "foo" incompatible with supertype "B" + def foo(self) -> int: # E: Signature of "foo" incompatible with supertype "B" \ + # N: Superclass: \ + # N: int \ + # N: Subclass: \ + # N: def foo(self) -> int ... [builtins fixtures/property.pyi] @@ -7731,3 +7949,66 @@ class Element(Generic[_T]): class Bar(Foo): ... e: Element[Bar] reveal_type(e.elements) # N: Revealed type is "typing.Sequence[__main__.Element[__main__.Bar]]" + +[case testIterableUnpackingWithGetAttr] +from typing import Union, Tuple + +class C: + def __getattr__(self, name): + pass + +class D: + def f(self) -> C: + return C() + + def g(self) -> None: + # iter(x) looks up `__iter__` on the type of x rather than x itself, + # so this is correct behaviour. + # Instances of C should not be treated as being iterable, + # despite having a __getattr__ method + # that could allow for arbitrary attributes to be accessed on instances, + # since `type(C()).__iter__` still raises AttributeError at runtime, + # and that's what matters. + a, b = self.f() # E: "C" has no attribute "__iter__" (not iterable) +[builtins fixtures/tuple.pyi] + +[case testUsingNumbersType] +from numbers import Number, Complex, Real, Rational, Integral + +def f1(x: Number) -> None: pass +f1(1) # E: Argument 1 to "f1" has incompatible type "int"; expected "Number" \ + # N: Types from "numbers" aren't supported for static type checking \ + # N: See https://peps.python.org/pep-0484/#the-numeric-tower \ + # N: Consider using a protocol instead, such as typing.SupportsFloat + +def f2(x: Complex) -> None: pass +f2(1) # E: Argument 1 to "f2" has incompatible type "int"; expected "Complex" \ + # N: Types from "numbers" aren't supported for static type checking \ + # N: See https://peps.python.org/pep-0484/#the-numeric-tower \ + # N: Consider using a protocol instead, such as typing.SupportsFloat + +def f3(x: Real) -> None: pass +f3(1) # E: Argument 1 to "f3" has incompatible type "int"; expected "Real" \ + # N: Types from "numbers" aren't supported for static type checking \ + # N: See https://peps.python.org/pep-0484/#the-numeric-tower \ + # N: Consider using a protocol instead, such as typing.SupportsFloat + +def f4(x: Rational) -> None: pass +f4(1) # E: Argument 1 to "f4" has incompatible type "int"; expected "Rational" \ + # N: Types from "numbers" aren't supported for static type checking \ + # N: See https://peps.python.org/pep-0484/#the-numeric-tower \ + # N: Consider using a protocol instead, such as typing.SupportsFloat + +def f5(x: Integral) -> None: pass +f5(1) # E: Argument 1 to "f5" has incompatible type "int"; expected "Integral" \ + # N: Types from "numbers" aren't supported for static type checking \ + # N: See https://peps.python.org/pep-0484/#the-numeric-tower \ + # N: Consider using a protocol instead, such as typing.SupportsFloat + +[case testImplicitClassScopedNames] +class C: + reveal_type(__module__) # N: Revealed type is "builtins.str" + reveal_type(__qualname__) # N: Revealed type is "builtins.str" + def f(self) -> None: + __module__ # E: Name "__module__" is not defined + __qualname__ # E: Name "__qualname__" is not defined diff --git a/test-data/unit/check-columns.test b/test-data/unit/check-columns.test index 9691e6565689..44524b9df943 100644 --- a/test-data/unit/check-columns.test +++ b/test-data/unit/check-columns.test @@ -27,7 +27,6 @@ A().f(1, 1) # E:10: Argument 2 to "f" of "A" has incompatible type "int"; expect (A().f(1, 'hello', 'hi')) # E:2: Too many arguments for "f" of "A" [case testColumnsInvalidArgumentType] -# flags: --strict-optional def f(x: int, y: str) -> None: ... def g(*x: int) -> None: pass def h(**x: int) -> None: pass @@ -328,7 +327,7 @@ if int(): # TODO: It would be better to point to the type comment xyz = 0 # type: blurbnard blarb [out] -main:3:5: error: syntax error in type comment "blurbnard blarb" +main:3:5: error: Syntax error in type comment "blurbnard blarb" [case testColumnProperty] class A: @@ -386,10 +385,6 @@ f(g( )) x[0] [out] -main:2:10:2:10: error: Incompatible types in assignment (expression has type "str", variable has type "int") -main:6:3:6:3: error: Argument 1 to "f" has incompatible type "int"; expected "str" -main:8:1:8:1: error: Value of type "int" is not indexable -[out version>=3.8] main:2:10:2:17: error: Incompatible types in assignment (expression has type "str", variable has type "int") main:6:3:7:1: error: Argument 1 to "f" has incompatible type "int"; expected "str" main:8:1:8:4: error: Value of type "int" is not indexable diff --git a/test-data/unit/check-ctypes.test b/test-data/unit/check-ctypes.test index beb1afd779c0..1eefdd3c66c1 100644 --- a/test-data/unit/check-ctypes.test +++ b/test-data/unit/check-ctypes.test @@ -7,7 +7,7 @@ class MyCInt(ctypes.c_int): intarr4 = ctypes.c_int * 4 a = intarr4(1, ctypes.c_int(2), MyCInt(3), 4) intarr4(1, 2, 3, "invalid") # E: Array constructor argument 4 of type "str" is not convertible to the array element type "c_int" -reveal_type(a) # N: Revealed type is "ctypes.Array[ctypes.c_int]" +reveal_type(a) # N: Revealed type is "_ctypes.Array[ctypes.c_int]" reveal_type(a[0]) # N: Revealed type is "builtins.int" reveal_type(a[1:3]) # N: Revealed type is "builtins.list[builtins.int]" a[0] = 42 @@ -33,7 +33,7 @@ myintarr4 = MyCInt * 4 mya = myintarr4(1, 2, MyCInt(3), 4) myintarr4(1, ctypes.c_int(2), MyCInt(3), "invalid") # E: Array constructor argument 2 of type "c_int" is not convertible to the array element type "MyCInt" \ # E: Array constructor argument 4 of type "str" is not convertible to the array element type "MyCInt" -reveal_type(mya) # N: Revealed type is "ctypes.Array[__main__.MyCInt]" +reveal_type(mya) # N: Revealed type is "_ctypes.Array[__main__.MyCInt]" reveal_type(mya[0]) # N: Revealed type is "__main__.MyCInt" reveal_type(mya[1:3]) # N: Revealed type is "builtins.list[__main__.MyCInt]" mya[0] = 42 @@ -63,7 +63,7 @@ class MyCInt(ctypes.c_int): pass mya: ctypes.Array[Union[MyCInt, ctypes.c_uint]] -reveal_type(mya) # N: Revealed type is "ctypes.Array[Union[__main__.MyCInt, ctypes.c_uint]]" +reveal_type(mya) # N: Revealed type is "_ctypes.Array[Union[__main__.MyCInt, ctypes.c_uint]]" reveal_type(mya[0]) # N: Revealed type is "Union[__main__.MyCInt, builtins.int]" reveal_type(mya[1:3]) # N: Revealed type is "builtins.list[Union[__main__.MyCInt, builtins.int]]" # The behavior here is not strictly correct, but intentional. @@ -161,10 +161,10 @@ intarr4 = ctypes.c_int * 4 intarr6 = ctypes.c_int * 6 int_values = [1, 2, 3, 4] c_int_values = [ctypes.c_int(1), ctypes.c_int(2), ctypes.c_int(3), ctypes.c_int(4)] -reveal_type(intarr4(*int_values)) # N: Revealed type is "ctypes.Array[ctypes.c_int]" -reveal_type(intarr4(*c_int_values)) # N: Revealed type is "ctypes.Array[ctypes.c_int]" -reveal_type(intarr6(1, ctypes.c_int(2), *int_values)) # N: Revealed type is "ctypes.Array[ctypes.c_int]" -reveal_type(intarr6(1, ctypes.c_int(2), *c_int_values)) # N: Revealed type is "ctypes.Array[ctypes.c_int]" +reveal_type(intarr4(*int_values)) # N: Revealed type is "_ctypes.Array[ctypes.c_int]" +reveal_type(intarr4(*c_int_values)) # N: Revealed type is "_ctypes.Array[ctypes.c_int]" +reveal_type(intarr6(1, ctypes.c_int(2), *int_values)) # N: Revealed type is "_ctypes.Array[ctypes.c_int]" +reveal_type(intarr6(1, ctypes.c_int(2), *c_int_values)) # N: Revealed type is "_ctypes.Array[ctypes.c_int]" [typing fixtures/typing-medium.pyi] float_values = [1.0, 2.0, 3.0, 4.0] diff --git a/test-data/unit/check-custom-plugin.test b/test-data/unit/check-custom-plugin.test index d7beea0390e7..63529cf165ce 100644 --- a/test-data/unit/check-custom-plugin.test +++ b/test-data/unit/check-custom-plugin.test @@ -684,12 +684,16 @@ plugins=/test-data/unit/plugins/dyn_class.py [case testDynamicClassHookFromClassMethod] # flags: --config-file tmp/mypy.ini -from mod import QuerySet, Manager +from mod import QuerySet, Manager, GenericQuerySet MyManager = Manager.from_queryset(QuerySet) +ManagerFromGenericQuerySet = GenericQuerySet[int].as_manager() reveal_type(MyManager()) # N: Revealed type is "__main__.MyManager" reveal_type(MyManager().attr) # N: Revealed type is "builtins.str" +reveal_type(ManagerFromGenericQuerySet()) # N: Revealed type is "__main__.ManagerFromGenericQuerySet" +reveal_type(ManagerFromGenericQuerySet().attr) # N: Revealed type is "builtins.int" +queryset: GenericQuerySet[int] = ManagerFromGenericQuerySet() def func(manager: MyManager) -> None: reveal_type(manager) # N: Revealed type is "__main__.MyManager" @@ -704,6 +708,12 @@ class QuerySet: class Manager: @classmethod def from_queryset(cls, queryset_cls: Type[QuerySet]): ... +T = TypeVar("T") +class GenericQuerySet(Generic[T]): + attr: T + + @classmethod + def as_manager(cls): ... [builtins fixtures/classmethod.pyi] [file mypy.ini] @@ -802,7 +812,7 @@ else: plugins=/test-data/unit/plugins/union_method.py [case testGetMethodHooksOnUnionsStrictOptional] -# flags: --config-file tmp/mypy.ini --strict-optional +# flags: --config-file tmp/mypy.ini from typing import Union class Foo: @@ -887,7 +897,10 @@ plugins=/test-data/unit/plugins/descriptor.py # flags: --config-file tmp/mypy.ini def dynamic_signature(arg1: str) -> str: ... -reveal_type(dynamic_signature(1)) # N: Revealed type is "builtins.int" +a: int = 1 +reveal_type(dynamic_signature(a)) # N: Revealed type is "builtins.int" +b: bytes = b'foo' +reveal_type(dynamic_signature(b)) # N: Revealed type is "builtins.bytes" [file mypy.ini] \[mypy] plugins=/test-data/unit/plugins/function_sig_hook.py @@ -1008,9 +1021,43 @@ class BaseAddMethod: pass class MyClass(BaseAddMethod): pass -my_class = MyClass() reveal_type(MyClass.foo_classmethod) # N: Revealed type is "def ()" reveal_type(MyClass.foo_staticmethod) # N: Revealed type is "def (builtins.int) -> builtins.str" + +my_class = MyClass() +reveal_type(my_class.foo_classmethod) # N: Revealed type is "def ()" +reveal_type(my_class.foo_staticmethod) # N: Revealed type is "def (builtins.int) -> builtins.str" [file mypy.ini] \[mypy] plugins=/test-data/unit/plugins/add_classmethod.py + +[case testAddOverloadedMethodPlugin] +# flags: --config-file tmp/mypy.ini +class AddOverloadedMethod: pass + +class MyClass(AddOverloadedMethod): + pass + +reveal_type(MyClass.method) # N: Revealed type is "Overload(def (self: __main__.MyClass, arg: builtins.int) -> builtins.str, def (self: __main__.MyClass, arg: builtins.str) -> builtins.int)" +reveal_type(MyClass.clsmethod) # N: Revealed type is "Overload(def (arg: builtins.int) -> builtins.str, def (arg: builtins.str) -> builtins.int)" +reveal_type(MyClass.stmethod) # N: Revealed type is "Overload(def (arg: builtins.int) -> builtins.str, def (arg: builtins.str) -> builtins.int)" + +my_class = MyClass() +reveal_type(my_class.method) # N: Revealed type is "Overload(def (arg: builtins.int) -> builtins.str, def (arg: builtins.str) -> builtins.int)" +reveal_type(my_class.clsmethod) # N: Revealed type is "Overload(def (arg: builtins.int) -> builtins.str, def (arg: builtins.str) -> builtins.int)" +reveal_type(my_class.stmethod) # N: Revealed type is "Overload(def (arg: builtins.int) -> builtins.str, def (arg: builtins.str) -> builtins.int)" +[file mypy.ini] +\[mypy] +plugins=/test-data/unit/plugins/add_overloaded_method.py + +[case testCustomErrorCodePlugin] +# flags: --config-file tmp/mypy.ini --show-error-codes +def main() -> int: + return 2 + +main() # E: Custom error [custom] +reveal_type(1) # N: Revealed type is "Literal[1]?" + +[file mypy.ini] +\[mypy] +plugins=/test-data/unit/plugins/custom_errorcode.py diff --git a/test-data/unit/check-dataclass-transform.test b/test-data/unit/check-dataclass-transform.test index 2a7fad1da992..51b2e186214f 100644 --- a/test-data/unit/check-dataclass-transform.test +++ b/test-data/unit/check-dataclass-transform.test @@ -22,7 +22,6 @@ Person('Jonh', 21, None) # E: Too many arguments for "Person" [builtins fixtures/dataclasses.pyi] [case testDataclassTransformIsFoundInTypingExtensions] -# flags: --python-version 3.7 from typing import Type from typing_extensions import dataclass_transform @@ -279,8 +278,7 @@ class Bad: bad1: int = field(alias=some_str()) # E: "alias" argument to dataclass field must be a string literal bad2: int = field(kw_only=some_bool()) # E: "kw_only" argument must be a boolean literal -# this metadata should only exist for dataclasses.dataclass classes -Foo.__dataclass_fields__ # E: "Type[Foo]" has no attribute "__dataclass_fields__" +reveal_type(Foo.__dataclass_fields__) # N: Revealed type is "builtins.dict[builtins.str, Any]" [typing fixtures/typing-full.pyi] [builtins fixtures/dataclasses.pyi] @@ -329,6 +327,38 @@ Foo(a=1, b='bye') [typing fixtures/typing-full.pyi] [builtins fixtures/dataclasses.pyi] +[case testDataclassTransformFieldSpecifierImplicitInit] +# flags: --python-version 3.11 +from typing import dataclass_transform, Literal, overload + +def init(*, init: Literal[True] = True): ... +def no_init(*, init: Literal[False] = False): ... + +@overload +def field_overload(*, custom: None, init: Literal[True] = True): ... +@overload +def field_overload(*, custom: str, init: Literal[False] = False): ... +def field_overload(*, custom, init): ... + +@dataclass_transform(field_specifiers=(init, no_init, field_overload)) +def my_dataclass(cls): return cls + +@my_dataclass +class Foo: + a: int = init() + b: int = field_overload(custom=None) + + bad1: int = no_init() + bad2: int = field_overload(custom="bad2") + +reveal_type(Foo) # N: Revealed type is "def (a: builtins.int, b: builtins.int) -> __main__.Foo" +Foo(a=1, b=2) +Foo(a=1, b=2, bad1=0) # E: Unexpected keyword argument "bad1" for "Foo" +Foo(a=1, b=2, bad2=0) # E: Unexpected keyword argument "bad2" for "Foo" + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + [case testDataclassTransformOverloadsDecoratorOnOverload] # flags: --python-version 3.11 from typing import dataclass_transform, overload, Any, Callable, Type, Literal @@ -416,7 +446,11 @@ from typing import dataclass_transform @dataclass_transform(frozen_default=True) class Dataclass(type): ... -class Person(metaclass=Dataclass, kw_only=True): +# Note that PEP 681 states that a class that directly specifies a dataclass_transform-decorated +# metaclass should be treated as neither frozen nor unfrozen. For Person to have frozen semantics, +# it may not directly specify the metaclass. +class BaseDataclass(metaclass=Dataclass): ... +class Person(BaseDataclass, kw_only=True): name: str age: int @@ -452,3 +486,589 @@ Foo(1) # E: Too many arguments for "Foo" [typing fixtures/typing-full.pyi] [builtins fixtures/dataclasses.pyi] + +[case testDataclassTransformTypeCheckingInFunction] +# flags: --python-version 3.11 +from typing import dataclass_transform, Type, TYPE_CHECKING + +@dataclass_transform() +def model(cls: Type) -> Type: + return cls + +@model +class FunctionModel: + if TYPE_CHECKING: + string_: str + integer_: int + else: + string_: tuple + integer_: tuple + +FunctionModel(string_="abc", integer_=1) +FunctionModel(string_="abc", integer_=tuple()) # E: Argument "integer_" to "FunctionModel" has incompatible type "Tuple[Never, ...]"; expected "int" + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[case testDataclassTransformNegatedTypeCheckingInFunction] +# flags: --python-version 3.11 +from typing import dataclass_transform, Type, TYPE_CHECKING + +@dataclass_transform() +def model(cls: Type) -> Type: + return cls + +@model +class FunctionModel: + if not TYPE_CHECKING: + string_: tuple + integer_: tuple + else: + string_: str + integer_: int + +FunctionModel(string_="abc", integer_=1) +FunctionModel(string_="abc", integer_=tuple()) # E: Argument "integer_" to "FunctionModel" has incompatible type "Tuple[Never, ...]"; expected "int" + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + + +[case testDataclassTransformTypeCheckingInBaseClass] +# flags: --python-version 3.11 +from typing import dataclass_transform, TYPE_CHECKING + +@dataclass_transform() +class ModelBase: + ... + +class BaseClassModel(ModelBase): + if TYPE_CHECKING: + string_: str + integer_: int + else: + string_: tuple + integer_: tuple + +BaseClassModel(string_="abc", integer_=1) +BaseClassModel(string_="abc", integer_=tuple()) # E: Argument "integer_" to "BaseClassModel" has incompatible type "Tuple[Never, ...]"; expected "int" + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[case testDataclassTransformNegatedTypeCheckingInBaseClass] +# flags: --python-version 3.11 +from typing import dataclass_transform, TYPE_CHECKING + +@dataclass_transform() +class ModelBase: + ... + +class BaseClassModel(ModelBase): + if not TYPE_CHECKING: + string_: tuple + integer_: tuple + else: + string_: str + integer_: int + +BaseClassModel(string_="abc", integer_=1) +BaseClassModel(string_="abc", integer_=tuple()) # E: Argument "integer_" to "BaseClassModel" has incompatible type "Tuple[Never, ...]"; expected "int" + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[case testDataclassTransformTypeCheckingInMetaClass] +# flags: --python-version 3.11 +from typing import dataclass_transform, Type, TYPE_CHECKING + +@dataclass_transform() +class ModelMeta(type): + ... + +class ModelBaseWithMeta(metaclass=ModelMeta): + ... + +class MetaClassModel(ModelBaseWithMeta): + if TYPE_CHECKING: + string_: str + integer_: int + else: + string_: tuple + integer_: tuple + +MetaClassModel(string_="abc", integer_=1) +MetaClassModel(string_="abc", integer_=tuple()) # E: Argument "integer_" to "MetaClassModel" has incompatible type "Tuple[Never, ...]"; expected "int" + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[case testDataclassTransformNegatedTypeCheckingInMetaClass] +# flags: --python-version 3.11 +from typing import dataclass_transform, Type, TYPE_CHECKING + +@dataclass_transform() +class ModelMeta(type): + ... + +class ModelBaseWithMeta(metaclass=ModelMeta): + ... + +class MetaClassModel(ModelBaseWithMeta): + if not TYPE_CHECKING: + string_: tuple + integer_: tuple + else: + string_: str + integer_: int + +MetaClassModel(string_="abc", integer_=1) +MetaClassModel(string_="abc", integer_=tuple()) # E: Argument "integer_" to "MetaClassModel" has incompatible type "Tuple[Never, ...]"; expected "int" + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[case testDataclassTransformStaticConditionalAttributes] +# flags: --python-version 3.11 --always-true TRUTH +from typing import dataclass_transform, Type, TYPE_CHECKING + +TRUTH = False # Is set to --always-true + +@dataclass_transform() +def model(cls: Type) -> Type: + return cls + +@model +class FunctionModel: + if TYPE_CHECKING: + present_1: int + else: + skipped_1: int + if True: # Mypy does not know if it is True or False, so the block is used + present_2: int + if False: # Mypy does not know if it is True or False, so the block is used + present_3: int + if not TRUTH: + skipped_2: int + else: + present_4: int + +FunctionModel( + present_1=1, + present_2=2, + present_3=3, + present_4=4, +) +FunctionModel() # E: Missing positional arguments "present_1", "present_2", "present_3", "present_4" in call to "FunctionModel" +FunctionModel( # E: Unexpected keyword argument "skipped_1" for "FunctionModel" + present_1=1, + present_2=2, + present_3=3, + present_4=4, + skipped_1=5, +) + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + + +[case testDataclassTransformStaticDeterministicConditionalElifAttributes] +# flags: --python-version 3.11 --always-true TRUTH --always-false LIE +from typing import dataclass_transform, Type, TYPE_CHECKING + +TRUTH = False # Is set to --always-true +LIE = True # Is set to --always-false + +@dataclass_transform() +def model(cls: Type) -> Type: + return cls + +@model +class FunctionModel: + if TYPE_CHECKING: + present_1: int + elif TRUTH: + skipped_1: int + else: + skipped_2: int + if LIE: + skipped_3: int + elif TRUTH: + present_2: int + else: + skipped_4: int + if LIE: + skipped_5: int + elif LIE: + skipped_6: int + else: + present_3: int + +FunctionModel( + present_1=1, + present_2=2, + present_3=3, +) + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[case testDataclassTransformStaticNotDeterministicConditionalElifAttributes] +# flags: --python-version 3.11 --always-true TRUTH --always-false LIE +from typing import dataclass_transform, Type, TYPE_CHECKING + +TRUTH = False # Is set to --always-true +LIE = True # Is set to --always-false + +@dataclass_transform() +def model(cls: Type) -> Type: + return cls + +@model +class FunctionModel: + if 123: # Mypy does not know if it is True or False, so this block is used + present_1: int + elif TRUTH: # Mypy does not know if previous condition is True or False, so it uses also this block + present_2: int + else: # Previous block is for sure True, so this block is skipped + skipped_1: int + if 123: + present_3: int + elif 123: + present_4: int + else: + present_5: int + if 123: # Mypy does not know if it is True or False, so this block is used + present_6: int + elif LIE: # This is for sure False, so the block is skipped used + skipped_2: int + else: # None of the conditions above for sure True, so this block is used + present_7: int + +FunctionModel( + present_1=1, + present_2=2, + present_3=3, + present_4=4, + present_5=5, + present_6=6, + present_7=7, +) + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[case testDataclassTransformFunctionConditionalAttributes] +# flags: --python-version 3.11 +from typing import dataclass_transform, Type + +@dataclass_transform() +def model(cls: Type) -> Type: + return cls + +def condition() -> bool: + return True + +@model +class FunctionModel: + if condition(): + x: int + y: int + z1: int + else: + x: str # E: Name "x" already defined on line 14 + y: int # E: Name "y" already defined on line 15 + z2: int + +FunctionModel(x=1, y=2, z1=3, z2=4) + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + + +[case testDataclassTransformNegatedFunctionConditionalAttributes] +# flags: --python-version 3.11 +from typing import dataclass_transform, Type + +@dataclass_transform() +def model(cls: Type) -> Type: + return cls + +def condition() -> bool: + return True + +@model +class FunctionModel: + if not condition(): + x: int + y: int + z1: int + else: + x: str # E: Name "x" already defined on line 14 + y: int # E: Name "y" already defined on line 15 + z2: int + +FunctionModel(x=1, y=2, z1=3, z2=4) + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[case testDataclassTransformDirectMetaclassNeitherFrozenNorNotFrozen] +# flags: --python-version 3.11 +from typing import dataclass_transform, Type + +@dataclass_transform() +class Meta(type): ... +class Base(metaclass=Meta): + base: int +class Foo(Base, frozen=True): + foo: int +class Bar(Base, frozen=False): + bar: int + + +foo = Foo(0, 1) +foo.foo = 5 # E: Property "foo" defined in "Foo" is read-only +foo.base = 6 +reveal_type(foo.base) # N: Revealed type is "builtins.int" +bar = Bar(0, 1) +bar.bar = 5 +bar.base = 6 +reveal_type(bar.base) # N: Revealed type is "builtins.int" + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[case testDataclassTransformReplace] +from dataclasses import replace +from typing import dataclass_transform, Type + +@dataclass_transform() +def my_dataclass(cls: Type) -> Type: + return cls + +@my_dataclass +class Person: + name: str + +p = Person('John') +y = replace(p, name='Bob') + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[case testDataclassTransformSimpleDescriptor] +# flags: --python-version 3.11 + +from typing import dataclass_transform, overload, Any + +@dataclass_transform() +def my_dataclass(cls): ... + +class Desc: + @overload + def __get__(self, instance: None, owner: Any) -> Desc: ... + @overload + def __get__(self, instance: object, owner: Any) -> str: ... + def __get__(self, instance: object | None, owner: Any) -> Desc | str: ... + + def __set__(self, instance: Any, value: str) -> None: ... + +@my_dataclass +class C: + x: Desc + y: int + +C(x='x', y=1) +C(x=1, y=1) # E: Argument "x" to "C" has incompatible type "int"; expected "str" +reveal_type(C(x='x', y=1).x) # N: Revealed type is "builtins.str" +reveal_type(C(x='x', y=1).y) # N: Revealed type is "builtins.int" +reveal_type(C.x) # N: Revealed type is "__main__.Desc" + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[case testDataclassTransformUnannotatedDescriptor] +# flags: --python-version 3.11 + +from typing import dataclass_transform, overload, Any + +@dataclass_transform() +def my_dataclass(cls): ... + +class Desc: + @overload + def __get__(self, instance: None, owner: Any) -> Desc: ... + @overload + def __get__(self, instance: object, owner: Any) -> str: ... + def __get__(self, instance: object | None, owner: Any) -> Desc | str: ... + + def __set__(*args, **kwargs): ... + +@my_dataclass +class C: + x: Desc + y: int + +C(x='x', y=1) +C(x=1, y=1) +reveal_type(C(x='x', y=1).x) # N: Revealed type is "builtins.str" +reveal_type(C(x='x', y=1).y) # N: Revealed type is "builtins.int" +reveal_type(C.x) # N: Revealed type is "__main__.Desc" + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[case testDataclassTransformGenericDescriptor] +# flags: --python-version 3.11 + +from typing import dataclass_transform, overload, Any, TypeVar, Generic + +@dataclass_transform() +def my_dataclass(frozen: bool = False): ... + +T = TypeVar("T") + +class Desc(Generic[T]): + @overload + def __get__(self, instance: None, owner: Any) -> Desc[T]: ... + @overload + def __get__(self, instance: object, owner: Any) -> T: ... + def __get__(self, instance: object | None, owner: Any) -> Desc | T: ... + + def __set__(self, instance: Any, value: T) -> None: ... + +@my_dataclass() +class C: + x: Desc[str] + +C(x='x') +C(x=1) # E: Argument "x" to "C" has incompatible type "int"; expected "str" +reveal_type(C(x='x').x) # N: Revealed type is "builtins.str" +reveal_type(C.x) # N: Revealed type is "__main__.Desc[builtins.str]" + +@my_dataclass() +class D(C): + y: Desc[int] + +d = D(x='x', y=1) +reveal_type(d.x) # N: Revealed type is "builtins.str" +reveal_type(d.y) # N: Revealed type is "builtins.int" +reveal_type(D.x) # N: Revealed type is "__main__.Desc[builtins.str]" +reveal_type(D.y) # N: Revealed type is "__main__.Desc[builtins.int]" + +@my_dataclass(frozen=True) +class F: + x: Desc[str] = Desc() + +F(x='x') +F(x=1) # E: Argument "x" to "F" has incompatible type "int"; expected "str" +reveal_type(F(x='x').x) # N: Revealed type is "builtins.str" +reveal_type(F.x) # N: Revealed type is "__main__.Desc[builtins.str]" + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[case testDataclassTransformGenericDescriptorWithInheritance] +# flags: --python-version 3.11 + +from typing import dataclass_transform, overload, Any, TypeVar, Generic + +@dataclass_transform() +def my_dataclass(cls): ... + +T = TypeVar("T") + +class Desc(Generic[T]): + @overload + def __get__(self, instance: None, owner: Any) -> Desc[T]: ... + @overload + def __get__(self, instance: object, owner: Any) -> T: ... + def __get__(self, instance: object | None, owner: Any) -> Desc | T: ... + + def __set__(self, instance: Any, value: T) -> None: ... + +class Desc2(Desc[str]): + pass + +@my_dataclass +class C: + x: Desc2 + +C(x='x') +C(x=1) # E: Argument "x" to "C" has incompatible type "int"; expected "str" +reveal_type(C(x='x').x) # N: Revealed type is "builtins.str" +reveal_type(C.x) # N: Revealed type is "__main__.Desc[builtins.str]" + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[case testDataclassTransformDescriptorWithDifferentGetSetTypes] +# flags: --python-version 3.11 + +from typing import dataclass_transform, overload, Any + +@dataclass_transform() +def my_dataclass(cls): ... + +class Desc: + @overload + def __get__(self, instance: None, owner: Any) -> int: ... + @overload + def __get__(self, instance: object, owner: Any) -> str: ... + def __get__(self, instance, owner): ... + + def __set__(self, instance: Any, value: bytes | None) -> None: ... + +@my_dataclass +class C: + x: Desc + +c = C(x=b'x') +c = C(x=None) +C(x=1) # E: Argument "x" to "C" has incompatible type "int"; expected "Optional[bytes]" +reveal_type(c.x) # N: Revealed type is "builtins.str" +reveal_type(C.x) # N: Revealed type is "builtins.int" +c.x = b'x' +c.x = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Optional[bytes]") + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[case testDataclassTransformUnsupportedDescriptors] +# flags: --python-version 3.11 + +from typing import dataclass_transform, overload, Any + +@dataclass_transform() +def my_dataclass(cls): ... + +class Desc: + @overload + def __get__(self, instance: None, owner: Any) -> int: ... + @overload + def __get__(self, instance: object, owner: Any) -> str: ... + def __get__(self, instance, owner): ... + + def __set__(*args, **kwargs) -> None: ... + +class Desc2: + @overload + def __get__(self, instance: None, owner: Any) -> int: ... + @overload + def __get__(self, instance: object, owner: Any) -> str: ... + def __get__(self, instance, owner): ... + + @overload + def __set__(self, instance: Any, value: bytes) -> None: ... + @overload + def __set__(self) -> None: ... + def __set__(self, *args, **kawrga) -> None: ... + +@my_dataclass +class C: + x: Desc # E: Unsupported signature for "__set__" in "Desc" + y: Desc2 # E: Unsupported "__set__" in "Desc2" + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 4d85be391186..a055507cdd78 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -1,5 +1,4 @@ [case testDataclassesBasic] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass @@ -18,7 +17,6 @@ Person('Jonh', 21, None) # E: Too many arguments for "Person" [typing fixtures/typing-medium.pyi] [case testDataclassesCustomInit] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass @@ -33,7 +31,6 @@ A('1') [builtins fixtures/dataclasses.pyi] [case testDataclassesBasicInheritance] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass @@ -56,7 +53,6 @@ Person(21, 'Jonh', None) # E: Too many arguments for "Person" [typing fixtures/typing-medium.pyi] [case testDataclassesDeepInheritance] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass @@ -83,7 +79,6 @@ reveal_type(D) # N: Revealed type is "def (a: builtins.int, b: builtins.int, c: [builtins fixtures/dataclasses.pyi] [case testDataclassesMultipleInheritance] -# flags: --python-version 3.7 from dataclasses import dataclass, field, InitVar @dataclass class A: @@ -106,7 +101,6 @@ reveal_type(C) # N: Revealed type is "def (b: builtins.bool, a: builtins.bool) [builtins fixtures/dataclasses.pyi] [case testDataclassesDeepInitVarInheritance] -# flags: --python-version 3.7 from dataclasses import dataclass, field, InitVar @dataclass class A: @@ -135,7 +129,6 @@ reveal_type(D) # N: Revealed type is "def (b: builtins.bool) -> __main__.D" [builtins fixtures/dataclasses.pyi] [case testDataclassesOverriding] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass @@ -169,7 +162,6 @@ ExtraSpecialPerson(21, 'John', 0.5) [case testDataclassesOverridingWithDefaults] # Issue #5681 https://github.com/python/mypy/issues/5681 -# flags: --python-version 3.7 from dataclasses import dataclass from typing import Any @@ -188,7 +180,6 @@ reveal_type(C) # N: Revealed type is "def (some_int: builtins.int, some_str: bu [builtins fixtures/dataclasses.pyi] [case testDataclassIncompatibleOverrides] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass @@ -198,7 +189,11 @@ class Base: @dataclass class BadDerived1(Base): def foo(self) -> int: # E: Dataclass attribute may only be overridden by another attribute \ - # E: Signature of "foo" incompatible with supertype "Base" + # E: Signature of "foo" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: int \ + # N: Subclass: \ + # N: def foo(self) -> int return 1 @dataclass @@ -213,7 +208,6 @@ class BadDerived3(Base): [builtins fixtures/dataclasses.pyi] [case testDataclassMultipleInheritance] -# flags: --python-version 3.7 from dataclasses import dataclass class Unrelated: @@ -233,7 +227,6 @@ reveal_type(d.bar) # N: Revealed type is "builtins.int" [builtins fixtures/dataclasses.pyi] [case testDataclassIncompatibleFrozenOverride] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass(frozen=True) @@ -248,7 +241,6 @@ class BadDerived(Base): [builtins fixtures/dataclasses.pyi] [case testDataclassesFreezing] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass(frozen=True) @@ -261,7 +253,6 @@ john.name = 'Ben' # E: Property "name" defined in "Person" is read-only [builtins fixtures/dataclasses.pyi] [case testDataclassesInconsistentFreezing] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass(frozen=True) @@ -283,7 +274,6 @@ class BadFrozenDerived(NormalBase): # E: Cannot inherit frozen dataclass from a [builtins fixtures/dataclasses.pyi] [case testDataclassesFields] -# flags: --python-version 3.7 from dataclasses import dataclass, field @dataclass @@ -299,7 +289,6 @@ john.age = 24 [builtins fixtures/dataclasses.pyi] [case testDataclassesBadInit] -# flags: --python-version 3.7 from dataclasses import dataclass, field @dataclass @@ -314,7 +303,6 @@ class Person: [builtins fixtures/dataclasses.pyi] [case testDataclassesMultiInit] -# flags: --python-version 3.7 from dataclasses import dataclass, field from typing import List @@ -330,7 +318,6 @@ reveal_type(Person) # N: Revealed type is "def (name: builtins.str, friend_name [builtins fixtures/dataclasses.pyi] [case testDataclassesMultiInitDefaults] -# flags: --python-version 3.7 from dataclasses import dataclass, field from typing import List, Optional @@ -347,7 +334,6 @@ reveal_type(Person) # N: Revealed type is "def (name: builtins.str, friend_name [builtins fixtures/dataclasses.pyi] [case testDataclassesDefaults] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass @@ -361,7 +347,6 @@ app = Application() [builtins fixtures/dataclasses.pyi] [case testDataclassesDefaultFactories] -# flags: --python-version 3.7 from dataclasses import dataclass, field @dataclass @@ -373,7 +358,6 @@ class Application: [builtins fixtures/dataclasses.pyi] [case testDataclassesDefaultFactoryTypeChecking] -# flags: --python-version 3.7 from dataclasses import dataclass, field @dataclass @@ -384,7 +368,6 @@ class Application: [builtins fixtures/dataclasses.pyi] [case testDataclassesDefaultOrdering] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass @@ -529,7 +512,6 @@ class Base: [builtins fixtures/dataclasses.pyi] [case testDataclassesClassmethods] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass @@ -545,7 +527,6 @@ app = Application.parse('') [builtins fixtures/dataclasses.pyi] [case testDataclassesOverloadsAndClassmethods] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import overload, Union @@ -578,20 +559,18 @@ reveal_type(A.foo("foo")) # N: Revealed type is "builtins.str" [builtins fixtures/dataclasses.pyi] [case testClassmethodShadowingFieldDoesNotCrash] -# flags: --python-version 3.7 from dataclasses import dataclass # This used to crash -- see #6217 @dataclass class Foo: bar: str - @classmethod # E: Name "bar" already defined on line 7 + @classmethod # E: Name "bar" already defined on line 6 def bar(cls) -> "Foo": return cls('asdf') [builtins fixtures/dataclasses.pyi] [case testDataclassesClassVars] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import ClassVar @@ -609,7 +588,6 @@ Application.COUNTER = 1 [builtins fixtures/dataclasses.pyi] [case testTypeAliasInDataclassDoesNotCrash] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import Callable from typing_extensions import TypeAlias @@ -638,7 +616,6 @@ reveal_type(x) # N: Revealed type is "typing._SpecialForm" [typing fixtures/typing-medium.pyi] [case testDataclassOrdering] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass(order=True) @@ -669,7 +646,6 @@ app1 >= app3 [builtins fixtures/dataclasses.pyi] [case testDataclassOrderingWithoutEquality] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass(eq=False, order=True) # E: "eq" must be True if "order" is True @@ -679,7 +655,6 @@ class Application: [builtins fixtures/dataclasses.pyi] [case testDataclassOrderingWithCustomMethods] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass(order=True) @@ -690,7 +665,6 @@ class Application: [builtins fixtures/dataclasses.pyi] [case testDataclassDefaultsInheritance] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import Optional @@ -708,7 +682,6 @@ reveal_type(SpecializedApplication) # N: Revealed type is "def (id: Union[built [builtins fixtures/dataclasses.pyi] [case testDataclassGenerics] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import Generic, List, Optional, TypeVar @@ -740,9 +713,19 @@ s: str = a.bar() # E: Incompatible types in assignment (expression has type "in [builtins fixtures/dataclasses.pyi] +[case testDataclassGenericCovariant] +from dataclasses import dataclass +from typing import Generic, TypeVar + +T_co = TypeVar("T_co", covariant=True) + +@dataclass +class MyDataclass(Generic[T_co]): + a: T_co + +[builtins fixtures/dataclasses.pyi] [case testDataclassUntypedGenericInheritance] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import Generic, TypeVar @@ -763,7 +746,6 @@ reveal_type(sub.attr) # N: Revealed type is "Any" [builtins fixtures/dataclasses.pyi] [case testDataclassGenericSubtype] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import Generic, TypeVar @@ -790,7 +772,6 @@ reveal_type(sub_str.attr) # N: Revealed type is "builtins.str" [builtins fixtures/dataclasses.pyi] [case testDataclassGenericInheritance] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import Generic, TypeVar @@ -817,7 +798,6 @@ reveal_type(sub.three) # N: Revealed type is "builtins.float" [builtins fixtures/dataclasses.pyi] [case testDataclassMultiGenericInheritance] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import Generic, TypeVar @@ -845,7 +825,6 @@ reveal_type(sub.middle_attr) # N: Revealed type is "builtins.str" [builtins fixtures/dataclasses.pyi] [case testDataclassGenericsClassmethod] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import Generic, TypeVar @@ -867,7 +846,6 @@ reveal_type(A(0).other) # N: Revealed type is "def (x: builtins.int) -> __main_ [builtins fixtures/dataclasses.pyi] [case testDataclassesForwardRefs] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass @@ -886,7 +864,6 @@ A(b=42) # E: Argument "b" to "A" has incompatible type "int"; expected "B" [case testDataclassesInitVars] -# flags: --python-version 3.7 from dataclasses import InitVar, dataclass @dataclass @@ -915,7 +892,6 @@ app.database_name # E: "SpecializedApplication" has no attribute "database_name [builtins fixtures/dataclasses.pyi] [case testDataclassesInitVarsAndDefer] -# flags: --python-version 3.7 from dataclasses import InitVar, dataclass defer: Yes @@ -935,7 +911,6 @@ class Yes: ... [builtins fixtures/dataclasses.pyi] [case testDataclassesNoInitInitVarInheritance] -# flags: --python-version 3.7 from dataclasses import dataclass, field, InitVar @dataclass @@ -952,7 +927,6 @@ sub.bar [builtins fixtures/dataclasses.pyi] [case testDataclassFactory] -# flags: --python-version 3.7 from typing import Type, TypeVar from dataclasses import dataclass @@ -968,7 +942,6 @@ class A: [builtins fixtures/dataclasses.pyi] [case testDataclassesInitVarOverride] -# flags: --python-version 3.7 import dataclasses @dataclasses.dataclass @@ -991,7 +964,6 @@ class B(A): [builtins fixtures/dataclasses.pyi] [case testDataclassesInitVarNoOverride] -# flags: --python-version 3.7 import dataclasses @dataclasses.dataclass @@ -1017,7 +989,6 @@ B(1, 'a') # E: Argument 2 to "B" has incompatible type "str"; expected "int" [builtins fixtures/dataclasses.pyi] [case testDataclassesInitVarPostInitOverride] -# flags: --python-version 3.7 import dataclasses @dataclasses.dataclass @@ -1053,7 +1024,6 @@ C(1, 'a') # E: Argument 2 to "C" has incompatible type "str"; expected "int" [builtins fixtures/primitives.pyi] [case testDataclassesInitVarIncremental] -# flags: --python-version 3.7 import a [file a.py] @@ -1099,7 +1069,6 @@ tmp/a.py:12: note: Revealed type is "def (a: builtins.int) -> a.B" [case testNoComplainFieldNone] -# flags: --python-version 3.7 # flags: --no-strict-optional from dataclasses import dataclass, field from typing import Optional @@ -1111,8 +1080,6 @@ class Foo: [out] [case testNoComplainFieldNoneStrict] -# flags: --python-version 3.7 -# flags: --strict-optional from dataclasses import dataclass, field from typing import Optional @@ -1123,7 +1090,7 @@ class Foo: [out] [case testDisallowUntypedWorksForward] -# flags: --disallow-untyped-defs --python-version 3.7 +# flags: --disallow-untyped-defs from dataclasses import dataclass from typing import List @@ -1138,7 +1105,7 @@ reveal_type(B) # N: Revealed type is "def (x: __main__.C) -> __main__.B" [builtins fixtures/dataclasses.pyi] [case testDisallowUntypedWorksForwardBad] -# flags: --disallow-untyped-defs --python-version 3.7 +# flags: --disallow-untyped-defs from dataclasses import dataclass @dataclass @@ -1150,7 +1117,6 @@ reveal_type(B) # N: Revealed type is "def (x: Any) -> __main__.B" [builtins fixtures/dataclasses.pyi] [case testMemberExprWorksAsField] -# flags: --python-version 3.7 import dataclasses @dataclasses.dataclass @@ -1170,7 +1136,6 @@ class C: [builtins fixtures/dict.pyi] [case testDataclassOrderingDeferred] -# flags: --python-version 3.7 from dataclasses import dataclass defer: Yes @@ -1188,7 +1153,6 @@ class Yes: ... [builtins fixtures/dataclasses.pyi] [case testDataclassFieldDeferred] -# flags: --python-version 3.7 from dataclasses import field, dataclass @dataclass @@ -1200,7 +1164,6 @@ C('no') # E: Argument 1 to "C" has incompatible type "str"; expected "int" [builtins fixtures/dataclasses.pyi] [case testDataclassFieldDeferredFrozen] -# flags: --python-version 3.7 from dataclasses import field, dataclass @dataclass(frozen=True) @@ -1213,7 +1176,6 @@ c.x = 1 # E: Property "x" defined in "C" is read-only [builtins fixtures/dataclasses.pyi] [case testTypeInDataclassDeferredStar] -# flags: --python-version 3.7 import lib [file lib.py] from dataclasses import dataclass @@ -1232,7 +1194,7 @@ import lib [builtins fixtures/dataclasses.pyi] [case testDeferredDataclassInitSignature] -# flags: --python-version 3.7 --no-strict-optional +# flags: --no-strict-optional from dataclasses import dataclass from typing import Optional, Type @@ -1249,7 +1211,6 @@ class Deferred: pass [builtins fixtures/dataclasses.pyi] [case testDeferredDataclassInitSignatureSubclass] -# flags: --strict-optional --python-version 3.7 from dataclasses import dataclass from typing import Optional @@ -1265,7 +1226,6 @@ a = C(None, 'abc') [builtins fixtures/dataclasses.pyi] [case testDataclassesDefaultsIncremental] -# flags: --python-version 3.7 import a [file a.py] @@ -1297,7 +1257,6 @@ class Person: [builtins fixtures/dataclasses.pyi] [case testDataclassesDefaultsMroOtherFile] -# flags: --python-version 3.7 import a [file a.py] @@ -1326,14 +1285,13 @@ class A2: [builtins fixtures/dataclasses.pyi] [case testDataclassesInheritingDuplicateField] -# flags: --python-version 3.7 # see mypy issue #7792 from dataclasses import dataclass @dataclass class A: x: int = 0 - x: int = 0 # E: Name "x" already defined on line 7 + x: int = 0 # E: Name "x" already defined on line 6 @dataclass class B(A): @@ -1342,7 +1300,6 @@ class B(A): [builtins fixtures/dataclasses.pyi] [case testDataclassInheritanceNoAnnotation] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass @@ -1359,7 +1316,6 @@ reveal_type(B) # N: Revealed type is "def (foo: builtins.int) -> __main__.B" [builtins fixtures/dataclasses.pyi] [case testDataclassInheritanceNoAnnotation2] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass(frozen=True) @@ -1376,7 +1332,6 @@ reveal_type(B) # N: Revealed type is "def (foo: builtins.int) -> __main__.B" [builtins fixtures/dataclasses.pyi] [case testDataclassHasAttributeWithFields] -# flags: --python-version 3.7 from dataclasses import dataclass @dataclass @@ -1388,7 +1343,6 @@ reveal_type(A.__dataclass_fields__) # N: Revealed type is "builtins.dict[builti [builtins fixtures/dict.pyi] [case testDataclassCallableFieldAccess] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import Callable @@ -1406,7 +1360,6 @@ reveal_type(A.y) # N: Revealed type is "def (builtins.int) -> builtins.int" [builtins fixtures/dataclasses.pyi] [case testDataclassCallableFieldAssignment] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import Callable @@ -1425,7 +1378,6 @@ a.x = x2 # E: Incompatible types in assignment (expression has type "Callable[[s [builtins fixtures/dataclasses.pyi] [case testDataclassFieldDoesNotFailOnKwargsUnpacking] -# flags: --python-version 3.7 # https://github.com/python/mypy/issues/10879 from dataclasses import dataclass, field @@ -1433,16 +1385,15 @@ from dataclasses import dataclass, field class Foo: bar: float = field(**{"repr": False}) [out] -main:7: error: Unpacking **kwargs in "field()" is not supported -main:7: error: No overload variant of "field" matches argument type "Dict[str, bool]" -main:7: note: Possible overload variants: -main:7: note: def [_T] field(*, default: _T, init: bool = ..., repr: bool = ..., hash: Optional[bool] = ..., compare: bool = ..., metadata: Optional[Mapping[str, Any]] = ..., kw_only: bool = ...) -> _T -main:7: note: def [_T] field(*, default_factory: Callable[[], _T], init: bool = ..., repr: bool = ..., hash: Optional[bool] = ..., compare: bool = ..., metadata: Optional[Mapping[str, Any]] = ..., kw_only: bool = ...) -> _T -main:7: note: def field(*, init: bool = ..., repr: bool = ..., hash: Optional[bool] = ..., compare: bool = ..., metadata: Optional[Mapping[str, Any]] = ..., kw_only: bool = ...) -> Any +main:6: error: Unpacking **kwargs in "field()" is not supported +main:6: error: No overload variant of "field" matches argument type "Dict[str, bool]" +main:6: note: Possible overload variants: +main:6: note: def [_T] field(*, default: _T, init: bool = ..., repr: bool = ..., hash: Optional[bool] = ..., compare: bool = ..., metadata: Optional[Mapping[str, Any]] = ..., kw_only: bool = ...) -> _T +main:6: note: def [_T] field(*, default_factory: Callable[[], _T], init: bool = ..., repr: bool = ..., hash: Optional[bool] = ..., compare: bool = ..., metadata: Optional[Mapping[str, Any]] = ..., kw_only: bool = ...) -> _T +main:6: note: def field(*, init: bool = ..., repr: bool = ..., hash: Optional[bool] = ..., compare: bool = ..., metadata: Optional[Mapping[str, Any]] = ..., kw_only: bool = ...) -> Any [builtins fixtures/dataclasses.pyi] [case testDataclassFieldWithPositionalArguments] -# flags: --python-version 3.7 from dataclasses import dataclass, field @dataclass @@ -1456,7 +1407,6 @@ class C: [builtins fixtures/dataclasses.pyi] [case testDataclassFieldWithTypedDictUnpacking] -# flags: --python-version 3.7 from dataclasses import dataclass, field from typing_extensions import TypedDict @@ -1505,6 +1455,22 @@ class Some: self.y = 1 # E: Trying to assign name "y" that is not in "__slots__" of type "__main__.Some" [builtins fixtures/dataclasses.pyi] +[case testDataclassWithSlotsDerivedFromNonSlot] +# flags: --python-version 3.10 +from dataclasses import dataclass + +class A: + pass + +@dataclass(slots=True) +class B(A): + x: int + + def __post_init__(self) -> None: + self.y = 42 + +[builtins fixtures/dataclasses.pyi] + [case testDataclassWithSlotsConflict] # flags: --python-version 3.10 from dataclasses import dataclass @@ -1543,6 +1509,35 @@ class Other: [builtins fixtures/dataclasses.pyi] +[case testDataclassWithSlotsRuntimeAttr] +# flags: --python-version 3.10 +from dataclasses import dataclass + +@dataclass(slots=True) +class Some: + x: int + y: str + z: bool + +reveal_type(Some.__slots__) # N: Revealed type is "Tuple[builtins.str, builtins.str, builtins.str]" + +@dataclass(slots=True) +class Other: + x: int + y: str + +reveal_type(Other.__slots__) # N: Revealed type is "Tuple[builtins.str, builtins.str]" + + +@dataclass +class NoSlots: + x: int + y: str + +NoSlots.__slots__ # E: "Type[NoSlots]" has no attribute "__slots__" +[builtins fixtures/dataclasses.pyi] + + [case testSlotsDefinitionWithTwoPasses1] # flags: --python-version 3.10 # https://github.com/python/mypy/issues/11821 @@ -1609,21 +1604,25 @@ class PublishedMessages: [builtins fixtures/dataclasses.pyi] [case testDataclassesAnyInherit] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import Any B: Any @dataclass class A(B): a: int +@dataclass +class C(B): + generated_args: int + generated_kwargs: int A(a=1, b=2) A(1) A(a="foo") # E: Argument "a" to "A" has incompatible type "str"; expected "int" +C(generated_args="foo", generated_kwargs="bar") # E: Argument "generated_args" to "C" has incompatible type "str"; expected "int" \ + # E: Argument "generated_kwargs" to "C" has incompatible type "str"; expected "int" [builtins fixtures/dataclasses.pyi] [case testDataclassesCallableFrozen] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import Any, Callable @dataclass(frozen=True) @@ -1639,7 +1638,6 @@ A(a=func).a = func # E: Property "a" defined in "A" is read-only [builtins fixtures/dataclasses.pyi] [case testDataclassInFunctionDoesNotCrash] -# flags: --python-version 3.7 from dataclasses import dataclass def foo() -> None: @@ -1669,7 +1667,6 @@ class Derived(A, B): [builtins fixtures/dataclasses.pyi] [case testDataclassGenericInheritance2] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import Any, Callable, Generic, TypeVar, List @@ -1701,7 +1698,6 @@ reveal_type(Child2[int, A]([A()], [1]).b) # N: Revealed type is "builtins.list[ [builtins fixtures/dataclasses.pyi] [case testDataclassInheritOptionalType] -# flags: --python-version 3.7 --strict-optional from dataclasses import dataclass from typing import Any, Callable, Generic, TypeVar, List, Optional @@ -1720,7 +1716,6 @@ Child(x=None, y=None) [builtins fixtures/dataclasses.pyi] [case testDataclassGenericInheritanceSpecialCase1] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import Generic, TypeVar, List @@ -1744,7 +1739,6 @@ def g(c: Child1) -> None: [builtins fixtures/dataclasses.pyi] [case testDataclassGenericInheritanceSpecialCase2] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import Generic, TypeVar @@ -1770,7 +1764,6 @@ Child2(y=1, key='') [builtins fixtures/dataclasses.pyi] [case testDataclassGenericWithBound] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import Generic, TypeVar @@ -1786,7 +1779,6 @@ C(x=2) [builtins fixtures/dataclasses.pyi] [case testDataclassGenericBoundToInvalidTypeVarDoesNotCrash] -# flags: --python-version 3.7 import dataclasses from typing import Generic, TypeVar @@ -1798,7 +1790,6 @@ class C(Generic[T]): [builtins fixtures/dataclasses.pyi] [case testDataclassInitVarCannotBeSet] -# flags: --python-version 3.7 from dataclasses import dataclass, InitVar @dataclass @@ -1818,7 +1809,6 @@ c.x # E: "C" has no attribute "x" [builtins fixtures/dataclasses.pyi] [case testDataclassCheckTypeVarBounds] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import ClassVar, Protocol, Dict, TypeVar, Generic @@ -1849,6 +1839,11 @@ class Two: bar: int t: Two reveal_type(t.__match_args__) # N: Revealed type is "Tuple[Literal['bar']]" +@dataclass +class Empty: + ... +e: Empty +reveal_type(e.__match_args__) # N: Revealed type is "Tuple[()]" [builtins fixtures/dataclasses.pyi] [case testDataclassWithoutMatchArgs] @@ -1935,7 +1930,6 @@ B = List[C] [builtins fixtures/dataclasses.pyi] [case testDataclassSelfType] -# flags: --strict-optional from dataclasses import dataclass from typing import Self, TypeVar, Generic, Optional @@ -2001,3 +1995,483 @@ class Bar(Foo): ... e: Element[Bar] reveal_type(e.elements) # N: Revealed type is "typing.Sequence[__main__.Element[__main__.Bar]]" [builtins fixtures/dataclasses.pyi] + +[case testIfConditionsInDefinition] +# flags: --python-version 3.11 --always-true TRUTH +from dataclasses import dataclass +from typing import TYPE_CHECKING + +TRUTH = False # Is set to --always-true + +@dataclass +class Foo: + if TYPE_CHECKING: + present_1: int + else: + skipped_1: int + if True: # Mypy does not know if it is True or False, so the block is used + present_2: int + if False: # Mypy does not know if it is True or False, so the block is used + present_3: int + if not TRUTH: + skipped_2: int + elif 123: + present_4: int + elif TRUTH: + present_5: int + else: + skipped_3: int + +Foo( + present_1=1, + present_2=2, + present_3=3, + present_4=4, + present_5=5, +) + +[builtins fixtures/dataclasses.pyi] + +[case testReplace] +from dataclasses import dataclass, replace, InitVar +from typing import ClassVar + +@dataclass +class A: + x: int + q: InitVar[int] + q2: InitVar[int] = 0 + c: ClassVar[int] + + +a = A(x=42, q=7) +a2 = replace(a) # E: Missing named argument "q" for "replace" of "A" +a2 = replace(a, q=42) +a2 = replace(a, x=42, q=42) +a2 = replace(a, x=42, q=42, c=7) # E: Unexpected keyword argument "c" for "replace" of "A" +a2 = replace(a, x='42', q=42) # E: Argument "x" to "replace" of "A" has incompatible type "str"; expected "int" +a2 = replace(a, q='42') # E: Argument "q" to "replace" of "A" has incompatible type "str"; expected "int" +reveal_type(a2) # N: Revealed type is "__main__.A" + +[builtins fixtures/tuple.pyi] + +[case testReplaceUnion] +from typing import Generic, Union, TypeVar +from dataclasses import dataclass, replace, InitVar + +T = TypeVar('T') + +@dataclass +class A(Generic[T]): + x: T # exercises meet(T=int, int) = int + y: bool # exercises meet(bool, int) = bool + z: str # exercises meet(str, bytes) = Never + w: dict # exercises meet(dict, Never) = Never + init_var: InitVar[int] # exercises (non-optional, optional) = non-optional + +@dataclass +class B: + x: int + y: int + z: bytes + init_var: int + + +a_or_b: Union[A[int], B] +_ = replace(a_or_b, x=42, y=True, init_var=42) +_ = replace(a_or_b, x=42, y=True) # E: Missing named argument "init_var" for "replace" of "Union[A[int], B]" +_ = replace(a_or_b, x=42, y=True, z='42', init_var=42) # E: Argument "z" to "replace" of "Union[A[int], B]" has incompatible type "str"; expected Never +_ = replace(a_or_b, x=42, y=True, w={}, init_var=42) # E: Argument "w" to "replace" of "Union[A[int], B]" has incompatible type "Dict[Never, Never]"; expected Never +_ = replace(a_or_b, y=42, init_var=42) # E: Argument "y" to "replace" of "Union[A[int], B]" has incompatible type "int"; expected "bool" + +[builtins fixtures/tuple.pyi] + +[case testReplaceUnionOfTypeVar] +from typing import Generic, Union, TypeVar +from dataclasses import dataclass, replace + +@dataclass +class A: + x: int + y: int + z: str + w: dict + +class B: + pass + +TA = TypeVar('TA', bound=A) +TB = TypeVar('TB', bound=B) + +def f(b_or_t: Union[TA, TB, int]) -> None: + a2 = replace(b_or_t) # E: Value of type variable "_DataclassT" of "replace" cannot be "Union[TA, TB, int]" + +[builtins fixtures/tuple.pyi] + +[case testReplaceTypeVarBoundNotDataclass] +from dataclasses import dataclass, replace +from typing import Union, TypeVar + +TInt = TypeVar('TInt', bound=int) +TAny = TypeVar('TAny') +TNone = TypeVar('TNone', bound=None) +TUnion = TypeVar('TUnion', bound=Union[str, int]) + +def f1(t: TInt) -> None: + _ = replace(t, x=42) # E: Value of type variable "_DataclassT" of "replace" cannot be "TInt" + +def f2(t: TAny) -> TAny: + return replace(t, x='spam') # E: Value of type variable "_DataclassT" of "replace" cannot be "TAny" + +def f3(t: TNone) -> TNone: + return replace(t, x='spam') # E: Value of type variable "_DataclassT" of "replace" cannot be "TNone" + +def f4(t: TUnion) -> TUnion: + return replace(t, x='spam') # E: Value of type variable "_DataclassT" of "replace" cannot be "TUnion" + +[builtins fixtures/tuple.pyi] + +[case testReplaceTypeVarBound] +from dataclasses import dataclass, replace +from typing import TypeVar + +@dataclass +class A: + x: int + +@dataclass +class B(A): + pass + +TA = TypeVar('TA', bound=A) + +def f(t: TA) -> TA: + t2 = replace(t, x=42) + reveal_type(t2) # N: Revealed type is "TA`-1" + _ = replace(t, x='42') # E: Argument "x" to "replace" of "TA" has incompatible type "str"; expected "int" + return t2 + +f(A(x=42)) +f(B(x=42)) + +[builtins fixtures/tuple.pyi] + +[case testReplaceAny] +from dataclasses import replace +from typing import Any + +a: Any +a2 = replace(a) +reveal_type(a2) # N: Revealed type is "Any" + +[builtins fixtures/tuple.pyi] + +[case testReplaceNotDataclass] +from dataclasses import replace + +replace(5) # E: Value of type variable "_DataclassT" of "replace" cannot be "int" + +class C: + pass + +replace(C()) # E: Value of type variable "_DataclassT" of "replace" cannot be "C" + +replace(None) # E: Value of type variable "_DataclassT" of "replace" cannot be "None" + +[builtins fixtures/tuple.pyi] + +[case testReplaceIsDataclass] +from dataclasses import is_dataclass, replace + +def f(x: object) -> None: + _ = replace(x) # E: Value of type variable "_DataclassT" of "replace" cannot be "object" + if is_dataclass(x): + _ = replace(x) # E: Value of type variable "_DataclassT" of "replace" cannot be "Union[DataclassInstance, Type[DataclassInstance]]" + if not isinstance(x, type): + _ = replace(x) + +[builtins fixtures/tuple.pyi] + +[case testReplaceGeneric] +from dataclasses import dataclass, replace, InitVar +from typing import ClassVar, Generic, TypeVar + +T = TypeVar('T') + +@dataclass +class A(Generic[T]): + x: T + +a = A(x=42) +reveal_type(a) # N: Revealed type is "__main__.A[builtins.int]" +a2 = replace(a, x=42) +reveal_type(a2) # N: Revealed type is "__main__.A[builtins.int]" +a2 = replace(a, x='42') # E: Argument "x" to "replace" of "A[int]" has incompatible type "str"; expected "int" +reveal_type(a2) # N: Revealed type is "__main__.A[builtins.int]" + +[builtins fixtures/tuple.pyi] + +[case testPostInitNotMethod] +def __post_init__() -> None: + pass + +[case testPostInitCorrectSignature] +from typing import Any, Generic, TypeVar, Callable, Self +from dataclasses import dataclass, InitVar + +@dataclass +class Test1: + x: int + def __post_init__(self) -> None: ... + +@dataclass +class Test2: + x: int + y: InitVar[int] + z: str + def __post_init__(self, y: int) -> None: ... + +@dataclass +class Test3: + x: InitVar[int] + y: InitVar[str] + def __post_init__(self, x: int, y: str) -> None: ... + +@dataclass +class Test4: + x: int + y: InitVar[str] + z: InitVar[bool] = True + def __post_init__(self, y: str, z: bool) -> None: ... + +@dataclass +class Test5: + y: InitVar[str] = 'a' + z: InitVar[bool] = True + def __post_init__(self, y: str = 'a', z: bool = True) -> None: ... + +F = TypeVar('F', bound=Callable[..., Any]) +def identity(f: F) -> F: return f + +@dataclass +class Test6: + y: InitVar[str] + @identity # decorated method works + def __post_init__(self, y: str) -> None: ... + +T = TypeVar('T') + +@dataclass +class Test7(Generic[T]): + t: InitVar[T] + def __post_init__(self, t: T) -> None: ... + +@dataclass +class Test8: + s: InitVar[Self] + def __post_init__(self, s: Self) -> None: ... +[builtins fixtures/dataclasses.pyi] + +[case testPostInitSubclassing] +from dataclasses import dataclass, InitVar + +@dataclass +class Base: + a: str + x: InitVar[int] + def __post_init__(self, x: int) -> None: ... + +@dataclass +class Child(Base): + b: str + y: InitVar[str] + def __post_init__(self, x: int, y: str) -> None: ... + +@dataclass +class GrandChild(Child): + c: int + z: InitVar[str] = "a" + def __post_init__(self, x: int, y: str, z: str) -> None: ... +[builtins fixtures/dataclasses.pyi] + +[case testPostInitNotADataclassCheck] +from dataclasses import dataclass, InitVar + +class Regular: + __post_init__ = 1 # can be whatever + +class Base: + x: InitVar[int] + def __post_init__(self) -> None: ... # can be whatever + +@dataclass +class Child(Base): + y: InitVar[str] + def __post_init__(self, y: str) -> None: ... +[builtins fixtures/dataclasses.pyi] + +[case testPostInitMissingParam] +from dataclasses import dataclass, InitVar + +@dataclass +class Child: + y: InitVar[str] + def __post_init__(self) -> None: ... +[builtins fixtures/dataclasses.pyi] +[out] +main:6: error: Signature of "__post_init__" incompatible with supertype "dataclass" +main:6: note: Superclass: +main:6: note: def __post_init__(self: Child, y: str) -> None +main:6: note: Subclass: +main:6: note: def __post_init__(self: Child) -> None + +[case testPostInitWrongTypeAndName] +from dataclasses import dataclass, InitVar + +@dataclass +class Test1: + y: InitVar[str] + def __post_init__(self, x: int) -> None: ... # E: Argument 2 of "__post_init__" is incompatible with supertype "dataclass"; supertype defines the argument type as "str" + +@dataclass +class Test2: + y: InitVar[str] = 'a' + def __post_init__(self, x: int) -> None: ... # E: Argument 2 of "__post_init__" is incompatible with supertype "dataclass"; supertype defines the argument type as "str" +[builtins fixtures/dataclasses.pyi] + +[case testPostInitExtraParam] +from dataclasses import dataclass, InitVar + +@dataclass +class Child: + y: InitVar[str] + def __post_init__(self, y: str, z: int) -> None: ... +[builtins fixtures/dataclasses.pyi] +[out] +main:6: error: Signature of "__post_init__" incompatible with supertype "dataclass" +main:6: note: Superclass: +main:6: note: def __post_init__(self: Child, y: str) -> None +main:6: note: Subclass: +main:6: note: def __post_init__(self: Child, y: str, z: int) -> None + +[case testPostInitReturnType] +from dataclasses import dataclass, InitVar + +@dataclass +class Child: + y: InitVar[str] + def __post_init__(self, y: str) -> int: ... # E: Return type "int" of "__post_init__" incompatible with return type "None" in supertype "dataclass" +[builtins fixtures/dataclasses.pyi] + +[case testPostInitDecoratedMethodError] +from dataclasses import dataclass, InitVar +from typing import Any, Callable, TypeVar + +F = TypeVar('F', bound=Callable[..., Any]) +def identity(f: F) -> F: return f + +@dataclass +class Klass: + y: InitVar[str] + @identity + def __post_init__(self) -> None: ... +[builtins fixtures/dataclasses.pyi] +[out] +main:11: error: Signature of "__post_init__" incompatible with supertype "dataclass" +main:11: note: Superclass: +main:11: note: def __post_init__(self: Klass, y: str) -> None +main:11: note: Subclass: +main:11: note: def __post_init__(self: Klass) -> None + +[case testPostInitIsNotAFunction] +from dataclasses import dataclass, InitVar + +@dataclass +class Test: + y: InitVar[str] + __post_init__ = 1 # E: "__post_init__" method must be an instance method +[builtins fixtures/dataclasses.pyi] + +[case testPostInitClassMethod] +from dataclasses import dataclass, InitVar + +@dataclass +class Test: + y: InitVar[str] + @classmethod + def __post_init__(cls) -> None: ... +[builtins fixtures/dataclasses.pyi] +[out] +main:7: error: Signature of "__post_init__" incompatible with supertype "dataclass" +main:7: note: Superclass: +main:7: note: def __post_init__(self: Test, y: str) -> None +main:7: note: Subclass: +main:7: note: @classmethod +main:7: note: def __post_init__(cls: Type[Test]) -> None + +[case testPostInitStaticMethod] +from dataclasses import dataclass, InitVar + +@dataclass +class Test: + y: InitVar[str] + @staticmethod + def __post_init__() -> None: ... +[builtins fixtures/dataclasses.pyi] +[out] +main:7: error: Signature of "__post_init__" incompatible with supertype "dataclass" +main:7: note: Superclass: +main:7: note: def __post_init__(self: Test, y: str) -> None +main:7: note: Subclass: +main:7: note: @staticmethod +main:7: note: def __post_init__() -> None + +[case testProtocolNoCrash] +from typing import Protocol, Union, ClassVar +from dataclasses import dataclass, field + +DEFAULT = 0 + +@dataclass +class Test(Protocol): + x: int + def reset(self) -> None: + self.x = DEFAULT +[builtins fixtures/dataclasses.pyi] + +[case testProtocolNoCrashOnJoining] +from dataclasses import dataclass +from typing import Protocol + +@dataclass +class MyDataclass(Protocol): ... + +a: MyDataclass +b = [a, a] # trigger joining the types + +[builtins fixtures/dataclasses.pyi] + +[case testPropertyAndFieldRedefinitionNoCrash] +from dataclasses import dataclass + +@dataclass +class Foo: + @property + def c(self) -> int: + return 0 + + c: int # E: Name "c" already defined on line 5 +[builtins fixtures/dataclasses.pyi] + +[case testDataclassInheritanceWorksWithExplicitOverrides] +# flags: --enable-error-code explicit-override +from dataclasses import dataclass + +@dataclass +class Base: + x: int + +@dataclass +class Child(Base): + y: int +[builtins fixtures/dataclasses.pyi] diff --git a/test-data/unit/check-dynamic-typing.test b/test-data/unit/check-dynamic-typing.test index dd4cc1579639..0dc05a7a0ea1 100644 --- a/test-data/unit/check-dynamic-typing.test +++ b/test-data/unit/check-dynamic-typing.test @@ -4,8 +4,8 @@ [case testAssignmentWithDynamic] from typing import Any -d = None # type: Any -a = None # type: A +d: Any +a: A if int(): a = d # Everything ok @@ -20,8 +20,9 @@ class A: pass [case testMultipleAssignmentWithDynamic] from typing import Any -d = None # type: Any -a, b = None, None # type: (A, B) +d: Any +a: A +b: B if int(): d, a = b, b # E: Incompatible types in assignment (expression has type "B", variable has type "A") @@ -51,7 +52,8 @@ from typing import Any def f(x: Any) -> 'A': pass -a, b = None, None # type: (A, B) +a: A +b: B if int(): b = f(a) # E: Incompatible types in assignment (expression has type "A", variable has type "B") @@ -75,7 +77,8 @@ from typing import Any def f(x: 'A') -> Any: pass -a, b = None, None # type: (A, B) +a: A +b: B a = f(b) # E: Argument 1 to "f" has incompatible type "B"; expected "A" @@ -88,10 +91,10 @@ class B: pass [case testBinaryOperationsWithDynamicLeftOperand] from typing import Any -d = None # type: Any -a = None # type: A -c = None # type: C -b = None # type: bool +d: Any +a: A +c: C +b: bool n = 0 d in a # E: Unsupported right operand type for in ("A") @@ -151,10 +154,10 @@ class dict: pass [case testBinaryOperationsWithDynamicAsRightOperand] from typing import Any -d = None # type: Any -a = None # type: A -c = None # type: C -b = None # type: bool +d: Any +a: A +c: C +b: bool n = 0 a and d @@ -224,9 +227,9 @@ class dict: pass [case testDynamicWithUnaryExpressions] from typing import Any -d = None # type: Any -a = None # type: A -b = None # type: bool +d: Any +a: A +b: bool if int(): a = not d # E: Incompatible types in assignment (expression has type "bool", variable has type "A") if int(): @@ -238,8 +241,8 @@ class A: pass [case testDynamicWithMemberAccess] from typing import Any -d = None # type: Any -a = None # type: A +d: Any +a: A if int(): a = d.foo(a()) # E: "A" not callable @@ -256,8 +259,8 @@ class A: pass [case testIndexingWithDynamic] from typing import Any -d = None # type: Any -a = None # type: A +d: Any +a: A if int(): a = d[a()] # E: "A" not callable @@ -270,10 +273,10 @@ d[a], d[a] = a, a class A: pass -[case testTupleExpressionsWithDynamci] +[case testTupleExpressionsWithDynamic] from typing import Tuple, Any -t2 = None # type: Tuple[A, A] -d = None # type: Any +t2: Tuple[A, A] +d: Any if int(): t2 = (d, d, d) # E: Incompatible types in assignment (expression has type "Tuple[Any, Any, Any]", variable has type "Tuple[A, A]") @@ -289,9 +292,9 @@ class A: pass class B: pass def f() -> None: pass -d = None # type: Any -a = None # type: A -b = None # type: B +d: Any +a: A +b: B if int(): b = cast(A, d) # E: Incompatible types in assignment (expression has type "A", variable has type "B") if int(): @@ -309,8 +312,8 @@ def g(a: 'A') -> None: class A: pass class B: pass -d = None # type: Any -t = None # type: Tuple[A, A] +d: Any +t: Tuple[A, A] # TODO: callable types, overloaded functions d = None # All ok @@ -362,10 +365,10 @@ class A: pass [case testImplicitGlobalFunctionSignature] from typing import Any, Callable -x = None # type: Any -a = None # type: A -g = None # type: Callable[[], None] -h = None # type: Callable[[A], None] +x: Any +a: A +g: Callable[[], None] +h: Callable[[A], None] def f(x): pass @@ -384,10 +387,10 @@ class A: pass [case testImplicitGlobalFunctionSignatureWithDifferentArgCounts] from typing import Callable -g0 = None # type: Callable[[], None] -g1 = None # type: Callable[[A], None] -g2 = None # type: Callable[[A, A], None] -a = None # type: A +g0: Callable[[], None] +g1: Callable[[A], None] +g2: Callable[[A, A], None] +a: A def f0(): pass def f2(x, y): pass @@ -415,16 +418,17 @@ from typing import Callable class A: pass class B: pass -a, b = None, None # type: (A, B) +a: A +b: B def f01(x = b): pass def f13(x, y = b, z = b): pass -g0 = None # type: Callable[[], None] -g1 = None # type: Callable[[A], None] -g2 = None # type: Callable[[A, A], None] -g3 = None # type: Callable[[A, A, A], None] -g4 = None # type: Callable[[A, A, A, A], None] +g0: Callable[[], None] +g1: Callable[[A], None] +g2: Callable[[A, A], None] +g3: Callable[[A, A, A], None] +g4: Callable[[A, A, A, A], None] f01(a, a) # E: Too many arguments for "f01" f13() # E: Missing positional argument "x" in call to "f13" @@ -456,7 +460,7 @@ if int(): [builtins fixtures/tuple.pyi] [case testSkipTypeCheckingWithImplicitSignature] -a = None # type: A +a: A def f(): a() def g(x): @@ -469,7 +473,7 @@ class A: pass [builtins fixtures/bool.pyi] [case testSkipTypeCheckingWithImplicitSignatureAndDefaultArgs] -a = None # type: A +a: A def f(x=a()): a() def g(x, y=a, z=a()): @@ -478,10 +482,10 @@ class A: pass [case testImplicitMethodSignature] from typing import Callable -g0 = None # type: Callable[[], None] -g1 = None # type: Callable[[A], None] -g2 = None # type: Callable[[A, A], None] -a = None # type: A +g0: Callable[[], None] +g1: Callable[[A], None] +g2: Callable[[A, A], None] +a: A if int(): g0 = a.f # E: Incompatible types in assignment (expression has type "Callable[[Any], Any]", variable has type "Callable[[], None]") @@ -502,7 +506,7 @@ if int(): [case testSkipTypeCheckingImplicitMethod] -a = None # type: A +a: A class A: def f(self): a() @@ -511,9 +515,9 @@ class A: [case testImplicitInheritedMethod] from typing import Callable -g0 = None # type: Callable[[], None] -g1 = None # type: Callable[[A], None] -a = None # type: A +g0: Callable[[], None] +g1: Callable[[A], None] +a: A if int(): g0 = a.f # E: Incompatible types in assignment (expression has type "Callable[[Any], Any]", variable has type "Callable[[], None]") @@ -559,9 +563,9 @@ from typing import Callable class A: def __init__(self, a, b): pass -f1 = None # type: Callable[[A], A] -f2 = None # type: Callable[[A, A], A] -a = None # type: A +f1: Callable[[A], A] +f2: Callable[[A, A], A] +a: A A(a) # E: Missing positional argument "b" in call to "A" if int(): @@ -576,7 +580,7 @@ class A: pass class B: def __init__(self): pass -t = None # type: type +t: type t = A t = B -- Type compatibility @@ -585,11 +589,11 @@ t = B [case testTupleTypeCompatibility] from typing import Any, Tuple -t1 = None # type: Tuple[Any, A] -t2 = None # type: Tuple[A, Any] -t3 = None # type: Tuple[Any, Any] -t4 = None # type: Tuple[A, A] -t5 = None # type: Tuple[Any, Any, Any] +t1: Tuple[Any, A] +t2: Tuple[A, Any] +t3: Tuple[Any, Any] +t4: Tuple[A, A] +t5: Tuple[Any, Any, Any] def f(): t1, t2, t3, t4, t5 # Prevent redefinition @@ -614,11 +618,11 @@ class A: pass [builtins fixtures/tuple.pyi] [case testFunctionTypeCompatibilityAndReturnTypes] -from typing import Any, Callable -f1 = None # type: Callable[[], Any] -f11 = None # type: Callable[[], Any] -f2 = None # type: Callable[[], A] -f3 = None # type: Callable[[], None] +from typing import Any, Callable, Optional +f1: Callable[[], Any] +f11: Callable[[], Any] +f2: Callable[[], Optional[A]] +f3: Callable[[], None] f2 = f3 @@ -631,9 +635,9 @@ class A: pass [case testFunctionTypeCompatibilityAndArgumentTypes] from typing import Any, Callable -f1 = None # type: Callable[[A, Any], None] -f2 = None # type: Callable[[Any, A], None] -f3 = None # type: Callable[[A, A], None] +f1: Callable[[A, Any], None] +f2: Callable[[Any, A], None] +f3: Callable[[A, A], None] f1 = f1 f1 = f2 @@ -651,8 +655,8 @@ class A: pass [case testFunctionTypeCompatibilityAndArgumentCounts] from typing import Any, Callable -f1 = None # type: Callable[[Any], None] -f2 = None # type: Callable[[Any, Any], None] +f1: Callable[[Any], None] +f2: Callable[[Any, Any], None] if int(): f1 = f2 # E: Incompatible types in assignment (expression has type "Callable[[Any, Any], None]", variable has type "Callable[[Any], None]") @@ -664,7 +668,8 @@ if int(): [case testOverridingMethodWithDynamicTypes] from typing import Any -a, b = None, None # type: (A, B) +a: A +b: B b.f(b) # E: Argument 1 to "f" of "B" has incompatible type "B"; expected "A" a = a.f(b) @@ -682,8 +687,8 @@ class A(B): [builtins fixtures/tuple.pyi] [case testOverridingMethodWithImplicitDynamicTypes] - -a, b = None, None # type: (A, B) +a: A +b: B b.f(b) # E: Argument 1 to "f" of "B" has incompatible type "B"; expected "A" a = a.f(b) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 80a7ca7ff99f..e8e65f464eaf 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -297,7 +297,7 @@ f(E.X) from enum import IntEnum class E(IntEnum): a = 1 -x = None # type: int +x: int reveal_type(E(x)) [out] main:5: note: Revealed type is "__main__.E" @@ -306,7 +306,7 @@ main:5: note: Revealed type is "__main__.E" from enum import IntEnum class E(IntEnum): a = 1 -s = None # type: str +s: str reveal_type(E[s]) [out] main:5: note: Revealed type is "__main__.E" @@ -452,55 +452,39 @@ from enum import Enum, IntEnum PictureSize = Enum('PictureSize', 'P0 P1 P2 P3 P4 P5 P6 P7 P8', type=str, module=__name__) fake_enum1 = Enum('fake_enum1', ['a', 'b']) -fake_enum2 = Enum('fake_enum1', names=['a', 'b']) -fake_enum3 = Enum(value='fake_enum1', names=['a', 'b']) -fake_enum4 = Enum(value='fake_enum1', names=['a', 'b'] , module=__name__) +fake_enum2 = Enum('fake_enum2', names=['a', 'b']) +fake_enum3 = Enum(value='fake_enum3', names=['a', 'b']) +fake_enum4 = Enum(value='fake_enum4', names=['a', 'b'] , module=__name__) [case testFunctionalEnumErrors] from enum import Enum, IntEnum -A = Enum('A') -B = Enum('B', 42) -C = Enum('C', 'a b', 'x', 'y', 'z', 'p', 'q') -D = Enum('D', foo) +A = Enum('A') # E: Too few arguments for Enum() +B = Enum('B', 42) # E: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members +C = Enum('C', 'a b', 'x', 'y', 'z', 'p', 'q') # E: Too many arguments for Enum() +D = Enum('D', foo) # E: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members \ + # E: Name "foo" is not defined bar = 'x y z' -E = Enum('E', bar) -I = IntEnum('I') -J = IntEnum('I', 42) -K = IntEnum('I', 'p q', 'x', 'y', 'z', 'p', 'q') -L = Enum('L', ' ') -M = Enum('M', ()) -N = IntEnum('M', []) -P = Enum('P', [42]) -Q = Enum('Q', [('a', 42, 0)]) -R = IntEnum('R', [[0, 42]]) -S = Enum('S', {1: 1}) -T = Enum('T', keyword='a b') -U = Enum('U', *['a']) -V = Enum('U', **{'a': 1}) +E = Enum('E', bar) # E: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members +I = IntEnum('I') # E: Too few arguments for IntEnum() +J = IntEnum('I', 42) # E: Second argument of IntEnum() must be string, tuple, list or dict literal for mypy to determine Enum members +K = IntEnum('I', 'p q', 'x', 'y', 'z', 'p', 'q') # E: Too many arguments for IntEnum() +L = Enum('L', ' ') # E: Enum() needs at least one item +M = Enum('M', ()) # E: Enum() needs at least one item +N = IntEnum('M', []) # E: IntEnum() needs at least one item +P = Enum('P', [42]) # E: Enum() with tuple or list expects strings or (name, value) pairs +Q = Enum('Q', [('a', 42, 0)]) # E: Enum() with tuple or list expects strings or (name, value) pairs +R = IntEnum('R', [[0, 42]]) # E: IntEnum() with tuple or list expects strings or (name, value) pairs +S = Enum('S', {1: 1}) # E: Enum() with dict literal requires string literals +T = Enum('T', keyword='a b') # E: Unexpected keyword argument "keyword" +U = Enum('U', *['a']) # E: Unexpected arguments to Enum() +V = Enum('U', **{'a': 1}) # E: Unexpected arguments to Enum() W = Enum('W', 'a b') -W.c +W.c # E: "Type[W]" has no attribute "c" +X = Enum('Something', 'a b') # E: String argument 1 "Something" to enum.Enum(...) does not match variable name "X" +reveal_type(X.a) # N: Revealed type is "Literal[__main__.Something@23.a]?" +X.asdf # E: "Type[Something@23]" has no attribute "asdf" + [typing fixtures/typing-medium.pyi] -[out] -main:2: error: Too few arguments for Enum() -main:3: error: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members -main:4: error: Too many arguments for Enum() -main:5: error: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members -main:5: error: Name "foo" is not defined -main:7: error: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members -main:8: error: Too few arguments for IntEnum() -main:9: error: Second argument of IntEnum() must be string, tuple, list or dict literal for mypy to determine Enum members -main:10: error: Too many arguments for IntEnum() -main:11: error: Enum() needs at least one item -main:12: error: Enum() needs at least one item -main:13: error: IntEnum() needs at least one item -main:14: error: Enum() with tuple or list expects strings or (name, value) pairs -main:15: error: Enum() with tuple or list expects strings or (name, value) pairs -main:16: error: IntEnum() with tuple or list expects strings or (name, value) pairs -main:17: error: Enum() with dict literal requires string literals -main:18: error: Unexpected keyword argument "keyword" -main:19: error: Unexpected arguments to Enum() -main:20: error: Unexpected arguments to Enum() -main:22: error: "Type[W]" has no attribute "c" [case testFunctionalEnumFlag] from enum import Flag, IntFlag @@ -953,7 +937,6 @@ else: [builtins fixtures/bool.pyi] [case testEnumReachabilityWithNone] -# flags: --strict-optional from enum import Enum from typing import Optional @@ -1016,7 +999,6 @@ reveal_type(x3) # N: Revealed type is "Union[__main__.Foo, __main__.Bar]" [builtins fixtures/bool.pyi] [case testEnumReachabilityPEP484ExampleWithFinal] -# flags: --strict-optional from typing import Union from typing_extensions import Final from enum import Enum @@ -1063,7 +1045,6 @@ def process(response: Union[str, Reason] = '') -> str: [case testEnumReachabilityPEP484ExampleSingleton] -# flags: --strict-optional from typing import Union from typing_extensions import Final from enum import Enum @@ -1088,7 +1069,6 @@ def func(x: Union[int, None, Empty] = _empty) -> int: [builtins fixtures/primitives.pyi] [case testEnumReachabilityPEP484ExampleSingletonWithMethod] -# flags: --strict-optional from typing import Union from typing_extensions import Final from enum import Enum @@ -1121,7 +1101,7 @@ from enum import Enum class A: def __init__(self) -> None: - self.b = Enum("x", [("foo", "bar")]) # E: Enum type as attribute is not supported + self.b = Enum("b", [("foo", "bar")]) # E: Enum type as attribute is not supported reveal_type(A().b) # N: Revealed type is "Any" @@ -1345,7 +1325,6 @@ class Foo(bytes, Enum): a = Foo.A reveal_type(a.value) # N: Revealed type is "Any" reveal_type(a._value_) # N: Revealed type is "Any" -[builtins fixtures/__new__.pyi] [builtins fixtures/primitives.pyi] [typing fixtures/typing-medium.pyi] @@ -1368,7 +1347,6 @@ class Bar(Foo): a = Bar.A reveal_type(a.value) # N: Revealed type is "Any" reveal_type(a._value_) # N: Revealed type is "Any" -[builtins fixtures/__new__.pyi] [builtins fixtures/primitives.pyi] [typing fixtures/typing-medium.pyi] @@ -1447,6 +1425,10 @@ from enum import Enum class Correct(Enum): x = 'y' y = 'x' +class Correct2(Enum): + x = 'y' + __z = 'y' + __z = 'x' class Foo(Enum): A = 1 A = 'a' # E: Attempted to reuse member name "A" in Enum definition "Foo" \ @@ -2127,3 +2109,32 @@ class AllPartialList(Enum): def check(self) -> None: reveal_type(self.value) # N: Revealed type is "builtins.list[Any]" + +[case testEnumPrivateAttributeNotMember] +from enum import Enum + +class MyEnum(Enum): + A = 1 + B = 2 + __my_dict = {A: "ham", B: "spam"} + +# TODO: change the next line to use MyEnum._MyEnum__my_dict when mypy implements name mangling +x: MyEnum = MyEnum.__my_dict # E: Incompatible types in assignment (expression has type "Dict[int, str]", variable has type "MyEnum") + +[case testEnumWithPrivateAttributeReachability] +# flags: --warn-unreachable +from enum import Enum + +class MyEnum(Enum): + A = 1 + B = 2 + __my_dict = {A: "ham", B: "spam"} + +e: MyEnum +if e == MyEnum.A: + reveal_type(e) # N: Revealed type is "Literal[__main__.MyEnum.A]" +elif e == MyEnum.B: + reveal_type(e) # N: Revealed type is "Literal[__main__.MyEnum.B]" +else: + reveal_type(e) # E: Statement is unreachable +[builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 8b3567ab7cf6..9d49480539e0 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -42,7 +42,7 @@ def f(): # E: Type signature has too many arguments [syntax] # type: (int) -> None 1 -x = 0 # type: x y # E: syntax error in type comment "x y" [syntax] +x = 0 # type: x y # E: Syntax error in type comment "x y" [syntax] [case testErrorCodeSyntaxError3] # This is a bit inconsistent -- syntax error would be more logical? @@ -83,7 +83,7 @@ b = 'x'.foobar(c) # type: int # type: ignore[name-defined, xyz] # E: "str" has [case testErrorCodeWarnUnusedIgnores1] # flags: --warn-unused-ignores -x # type: ignore[name-defined, attr-defined] # E: Unused "type: ignore[attr-defined]" comment +x # type: ignore[name-defined, attr-defined] # E: Unused "type: ignore[attr-defined]" comment [unused-ignore] [case testErrorCodeWarnUnusedIgnores2] # flags: --warn-unused-ignores @@ -91,19 +91,19 @@ x # type: ignore[name-defined, attr-defined] # E: Unused "type: ignore[attr-defi [case testErrorCodeWarnUnusedIgnores3] # flags: --warn-unused-ignores -"x".foobar(y) # type: ignore[name-defined, attr-defined, xyz] # E: Unused "type: ignore[xyz]" comment +"x".foobar(y) # type: ignore[name-defined, attr-defined, xyz] # E: Unused "type: ignore[xyz]" comment [unused-ignore] [case testErrorCodeWarnUnusedIgnores4] # flags: --warn-unused-ignores -"x".foobar(y) # type: ignore[name-defined, attr-defined, valid-type] # E: Unused "type: ignore[valid-type]" comment +"x".foobar(y) # type: ignore[name-defined, attr-defined, valid-type] # E: Unused "type: ignore[valid-type]" comment [unused-ignore] [case testErrorCodeWarnUnusedIgnores5] # flags: --warn-unused-ignores -"x".foobar(y) # type: ignore[name-defined, attr-defined, valid-type, xyz] # E: Unused "type: ignore[valid-type, xyz]" comment +"x".foobar(y) # type: ignore[name-defined, attr-defined, valid-type, xyz] # E: Unused "type: ignore[valid-type, xyz]" comment [unused-ignore] [case testErrorCodeWarnUnusedIgnores6_NoDetailWhenSingleErrorCode] # flags: --warn-unused-ignores -"x" # type: ignore[name-defined] # E: Unused "type: ignore" comment +"x" # type: ignore[name-defined] # E: Unused "type: ignore" comment [unused-ignore] [case testErrorCodeMissingWhenRequired] # flags: --enable-error-code ignore-without-code @@ -114,18 +114,20 @@ z # type: ignore[name-defined] [case testErrorCodeMissingDoesntTrampleUnusedIgnoresWarning] # flags: --enable-error-code ignore-without-code --warn-unused-ignores -"x" # type: ignore # E: Unused "type: ignore" comment -"y" # type: ignore[ignore-without-code] # E: Unused "type: ignore" comment -z # type: ignore[ignore-without-code] # E: Unused "type: ignore" comment # E: Name "z" is not defined [name-defined] # N: Error code "name-defined" not covered by "type: ignore" comment +"x" # type: ignore # E: Unused "type: ignore" comment [unused-ignore] +"y" # type: ignore[ignore-without-code] # E: Unused "type: ignore" comment [unused-ignore] +z # type: ignore[ignore-without-code] # E: Unused "type: ignore" comment [unused-ignore] \ + # E: Name "z" is not defined [name-defined] \ + # N: Error code "name-defined" not covered by "type: ignore" comment [case testErrorCodeMissingWholeFileIgnores] # flags: --enable-error-code ignore-without-code # type: ignore # whole file ignore x -y # type: ignore # ignore the lack of error code since we're ignore the whole file +y # type: ignore # ignore the lack of error code since we ignore the whole file [case testErrorCodeMissingMultiple] -# flags: --enable-error-code ignore-without-code --python-version 3.7 +# flags: --enable-error-code ignore-without-code from __future__ import annotations class A: attr: int @@ -181,7 +183,7 @@ from defusedxml import xyz # type: ignore[import] [case testErrorCodeBadIgnore] import nostub # type: ignore xyz # E: Invalid "type: ignore" comment [syntax] \ - # E: Cannot find implementation or library stub for module named "nostub" [import] \ + # E: Cannot find implementation or library stub for module named "nostub" [import-not-found] \ # N: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports import nostub # type: ignore[ # E: Invalid "type: ignore" comment [syntax] import nostub # type: ignore[foo # E: Invalid "type: ignore" comment [syntax] @@ -209,7 +211,7 @@ def f(x, # type: int # type: ignore[ pass [out] main:2: error: Invalid "type: ignore" comment [syntax] -main:2: error: Cannot find implementation or library stub for module named "nostub" [import] +main:2: error: Cannot find implementation or library stub for module named "nostub" [import-not-found] main:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports main:3: error: Invalid "type: ignore" comment [syntax] main:4: error: Invalid "type: ignore" comment [syntax] @@ -520,12 +522,12 @@ if int() is str(): # E: Non-overlapping identity check (left operand type: "int [builtins fixtures/primitives.pyi] [case testErrorCodeMissingModule] -from defusedxml import xyz # E: Cannot find implementation or library stub for module named "defusedxml" [import] -from nonexistent import foobar # E: Cannot find implementation or library stub for module named "nonexistent" [import] -import nonexistent2 # E: Cannot find implementation or library stub for module named "nonexistent2" [import] -from nonexistent3 import * # E: Cannot find implementation or library stub for module named "nonexistent3" [import] +from defusedxml import xyz # E: Cannot find implementation or library stub for module named "defusedxml" [import-not-found] +from nonexistent import foobar # E: Cannot find implementation or library stub for module named "nonexistent" [import-not-found] +import nonexistent2 # E: Cannot find implementation or library stub for module named "nonexistent2" [import-not-found] +from nonexistent3 import * # E: Cannot find implementation or library stub for module named "nonexistent3" [import-not-found] from pkg import bad # E: Module "pkg" has no attribute "bad" [attr-defined] -from pkg.bad2 import bad3 # E: Cannot find implementation or library stub for module named "pkg.bad2" [import] \ +from pkg.bad2 import bad3 # E: Cannot find implementation or library stub for module named "pkg.bad2" [import-not-found] \ # N: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports [file pkg/__init__.py] @@ -551,15 +553,15 @@ from typing import Callable def f() -> None: pass -x = f() # E: "f" does not return a value [func-returns-value] +x = f() # E: "f" does not return a value (it only ever returns None) [func-returns-value] class A: def g(self) -> None: pass -y = A().g() # E: "g" of "A" does not return a value [func-returns-value] +y = A().g() # E: "g" of "A" does not return a value (it only ever returns None) [func-returns-value] c: Callable[[], None] -z = c() # E: Function does not return a value [func-returns-value] +z = c() # E: Function does not return a value (it only ever returns None) [func-returns-value] [case testErrorCodeInstantiateAbstract] from abc import abstractmethod @@ -638,8 +640,8 @@ def g() -> int: '%d' % 'no' # E: Incompatible types in string interpolation (expression has type "str", placeholder has type "Union[int, float, SupportsInt]") [str-format] '%d + %d' % (1, 2, 3) # E: Not all arguments converted during string formatting [str-format] -'{}'.format(b'abc') # E: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior [str-bytes-safe] -'%s' % b'abc' # E: On Python 3 formatting "b'abc'" with "%s" produces "b'abc'", not "abc"; use "%r" if this is desired behavior [str-bytes-safe] +'{}'.format(b'abc') # E: If x = b'abc' then f"{x}" or "{}".format(x) produces "b'abc'", not "abc". If this is desired behavior, use f"{x!r}" or "{!r}".format(x). Otherwise, decode the bytes [str-bytes-safe] +'%s' % b'abc' # E: If x = b'abc' then "%s" % x produces "b'abc'", not "abc". If this is desired behavior use "%r" % x. Otherwise, decode the bytes [str-bytes-safe] [builtins fixtures/primitives.pyi] [typing fixtures/typing-medium.pyi] @@ -718,8 +720,8 @@ main:2: error: Name "y" is not defined [name-defined] x = y # type: int # type: ignored[foo] x = y # type: int # type: ignored [foo] [out] -main:1: error: syntax error in type comment "int" [syntax] -main:2: error: syntax error in type comment "int" [syntax] +main:1: error: Syntax error in type comment "int" [syntax] +main:2: error: Syntax error in type comment "int" [syntax] [case testErrorCode__exit__Return] class InvalidReturn: @@ -730,7 +732,6 @@ class InvalidReturn: [builtins fixtures/bool.pyi] [case testErrorCodeOverloadedOperatorMethod] -# flags: --strict-optional from typing import Optional, overload class A: @@ -756,7 +757,6 @@ class C: x - C() # type: ignore[operator] [case testErrorCodeMultiLineBinaryOperatorOperand] -# flags: --strict-optional from typing import Optional class C: pass @@ -805,6 +805,22 @@ j = [x for x in lst if False] # E: If condition in comprehension is a k = [x for x in lst if isinstance(x, int) or foo()] # E: If condition in comprehension is always true [redundant-expr] [builtins fixtures/isinstancelist.pyi] +[case testRedundantExprTruthiness] +# flags: --enable-error-code redundant-expr +from typing import List + +def maybe() -> bool: ... + +class Foo: + def __init__(self, x: List[int]) -> None: + self.x = x or [] + + def method(self) -> int: + if not self.x or maybe(): + return 1 + return 2 +[builtins fixtures/list.pyi] + [case testNamedTupleNameMismatch] from typing import NamedTuple @@ -879,17 +895,16 @@ if any_or_object: [builtins fixtures/list.pyi] [case testTruthyFunctions] -# flags: --strict-optional def f(): pass -if f: # E: Function "Callable[[], Any]" could always be true in boolean context [truthy-function] +if f: # E: Function "f" could always be true in boolean context [truthy-function] pass -if not f: # E: Function "Callable[[], Any]" could always be true in boolean context [truthy-function] +if not f: # E: Function "f" could always be true in boolean context [truthy-function] pass -conditional_result = 'foo' if f else 'bar' # E: Function "Callable[[], Any]" could always be true in boolean context [truthy-function] +conditional_result = 'foo' if f else 'bar' # E: Function "f" could always be true in boolean context [truthy-function] [case testTruthyIterable] -# flags: --strict-optional --enable-error-code truthy-iterable +# flags: --enable-error-code truthy-iterable from typing import Iterable def func(var: Iterable[str]) -> None: if var: # E: "var" has type "Iterable[str]" which can always be true in boolean context. Consider using "Collection[str]" instead. [truthy-iterable] @@ -960,7 +975,13 @@ def f(d: D, s: str) -> None: [typing fixtures/typing-typeddict.pyi] [case testRecommendErrorCode] -# type: ignore[whatever] # E: type ignore with error code is not supported for modules; use `# mypy: disable-error-code=...` [syntax] +# type: ignore[whatever] # E: type ignore with error code is not supported for modules; use `# mypy: disable-error-code="whatever"` [syntax] \ + # N: Error code "syntax" not covered by "type: ignore" comment +1 + "asdf" + +[case testRecommendErrorCode2] +# type: ignore[whatever, other] # E: type ignore with error code is not supported for modules; use `# mypy: disable-error-code="whatever, other"` [syntax] \ + # N: Error code "syntax" not covered by "type: ignore" comment 1 + "asdf" [case testShowErrorCodesInConfig] @@ -973,7 +994,6 @@ var: int = "" # E: Incompatible types in assignment (expression has type "str", show_error_codes = True [case testErrorCodeUnsafeSuper_no_empty] -# flags: --strict-optional from abc import abstractmethod class Base: @@ -986,7 +1006,6 @@ class Sub(Base): [builtins fixtures/exception.pyi] [case testDedicatedErrorCodeForEmpty_no_empty] -# flags: --strict-optional from typing import Optional def foo() -> int: ... # E: Missing return statement [empty-body] def bar() -> None: ... @@ -1050,4 +1069,124 @@ A.f = h # OK class A: def f(self) -> None: pass def h(self: A) -> None: pass -A.f = h # type: ignore[assignment] # E: Unused "type: ignore" comment, use narrower [method-assign] instead of [assignment] +A.f = h # type: ignore[assignment] # E: Unused "type: ignore" comment, use narrower [method-assign] instead of [assignment] code [unused-ignore] + +[case testUnusedIgnoreEnableCode] +# flags: --enable-error-code=unused-ignore +x = 1 # type: ignore # E: Unused "type: ignore" comment [unused-ignore] + +[case testErrorCodeUnsafeOverloadError] +from typing import overload, Union + +@overload +def unsafe_func(x: int) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types [overload-overlap] +@overload +def unsafe_func(x: object) -> str: ... +def unsafe_func(x: object) -> Union[int, str]: + if isinstance(x, int): + return 42 + else: + return "some string" +[builtins fixtures/isinstancelist.pyi] + + +### +# unimported-reveal +### + +[case testUnimportedRevealType] +# flags: --enable-error-code=unimported-reveal +x = 1 +reveal_type(x) +[out] +main:3: error: Name "reveal_type" is not defined [unimported-reveal] +main:3: note: Did you forget to import it from "typing_extensions"? (Suggestion: "from typing_extensions import reveal_type") +main:3: note: Revealed type is "builtins.int" +[builtins fixtures/isinstancelist.pyi] + +[case testUnimportedRevealTypePy311] +# flags: --enable-error-code=unimported-reveal --python-version=3.11 +x = 1 +reveal_type(x) +[out] +main:3: error: Name "reveal_type" is not defined [unimported-reveal] +main:3: note: Did you forget to import it from "typing"? (Suggestion: "from typing import reveal_type") +main:3: note: Revealed type is "builtins.int" +[builtins fixtures/isinstancelist.pyi] + +[case testUnimportedRevealTypeInUncheckedFunc] +# flags: --enable-error-code=unimported-reveal +def unchecked(): + x = 1 + reveal_type(x) +[out] +main:4: error: Name "reveal_type" is not defined [unimported-reveal] +main:4: note: Did you forget to import it from "typing_extensions"? (Suggestion: "from typing_extensions import reveal_type") +main:4: note: Revealed type is "Any" +main:4: note: 'reveal_type' always outputs 'Any' in unchecked functions +[builtins fixtures/isinstancelist.pyi] + +[case testUnimportedRevealTypeImportedTypingExtensions] +# flags: --enable-error-code=unimported-reveal +from typing_extensions import reveal_type +x = 1 +reveal_type(x) # N: Revealed type is "builtins.int" +[builtins fixtures/isinstancelist.pyi] + +[case testUnimportedRevealTypeImportedTyping311] +# flags: --enable-error-code=unimported-reveal --python-version=3.11 +from typing import reveal_type +x = 1 +reveal_type(x) # N: Revealed type is "builtins.int" +[builtins fixtures/isinstancelist.pyi] +[typing fixtures/typing-full.pyi] + +[case testUnimportedRevealLocals] +# flags: --enable-error-code=unimported-reveal +x = 1 +reveal_locals() +[out] +main:3: note: Revealed local types are: +main:3: note: x: builtins.int +main:3: error: Name "reveal_locals" is not defined [unimported-reveal] +[builtins fixtures/isinstancelist.pyi] + +[case testCovariantMutableOverride] +# flags: --enable-error-code=mutable-override +from typing import Any + +class C: + x: float + y: float + z: float + w: Any + @property + def foo(self) -> float: ... + @property + def bar(self) -> float: ... + @bar.setter + def bar(self, val: float) -> None: ... + baz: float + bad1: float + bad2: float +class D(C): + x: int # E: Covariant override of a mutable attribute (base class "C" defined the type as "float", expression has type "int") [mutable-override] + y: float + z: Any + w: float + foo: int + bar: int # E: Covariant override of a mutable attribute (base class "C" defined the type as "float", expression has type "int") [mutable-override] + def one(self) -> None: + self.baz = 5 + bad1 = 5 # E: Covariant override of a mutable attribute (base class "C" defined the type as "float", expression has type "int") [mutable-override] + def other(self) -> None: + self.bad2: int = 5 # E: Covariant override of a mutable attribute (base class "C" defined the type as "float", expression has type "int") [mutable-override] +[builtins fixtures/property.pyi] + +[case testNarrowedTypeNotSubtype] +from typing_extensions import TypeIs + +def f(x: str) -> TypeIs[int]: # E: Narrowed type "int" is not a subtype of input type "str" [narrowed-type-not-subtype] + pass + +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 49a3f0d4aaa7..04b3f7a131cc 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -13,11 +13,12 @@ [case testNoneAsRvalue] import typing -a = None # type: A +a: A class A: pass [out] [case testNoneAsArgument] +# flags: --no-strict-optional import typing def f(x: 'A', y: 'B') -> None: pass f(None, None) @@ -32,7 +33,7 @@ class B(A): pass [case testIntLiteral] a = 0 -b = None # type: A +b: A if int(): b = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "A") if int(): @@ -42,7 +43,7 @@ class A: [case testStrLiteral] a = '' -b = None # type: A +b: A if int(): b = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "A") if int(): @@ -56,7 +57,7 @@ class A: [case testFloatLiteral] a = 0.0 -b = None # type: A +b: A if str(): b = 1.1 # E: Incompatible types in assignment (expression has type "float", variable has type "A") if str(): @@ -67,7 +68,7 @@ class A: [case testComplexLiteral] a = 0.0j -b = None # type: A +b: A if str(): b = 1.1j # E: Incompatible types in assignment (expression has type "complex", variable has type "A") if str(): @@ -77,7 +78,8 @@ class A: [builtins fixtures/dict.pyi] [case testBytesLiteral] -b, a = None, None # type: (bytes, A) +b: bytes +a: A if str(): b = b'foo' if str(): @@ -90,10 +92,10 @@ class A: pass [builtins fixtures/dict.pyi] [case testUnicodeLiteralInPython3] -s = None # type: str +s: str if int(): s = u'foo' -b = None # type: bytes +b: bytes if int(): b = u'foo' # E: Incompatible types in assignment (expression has type "str", variable has type "bytes") [builtins fixtures/primitives.pyi] @@ -104,7 +106,9 @@ if int(): [case testAdd] -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C if int(): c = a + c # E: Unsupported operand types for + ("A" and "C") if int(): @@ -124,7 +128,9 @@ class C: [builtins fixtures/tuple.pyi] [case testSub] -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C if int(): c = a - c # E: Unsupported operand types for - ("A" and "C") if int(): @@ -144,7 +150,9 @@ class C: [builtins fixtures/tuple.pyi] [case testMul] -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C if int(): c = a * c # E: Unsupported operand types for * ("A" and "C") if int(): @@ -164,7 +172,9 @@ class C: [builtins fixtures/tuple.pyi] [case testMatMul] -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C if int(): c = a @ c # E: Unsupported operand types for @ ("A" and "C") if int(): @@ -184,7 +194,9 @@ class C: [builtins fixtures/tuple.pyi] [case testDiv] -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C if int(): c = a / c # E: Unsupported operand types for / ("A" and "C") a = a / b # E: Incompatible types in assignment (expression has type "C", variable has type "A") @@ -203,7 +215,9 @@ class C: [builtins fixtures/tuple.pyi] [case testIntDiv] -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C if int(): c = a // c # E: Unsupported operand types for // ("A" and "C") a = a // b # E: Incompatible types in assignment (expression has type "C", variable has type "A") @@ -222,7 +236,9 @@ class C: [builtins fixtures/tuple.pyi] [case testMod] -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C if int(): c = a % c # E: Unsupported operand types for % ("A" and "C") if int(): @@ -242,7 +258,9 @@ class C: [builtins fixtures/tuple.pyi] [case testPow] -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C if int(): c = a ** c # E: Unsupported operand types for ** ("A" and "C") if int(): @@ -262,8 +280,8 @@ class C: [builtins fixtures/tuple.pyi] [case testMiscBinaryOperators] - -a, b = None, None # type: (A, B) +a: A +b: B b = a & a # Fail b = a | b # Fail b = a ^ a # Fail @@ -291,7 +309,8 @@ main:6: error: Unsupported operand types for << ("A" and "B") main:7: error: Unsupported operand types for >> ("A" and "A") [case testBooleanAndOr] -a, b = None, None # type: (A, bool) +a: A +b: bool if int(): b = b and b if int(): @@ -310,8 +329,8 @@ class A: pass [case testRestrictedTypeAnd] -b = None # type: bool -i = None # type: str +b: bool +i: str j = not b and i if j: reveal_type(j) # N: Revealed type is "builtins.str" @@ -319,8 +338,8 @@ if j: [case testRestrictedTypeOr] -b = None # type: bool -i = None # type: str +b: bool +i: str j = b or i if not j: reveal_type(j) # N: Revealed type is "builtins.str" @@ -343,7 +362,9 @@ def f(a: List[str], b: bool) -> bool: [builtins fixtures/list.pyi] [case testNonBooleanOr] -c, d, b = None, None, None # type: (C, D, bool) +c: C +d: D +b: bool if int(): c = c or c if int(): @@ -362,7 +383,11 @@ class D(C): pass [case testInOperator] from typing import Iterator, Iterable, Any -a, b, c, d, e = None, None, None, None, None # type: (A, B, bool, D, Any) +a: A +b: B +c: bool +d: D +e: Any if int(): c = c in a # E: Unsupported operand types for in ("bool" and "A") if int(): @@ -389,7 +414,11 @@ class D(Iterable[A]): [case testNotInOperator] from typing import Iterator, Iterable, Any -a, b, c, d, e = None, None, None, None, None # type: (A, B, bool, D, Any) +a: A +b: B +c: bool +d: D +e: Any if int(): c = c not in a # E: Unsupported operand types for in ("bool" and "A") if int(): @@ -415,7 +444,9 @@ class D(Iterable[A]): [builtins fixtures/bool.pyi] [case testNonBooleanContainsReturnValue] -a, b, c = None, None, None # type: (A, bool, str) +a: A +b: bool +c: str if int(): b = a not in a if int(): @@ -434,8 +465,8 @@ a = 1 in ([1] + ['x']) # E: List item 0 has incompatible type "str"; expected " [builtins fixtures/list.pyi] [case testEq] - -a, b = None, None # type: (A, bool) +a: A +b: bool if int(): a = a == b # E: Incompatible types in assignment (expression has type "bool", variable has type "A") if int(): @@ -451,7 +482,9 @@ class A: [builtins fixtures/bool.pyi] [case testLtAndGt] -a, b, bo = None, None, None # type: (A, B, bool) +a: A +b: B +bo: bool if int(): a = a < b # E: Incompatible types in assignment (expression has type "bool", variable has type "A") if int(): @@ -470,7 +503,9 @@ class B: [builtins fixtures/bool.pyi] [case cmpIgnoredPy3] -a, b, bo = None, None, None # type: (A, B, bool) +a: A +b: B +bo: bool bo = a <= b # E: Unsupported left operand type for <= ("A") class A: @@ -480,7 +515,9 @@ class B: [builtins fixtures/bool.pyi] [case testLeAndGe] -a, b, bo = None, None, None # type: (A, B, bool) +a: A +b: B +bo: bool if int(): a = a <= b # E: Incompatible types in assignment (expression has type "bool", variable has type "A") if int(): @@ -499,8 +536,9 @@ class B: [builtins fixtures/bool.pyi] [case testChainedComp] - -a, b, bo = None, None, None # type: (A, B, bool) +a: A +b: B +bo: bool a < a < b < b # Fail a < b < b < b a < a > a < b # Fail @@ -513,13 +551,15 @@ class B: def __gt__(self, o: 'B') -> bool: pass [builtins fixtures/bool.pyi] [out] -main:3: error: Unsupported operand types for < ("A" and "A") -main:5: error: Unsupported operand types for < ("A" and "A") -main:5: error: Unsupported operand types for > ("A" and "A") +main:4: error: Unsupported operand types for < ("A" and "A") +main:6: error: Unsupported operand types for < ("A" and "A") +main:6: error: Unsupported operand types for > ("A" and "A") [case testChainedCompBoolRes] -a, b, bo = None, None, None # type: (A, B, bool) +a: A +b: B +bo: bool if int(): bo = a < b < b if int(): @@ -535,8 +575,12 @@ class B: [case testChainedCompResTyp] -x, y = None, None # type: (X, Y) -a, b, p, bo = None, None, None, None # type: (A, B, P, bool) +x: X +y: Y +a: A +b: B +p: P +bo: bool if int(): b = y == y == y if int(): @@ -566,7 +610,8 @@ class Y: [case testIs] -a, b = None, None # type: (A, bool) +a: A +b: bool if int(): a = a is b # E: Incompatible types in assignment (expression has type "bool", variable has type "A") if int(): @@ -579,7 +624,8 @@ class A: pass [builtins fixtures/bool.pyi] [case testIsNot] -a, b = None, None # type: (A, bool) +a: A +b: bool if int(): a = a is not b # E: Incompatible types in assignment (expression has type "bool", variable has type "A") if int(): @@ -604,8 +650,8 @@ class A: def __add__(self, x: int) -> int: pass class B: def __radd__(self, x: A) -> str: pass -s = None # type: str -n = None # type: int +s: str +n: int if int(): n = A() + 1 if int(): @@ -618,8 +664,8 @@ class A: def __add__(self, x: 'A') -> object: pass class B: def __radd__(self, x: A) -> str: pass -s = None # type: str -n = None # type: int +s: str +n: int if int(): s = A() + B() n = A() + B() # E: Incompatible types in assignment (expression has type "str", variable has type "int") @@ -632,7 +678,7 @@ class A: def __add__(self, x: N) -> int: pass class B: def __radd__(self, x: N) -> str: pass -s = None # type: str +s: str s = A() + B() # E: Unsupported operand types for + ("A" and "B") [case testBinaryOperatorWithAnyRightOperand] @@ -647,8 +693,8 @@ class A: def __lt__(self, x: C) -> int: pass # E: Signatures of "__lt__" of "A" and "__gt__" of "C" are unsafely overlapping class B: def __gt__(self, x: A) -> str: pass -s = None # type: str -n = None # type: int +s: str +n: int if int(): n = A() < C() s = A() < B() @@ -743,8 +789,8 @@ divmod('foo', d) # E: Unsupported operand types for divmod ("str" and "Decimal" [case testUnaryMinus] - -a, b = None, None # type: (A, B) +a: A +b: B if int(): a = -a # E: Incompatible types in assignment (expression has type "B", variable has type "A") if int(): @@ -760,7 +806,8 @@ class B: [builtins fixtures/tuple.pyi] [case testUnaryPlus] -a, b = None, None # type: (A, B) +a: A +b: B if int(): a = +a # E: Incompatible types in assignment (expression has type "B", variable has type "A") if int(): @@ -776,7 +823,8 @@ class B: [builtins fixtures/tuple.pyi] [case testUnaryNot] -a, b = None, None # type: (A, bool) +a: A +b: bool if int(): a = not b # E: Incompatible types in assignment (expression has type "bool", variable has type "A") if int(): @@ -788,7 +836,8 @@ class A: [builtins fixtures/bool.pyi] [case testUnaryBitwiseNeg] -a, b = None, None # type: (A, B) +a: A +b: B if int(): a = ~a # E: Incompatible types in assignment (expression has type "B", variable has type "A") if int(): @@ -809,8 +858,9 @@ class B: [case testIndexing] - -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C if int(): c = a[c] # E: Invalid index type "C" for "A"; expected type "B" if int(): @@ -828,8 +878,9 @@ class C: pass [builtins fixtures/tuple.pyi] [case testIndexingAsLvalue] - -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C a[c] = c # Fail a[b] = a # Fail b[a] = c # Fail @@ -844,16 +895,17 @@ class C: pass [builtins fixtures/tuple.pyi] [out] -main:3: error: Invalid index type "C" for "A"; expected type "B" -main:4: error: Incompatible types in assignment (expression has type "A", target has type "C") -main:5: error: Unsupported target for indexed assignment ("B") +main:4: error: Invalid index type "C" for "A"; expected type "B" +main:5: error: Incompatible types in assignment (expression has type "A", target has type "C") +main:6: error: Unsupported target for indexed assignment ("B") [case testOverloadedIndexing] from foo import * [file foo.pyi] from typing import overload - -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C a[b] a[c] a[1] # E: No overload variant of "__getitem__" of "A" matches argument type "int" \ @@ -861,7 +913,8 @@ a[1] # E: No overload variant of "__getitem__" of "A" matches argument type "in # N: def __getitem__(self, B, /) -> int \ # N: def __getitem__(self, C, /) -> str -i, s = None, None # type: (int, str) +i: int +s: str if int(): i = a[b] if int(): @@ -893,7 +946,9 @@ from typing import cast, Any class A: pass class B: pass class C(A): pass -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C if int(): a = cast(A, a()) # E: "A" not callable @@ -916,7 +971,8 @@ if int(): [case testAnyCast] from typing import cast, Any -a, b = None, None # type: (A, B) +a: A +b: B a = cast(Any, a()) # Fail a = cast(Any, b) b = cast(Any, a) @@ -924,7 +980,7 @@ class A: pass class B: pass [builtins fixtures/tuple.pyi] [out] -main:3: error: "A" not callable +main:4: error: "A" not callable -- assert_type() @@ -937,6 +993,8 @@ reveal_type(returned) # N: Revealed type is "builtins.int" assert_type(a, str) # E: Expression is of type "int", not "str" assert_type(a, Any) # E: Expression is of type "int", not "Any" assert_type(a, Literal[1]) # E: Expression is of type "int", not "Literal[1]" +assert_type(42, Literal[42]) +assert_type(42, int) # E: Expression is of type "Literal[42]", not "int" [builtins fixtures/tuple.pyi] [case testAssertTypeGeneric] @@ -987,6 +1045,23 @@ def reduce_it(s: Scalar) -> Scalar: assert_type(reduce_it(True), Scalar) [builtins fixtures/tuple.pyi] +[case testAssertTypeWithDeferredNodes] +from typing import Callable, TypeVar, assert_type + +T = TypeVar("T") + +def dec(f: Callable[[], T]) -> Callable[[], T]: + return f + +def func() -> None: + some = _inner_func() + assert_type(some, int) + +@dec +def _inner_func() -> int: + return 1 +[builtins fixtures/tuple.pyi] + -- None return type -- ---------------- @@ -1001,17 +1076,18 @@ class A: def __call__(self) -> None: pass -a, o = None, None # type: (A, object) +a: A +o: object if int(): - a = f() # E: "f" does not return a value + a = f() # E: "f" does not return a value (it only ever returns None) if int(): - o = a() # E: Function does not return a value + o = a() # E: Function does not return a value (it only ever returns None) if int(): - o = A().g(a) # E: "g" of "A" does not return a value + o = A().g(a) # E: "g" of "A" does not return a value (it only ever returns None) if int(): - o = A.g(a, a) # E: "g" of "A" does not return a value -A().g(f()) # E: "f" does not return a value -x: A = f() # E: "f" does not return a value + o = A.g(a, a) # E: "g" of "A" does not return a value (it only ever returns None) +A().g(f()) # E: "f" does not return a value (it only ever returns None) +x: A = f() # E: "f" does not return a value (it only ever returns None) f() A().g(a) [builtins fixtures/tuple.pyi] @@ -1020,15 +1096,15 @@ A().g(a) import typing def f() -> None: pass -if f(): # E: "f" does not return a value +if f(): # E: "f" does not return a value (it only ever returns None) pass -elif f(): # E: "f" does not return a value +elif f(): # E: "f" does not return a value (it only ever returns None) pass -while f(): # E: "f" does not return a value +while f(): # E: "f" does not return a value (it only ever returns None) pass def g() -> object: - return f() # E: "f" does not return a value -raise f() # E: "f" does not return a value + return f() # E: "f" does not return a value (it only ever returns None) +raise f() # E: "f" does not return a value (it only ever returns None) [builtins fixtures/exception.pyi] [case testNoneReturnTypeWithExpressions] @@ -1038,14 +1114,14 @@ def f() -> None: pass class A: def __add__(self, x: 'A') -> 'A': pass -a = None # type: A -[f()] # E: "f" does not return a value -f() + a # E: "f" does not return a value -a + f() # E: "f" does not return a value -f() == a # E: "f" does not return a value -a != f() # E: "f" does not return a value +a: A +[f()] # E: "f" does not return a value (it only ever returns None) +f() + a # E: "f" does not return a value (it only ever returns None) +a + f() # E: "f" does not return a value (it only ever returns None) +f() == a # E: "f" does not return a value (it only ever returns None) +a != f() # E: "f" does not return a value (it only ever returns None) cast(A, f()) -f().foo # E: "f" does not return a value +f().foo # E: "f" does not return a value (it only ever returns None) [builtins fixtures/list.pyi] [case testNoneReturnTypeWithExpressions2] @@ -1056,15 +1132,16 @@ class A: def __add__(self, x: 'A') -> 'A': pass -a, b = None, None # type: (A, bool) -f() in a # E: "f" does not return a value # E: Unsupported right operand type for in ("A") -a < f() # E: "f" does not return a value -f() <= a # E: "f" does not return a value -a in f() # E: "f" does not return a value --f() # E: "f" does not return a value -not f() # E: "f" does not return a value -f() and b # E: "f" does not return a value -b or f() # E: "f" does not return a value +a: A +b: bool +f() in a # E: "f" does not return a value (it only ever returns None) # E: Unsupported right operand type for in ("A") +a < f() # E: "f" does not return a value (it only ever returns None) +f() <= a # E: "f" does not return a value (it only ever returns None) +a in f() # E: "f" does not return a value (it only ever returns None) +-f() # E: "f" does not return a value (it only ever returns None) +not f() # E: "f" does not return a value (it only ever returns None) +f() and b # E: "f" does not return a value (it only ever returns None) +b or f() # E: "f" does not return a value (it only ever returns None) [builtins fixtures/bool.pyi] @@ -1073,7 +1150,8 @@ b or f() # E: "f" does not return a value [case testGetSlice] -a, b = None, None # type: (A, B) +a: A +b: B if int(): a = a[1:2] # E: Incompatible types in assignment (expression has type "B", variable has type "A") if int(): @@ -1099,7 +1177,7 @@ class B: pass [case testSlicingWithInvalidBase] -a = None # type: A +a: A a[1:2] # E: Invalid index type "slice" for "A"; expected type "int" a[:] # E: Invalid index type "slice" for "A"; expected type "int" class A: @@ -1108,23 +1186,40 @@ class A: [case testSlicingWithNonindexable] -o = None # type: object +o: object o[1:2] # E: Value of type "object" is not indexable o[:] # E: Value of type "object" is not indexable [builtins fixtures/slice.pyi] [case testNonIntSliceBounds] from typing import Any -a, o = None, None # type: (Any, object) -a[o:1] # E: Slice index must be an integer or None -a[1:o] # E: Slice index must be an integer or None -a[o:] # E: Slice index must be an integer or None -a[:o] # E: Slice index must be an integer or None +a: Any +o: object +a[o:1] # E: Slice index must be an integer, SupportsIndex or None +a[1:o] # E: Slice index must be an integer, SupportsIndex or None +a[o:] # E: Slice index must be an integer, SupportsIndex or None +a[:o] # E: Slice index must be an integer, SupportsIndex or None +[builtins fixtures/slice.pyi] + +[case testSliceSupportsIndex] +import typing_extensions +class Index: + def __init__(self, value: int) -> None: + self.value = value + def __index__(self) -> int: + return self.value + +c = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +reveal_type(c[Index(0):Index(5)]) # N: Revealed type is "builtins.list[builtins.int]" +[file typing_extensions.pyi] +from typing import Protocol +class SupportsIndex(Protocol): + def __index__(self) -> int: ... [builtins fixtures/slice.pyi] [case testNoneSliceBounds] from typing import Any -a = None # type: Any +a: Any a[None:1] a[1:None] a[None:] @@ -1132,9 +1227,8 @@ a[:None] [builtins fixtures/slice.pyi] [case testNoneSliceBoundsWithStrictOptional] -# flags: --strict-optional from typing import Any -a = None # type: Any +a: Any a[None:1] a[1:None] a[None:] @@ -1163,6 +1257,7 @@ def void() -> None: x = lambda: void() # type: typing.Callable[[], None] [case testNoCrashOnLambdaGenerator] +# flags: --no-strict-optional from typing import Iterator, Callable # These should not crash @@ -1192,7 +1287,7 @@ def f() -> None: [case testSimpleListComprehension] from typing import List -a = None # type: List[A] +a: List[A] a = [x for x in a] b = [x for x in a] # type: List[B] # E: List comprehension has incompatible type List[A]; expected List[B] class A: pass @@ -1201,7 +1296,7 @@ class B: pass [case testSimpleListComprehensionNestedTuples] from typing import List, Tuple -l = None # type: List[Tuple[A, Tuple[A, B]]] +l: List[Tuple[A, Tuple[A, B]]] a = [a2 for a1, (a2, b1) in l] # type: List[A] b = [a2 for a1, (a2, b1) in l] # type: List[B] # E: List comprehension has incompatible type List[A]; expected List[B] class A: pass @@ -1210,7 +1305,7 @@ class B: pass [case testSimpleListComprehensionNestedTuples2] from typing import List, Tuple -l = None # type: List[Tuple[int, Tuple[int, str]]] +l: List[Tuple[int, Tuple[int, str]]] a = [f(d) for d, (i, s) in l] b = [f(s) for d, (i, s) in l] # E: Argument 1 to "f" has incompatible type "str"; expected "int" @@ -1233,14 +1328,14 @@ def f(a: A) -> B: pass [case testErrorInListComprehensionCondition] from typing import List -a = None # type: List[A] +a: List[A] a = [x for x in a if x()] # E: "A" not callable class A: pass [builtins fixtures/for.pyi] [case testTypeInferenceOfListComprehension] from typing import List -a = None # type: List[A] +a: List[A] o = [x for x in a] # type: List[object] class A: pass [builtins fixtures/for.pyi] @@ -1248,7 +1343,7 @@ class A: pass [case testSimpleListComprehensionInClassBody] from typing import List class A: - a = None # type: List[A] + a: List[A] a = [x for x in a] b = [x for x in a] # type: List[B] # E: List comprehension has incompatible type List[A]; expected List[B] class B: pass @@ -1262,7 +1357,7 @@ class B: pass [case testSimpleSetComprehension] from typing import Set -a = None # type: Set[A] +a: Set[A] a = {x for x in a} b = {x for x in a} # type: Set[B] # E: Set comprehension has incompatible type Set[A]; expected Set[B] class A: pass @@ -1276,8 +1371,8 @@ class B: pass [case testSimpleDictionaryComprehension] from typing import Dict, List, Tuple -abd = None # type: Dict[A, B] -abl = None # type: List[Tuple[A, B]] +abd: Dict[A, B] +abl: List[Tuple[A, B]] abd = {a: b for a, b in abl} x = {a: b for a, b in abl} # type: Dict[B, A] y = {a: b for a, b in abl} # type: A @@ -1293,7 +1388,7 @@ main:6: error: Incompatible types in assignment (expression has type "Dict[A, B] [case testDictionaryComprehensionWithNonDirectMapping] from typing import Dict, List, Tuple abd: Dict[A, B] -abl = None # type: List[Tuple[A, B]] +abl: List[Tuple[A, B]] abd = {a: f(b) for a, b in abl} class A: pass class B: pass @@ -1313,10 +1408,10 @@ main:4: error: Argument 1 to "f" has incompatible type "B"; expected "A" from typing import Iterator # The implementation is mostly identical to list comprehensions, so only a few # test cases is ok. -a = None # type: Iterator[int] +a: Iterator[int] if int(): a = (x for x in a) -b = None # type: Iterator[str] +b: Iterator[str] if int(): b = (x for x in a) # E: Generator has incompatible item type "int"; expected "str" [builtins fixtures/for.pyi] @@ -1325,7 +1420,7 @@ if int(): from typing import Callable, Iterator, List a = [] # type: List[Callable[[], str]] -b = None # type: Iterator[Callable[[], int]] +b: Iterator[Callable[[], int]] if int(): b = (x for x in a) # E: Generator has incompatible item type "Callable[[], str]"; expected "Callable[[], int]" [builtins fixtures/list.pyi] @@ -1346,7 +1441,7 @@ if int(): [case testConditionalExpressionWithEmptyCondition] import typing def f() -> None: pass -x = 1 if f() else 2 # E: "f" does not return a value +x = 1 if f() else 2 # E: "f" does not return a value (it only ever returns None) [case testConditionalExpressionWithSubtyping] import typing @@ -1409,7 +1504,7 @@ from typing import List, Union x = [] y = "" x.append(y) if bool() else x.append(y) -z = x.append(y) if bool() else x.append(y) # E: "append" of "list" does not return a value +z = x.append(y) if bool() else x.append(y) # E: "append" of "list" does not return a value (it only ever returns None) [builtins fixtures/list.pyi] -- Special cases @@ -1422,14 +1517,14 @@ class A: def __add__(self, a: 'A') -> 'A': pass def f() -> None: pass -a = None # type: A +a: A None + a # E: Unsupported left operand type for + ("None") f + a # E: Unsupported left operand type for + ("Callable[[], None]") a + f # E: Unsupported operand types for + ("A" and "Callable[[], None]") cast(A, f) [case testOperatorMethodWithInvalidArgCount] -a = None # type: A +a: A a + a # Fail class A: @@ -1443,7 +1538,7 @@ from typing import Any class A: def __init__(self, _add: Any) -> None: self.__add__ = _add -a = None # type: A +a: A a + a [out] @@ -1452,15 +1547,16 @@ a + a class A: def f(self, x: int) -> str: pass __add__ = f -s = None # type: str +s: str s = A() + 1 A() + (A() + 1) [out] main:7: error: Argument 1 has incompatible type "str"; expected "int" [case testIndexedLvalueWithSubtypes] - -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C a[c] = c a[b] = c a[c] = b @@ -1482,7 +1578,7 @@ class C(B): [case testEllipsis] -a = None # type: A +a: A if str(): a = ... # E: Incompatible types in assignment (expression has type "ellipsis", variable has type "A") b = ... @@ -1493,8 +1589,7 @@ if str(): ....a # E: "ellipsis" has no attribute "a" class A: pass -[builtins fixtures/dict.pyi] -[out] +[builtins fixtures/dict-full.pyi] -- Yield expression @@ -1508,7 +1603,7 @@ def f(x: int) -> None: [builtins fixtures/for.pyi] [out] main:1: error: The return type of a generator function should be "Generator" or one of its supertypes -main:2: error: "f" does not return a value +main:2: error: "f" does not return a value (it only ever returns None) main:2: error: Argument 1 to "f" has incompatible type "str"; expected "int" [case testYieldExpressionWithNone] @@ -1528,7 +1623,7 @@ from typing import Iterator def f() -> Iterator[int]: yield 5 def g() -> Iterator[int]: - a = yield from f() # E: Function does not return a value + a = yield from f() # E: Function does not return a value (it only ever returns None) [case testYieldFromGeneratorHasValue] from typing import Iterator, Generator @@ -1543,12 +1638,12 @@ def g() -> Iterator[int]: [case testYieldFromTupleExpression] from typing import Generator def g() -> Generator[int, None, None]: - x = yield from () # E: Function does not return a value - x = yield from (0, 1, 2) # E: Function does not return a value + x = yield from () # E: Function does not return a value (it only ever returns None) + x = yield from (0, 1, 2) # E: Function does not return a value (it only ever returns None) x = yield from (0, "ERROR") # E: Incompatible types in "yield from" (actual type "object", expected type "int") \ - # E: Function does not return a value + # E: Function does not return a value (it only ever returns None) x = yield from ("ERROR",) # E: Incompatible types in "yield from" (actual type "str", expected type "int") \ - # E: Function does not return a value + # E: Function does not return a value (it only ever returns None) [builtins fixtures/tuple.pyi] -- dict(...) @@ -1769,13 +1864,35 @@ b = {'z': 26, *a} # E: invalid syntax [case testDictWithStarStarExpr] -from typing import Dict +from typing import Dict, Iterable + +class Thing: + def keys(self) -> Iterable[str]: + ... + def __getitem__(self, key: str) -> int: + ... + a = {'a': 1} b = {'z': 26, **a} c = {**b} d = {**a, **b, 'c': 3} -e = {1: 'a', **a} # E: Argument 1 to "update" of "dict" has incompatible type "Dict[str, int]"; expected "Mapping[int, str]" -f = {**b} # type: Dict[int, int] # E: List item 0 has incompatible type "Dict[str, int]"; expected "Mapping[int, int]" +e = {1: 'a', **a} # E: Cannot infer type argument 1 of \ + # N: Try assigning the literal to a variable annotated as dict[, ] +f = {**b} # type: Dict[int, int] # E: Unpacked dict entry 0 has incompatible type "Dict[str, int]"; expected "SupportsKeysAndGetItem[int, int]" +g = {**Thing()} +h = {**a, **Thing()} +i = {**Thing()} # type: Dict[int, int] # E: Unpacked dict entry 0 has incompatible type "Thing"; expected "SupportsKeysAndGetItem[int, int]" \ + # N: Following member(s) of "Thing" have conflicts: \ + # N: Expected: \ + # N: def __getitem__(self, int, /) -> int \ + # N: Got: \ + # N: def __getitem__(self, str, /) -> int \ + # N: Expected: \ + # N: def keys(self) -> Iterable[int] \ + # N: Got: \ + # N: def keys(self) -> Iterable[str] +j = {1: 'a', **Thing()} # E: Cannot infer type argument 1 of \ + # N: Try assigning the literal to a variable annotated as dict[, ] [builtins fixtures/dict.pyi] [typing fixtures/typing-medium.pyi] @@ -1947,7 +2064,7 @@ x is 42 [typing fixtures/typing-full.pyi] [case testStrictEqualityStrictOptional] -# flags: --strict-equality --strict-optional +# flags: --strict-equality x: str if x is not None: # OK even with strict-optional @@ -1963,7 +2080,7 @@ if x is not None: # OK without strict-optional [builtins fixtures/bool.pyi] [case testStrictEqualityEqNoOptionalOverlap] -# flags: --strict-equality --strict-optional +# flags: --strict-equality from typing import Optional x: Optional[str] @@ -2247,6 +2364,51 @@ b"x" in data [builtins fixtures/primitives.pyi] [typing fixtures/typing-full.pyi] +[case testStrictEqualityWithDifferentMapTypes] +# flags: --strict-equality +from typing import Mapping + +class A(Mapping[int, str]): ... +class B(Mapping[int, str]): ... + +a: A +b: B +assert a == b +[builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi] + +[case testStrictEqualityWithRecursiveMapTypes] +# flags: --strict-equality +from typing import Dict + +R = Dict[str, R] + +a: R +b: R +assert a == b + +R2 = Dict[int, R2] +c: R2 +assert a == c # E: Non-overlapping equality check (left operand type: "Dict[str, R]", right operand type: "Dict[int, R2]") +[builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi] + +[case testStrictEqualityWithRecursiveListTypes] +# flags: --strict-equality +from typing import List, Union + +R = List[Union[str, R]] + +a: R +b: R +assert a == b + +R2 = List[Union[int, R2]] +c: R2 +assert a == c +[builtins fixtures/list.pyi] +[typing fixtures/typing-full.pyi] + [case testUnimportedHintAny] def f(x: Any) -> None: # E: Name "Any" is not defined \ # N: Did you forget to import it from "typing"? (Suggestion: "from typing import Any") diff --git a/test-data/unit/check-fastparse.test b/test-data/unit/check-fastparse.test index f172a9727d49..534967b1edbf 100644 --- a/test-data/unit/check-fastparse.test +++ b/test-data/unit/check-fastparse.test @@ -4,7 +4,7 @@ [case testFastParseTypeCommentSyntaxError] -x = None # type: a : b # E: syntax error in type comment "a : b" +x = None # type: a : b # E: Syntax error in type comment "a : b" [case testFastParseInvalidTypeComment] @@ -14,13 +14,13 @@ x = None # type: a + b # E: Invalid type comment or annotation -- This happens in both parsers. [case testFastParseFunctionAnnotationSyntaxError] -def f(): # E: syntax error in type comment "None -> None" # N: Suggestion: wrap argument types in parentheses +def f(): # E: Syntax error in type comment "None -> None" # N: Suggestion: wrap argument types in parentheses # type: None -> None pass [case testFastParseFunctionAnnotationSyntaxErrorSpaces] -def f(): # E: syntax error in type comment "None -> None" # N: Suggestion: wrap argument types in parentheses +def f(): # E: Syntax error in type comment "None -> None" # N: Suggestion: wrap argument types in parentheses # type: None -> None pass @@ -31,7 +31,6 @@ def f(x): # E: Invalid type comment or annotation pass [case testFastParseInvalidTypes3] -# flags: --python-version 3.6 # All of these should not crash from typing import Callable, Tuple, Iterable @@ -228,8 +227,8 @@ def g(): # E: Type signature has too many arguments assert 1, 2 assert (1, 2) # E: Assertion is always true, perhaps remove parentheses? assert (1, 2), 3 # E: Assertion is always true, perhaps remove parentheses? -assert () assert (1,) # E: Assertion is always true, perhaps remove parentheses? +assert () [builtins fixtures/tuple.pyi] [case testFastParseAssertMessage] diff --git a/test-data/unit/check-final.test b/test-data/unit/check-final.test index da034caced76..26a0d0782503 100644 --- a/test-data/unit/check-final.test +++ b/test-data/unit/check-final.test @@ -6,11 +6,13 @@ [case testFinalDefiningModuleVar] from typing import Final +w: 'Final' = int() x: Final = int() y: Final[float] = int() z: Final[int] = int() bad: Final[str] = int() # E: Incompatible types in assignment (expression has type "int", variable has type "str") +reveal_type(w) # N: Revealed type is "builtins.int" reveal_type(x) # N: Revealed type is "builtins.int" reveal_type(y) # N: Revealed type is "builtins.float" reveal_type(z) # N: Revealed type is "builtins.int" @@ -1117,3 +1119,60 @@ from typing import Final class MyClass: a: None a: Final[int] = 1 # E: Cannot redefine an existing name as final # E: Name "a" already defined on line 5 + +[case testFinalOverrideAllowedForPrivate] +from typing import Final, final + +class Parent: + __foo: Final[int] = 0 + @final + def __bar(self) -> None: ... + +class Child(Parent): + __foo: Final[int] = 1 + @final + def __bar(self) -> None: ... + +[case testFinalWithoutBool] +from typing_extensions import final, Literal + +class A: + pass + +@final +class B: + pass + +@final +class C: + def __len__(self) -> Literal[1]: return 1 + +reveal_type(A() and 42) # N: Revealed type is "Union[__main__.A, Literal[42]?]" +reveal_type(B() and 42) # N: Revealed type is "Literal[42]?" +reveal_type(C() and 42) # N: Revealed type is "Literal[42]?" + +[builtins fixtures/bool.pyi] + +[case testFinalWithoutBoolButWithLen] +from typing_extensions import final, Literal + +# Per Python data model, __len__ is called if __bool__ does not exist. +# In a @final class, __bool__ would not exist. + +@final +class A: + def __len__(self) -> int: ... + +@final +class B: + def __len__(self) -> Literal[1]: return 1 + +@final +class C: + def __len__(self) -> Literal[0]: return 0 + +reveal_type(A() and 42) # N: Revealed type is "Union[__main__.A, Literal[42]?]" +reveal_type(B() and 42) # N: Revealed type is "Literal[42]?" +reveal_type(C() and 42) # N: Revealed type is "__main__.C" + +[builtins fixtures/bool.pyi] diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 0ac39ebf9c10..c90c773e320f 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -427,7 +427,7 @@ async def h() -> NoReturn: # E: Implicit return in function which does not retu [typing fixtures/typing-async.pyi] [case testNoWarnNoReturn] -# flags: --no-warn-no-return --strict-optional +# flags: --no-warn-no-return import typing def implicit_optional_return(arg) -> typing.Optional[str]: @@ -1305,34 +1305,34 @@ main:3: error: Function is missing a type annotation for one or more arguments [case testDisallowIncompleteDefsAttrsNoAnnotations] # flags: --disallow-incomplete-defs -import attr +import attrs -@attr.s() +@attrs.define class Unannotated: - foo = attr.ib() + foo = attrs.field() -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testDisallowIncompleteDefsAttrsWithAnnotations] # flags: --disallow-incomplete-defs -import attr +import attrs -@attr.s() +@attrs.define class Annotated: - bar: int = attr.ib() + bar: int = attrs.field() -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testDisallowIncompleteDefsAttrsPartialAnnotations] # flags: --disallow-incomplete-defs -import attr +import attrs -@attr.s() +@attrs.define class PartiallyAnnotated: # E: Function is missing a type annotation for one or more arguments - bar: int = attr.ib() - baz = attr.ib() + bar: int = attrs.field() + baz = attrs.field() -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAlwaysTrueAlwaysFalseFlags] # flags: --always-true=YOLO --always-true=YOLO1 --always-false=BLAH1 --always-false BLAH --ignore-missing-imports @@ -1611,14 +1611,22 @@ from other_module_2 import a # E: Module "other_module_2" does not explicitly e reveal_type(a) # N: Revealed type is "builtins.int" import other_module_2 -# TODO: this should also reveal builtins.int, see #13965 -reveal_type(other_module_2.a) # E: "object" does not explicitly export attribute "a" [attr-defined] \ - # N: Revealed type is "Any" +reveal_type(other_module_2.a) # E: Module "other_module_2" does not explicitly export attribute "a" [attr-defined] \ + # N: Revealed type is "builtins.int" + +from other_module_2 import b # E: Module "other_module_2" does not explicitly export attribute "b" [attr-defined] +reveal_type(b) # N: Revealed type is "def (a: builtins.int) -> builtins.str" + +import other_module_2 +reveal_type(other_module_2.b) # E: Module "other_module_2" does not explicitly export attribute "b" [attr-defined] \ + # N: Revealed type is "def (a: builtins.int) -> builtins.str" [file other_module_1.py] a = 5 +def b(a: int) -> str: ... [file other_module_2.py] -from other_module_1 import a +from other_module_1 import a, b +[builtins fixtures/module.pyi] [case testNoImplicitReexportRespectsAll] # flags: --no-implicit-reexport @@ -1647,13 +1655,17 @@ __all__ = ('b',) [builtins fixtures/tuple.pyi] [case testNoImplicitReexportGetAttr] -# flags: --no-implicit-reexport --python-version 3.7 +# flags: --no-implicit-reexport from other_module_2 import a # E: Module "other_module_2" does not explicitly export attribute "a" +reveal_type(a) # N: Revealed type is "builtins.int" +from other_module_2 import b # E: Module "other_module_2" does not explicitly export attribute "b" +reveal_type(b) # N: Revealed type is "builtins.str" [file other_module_1.py] -from typing import Any -def __getattr__(name: str) -> Any: ... +b: str = "asdf" +def __getattr__(name: str) -> int: ... [file other_module_2.py] -from other_module_1 import a +from other_module_1 import a, b +def __getattr__(name: str) -> bytes: ... [builtins fixtures/tuple.pyi] [case textNoImplicitReexportSuggestions] @@ -1733,7 +1745,7 @@ def h() -> List[Any]: # E: Explicit "Any" is not allowed [builtins fixtures/list.pyi] [case testDisallowAnyExplicitVarDeclaration] -# flags: --python-version 3.6 --disallow-any-explicit +# flags: --disallow-any-explicit from typing import Any v: Any = '' # E: Explicit "Any" is not allowed w = '' # type: Any # E: Explicit "Any" is not allowed @@ -1741,7 +1753,7 @@ class X: y = '' # type: Any # E: Explicit "Any" is not allowed [case testDisallowAnyExplicitGenericVarDeclaration] -# flags: --python-version 3.6 --disallow-any-explicit +# flags: --disallow-any-explicit from typing import Any, List v: List[Any] = [] # E: Explicit "Any" is not allowed [builtins fixtures/list.pyi] @@ -1836,7 +1848,7 @@ N = TypedDict('N', {'x': str, 'y': List}) # no error [builtins fixtures/dict.pyi] [case testDisallowAnyGenericsTupleNoTypeParams] -# flags: --python-version 3.6 --disallow-any-generics +# flags: --disallow-any-generics from typing import Tuple def f(s: Tuple) -> None: pass # E: Missing type parameters for generic type "Tuple" @@ -1877,7 +1889,7 @@ def g(l: L[str]) -> None: pass # no error [builtins fixtures/list.pyi] [case testDisallowAnyGenericsGenericAlias] -# flags: --python-version 3.6 --disallow-any-generics +# flags: --disallow-any-generics from typing import TypeVar, Tuple T = TypeVar('T') @@ -1892,7 +1904,7 @@ x: A = ('a', 'b', 1) # E: Missing type parameters for generic type "A" [builtins fixtures/tuple.pyi] [case testDisallowAnyGenericsPlainList] -# flags: --python-version 3.6 --disallow-any-generics +# flags: --disallow-any-generics from typing import List def f(l: List) -> None: pass # E: Missing type parameters for generic type "List" @@ -1905,7 +1917,7 @@ y: List = [] # E: Missing type parameters for generic type "List" [builtins fixtures/list.pyi] [case testDisallowAnyGenericsCustomGenericClass] -# flags: --python-version 3.6 --disallow-any-generics +# flags: --disallow-any-generics from typing import Generic, TypeVar, Any T = TypeVar('T') @@ -2077,6 +2089,61 @@ y = 1 f(reveal_type(y)) # E: Call to untyped function "f" in typed context \ # N: Revealed type is "builtins.int" +[case testDisallowUntypedCallsAllowListFlags] +# flags: --disallow-untyped-calls --untyped-calls-exclude=foo --untyped-calls-exclude=bar.A +from foo import test_foo +from bar import A, B +from baz import test_baz +from foobar import bad + +test_foo(42) # OK +test_baz(42) # E: Call to untyped function "test_baz" in typed context +bad(42) # E: Call to untyped function "bad" in typed context + +a: A +b: B +a.meth() # OK +b.meth() # E: Call to untyped function "meth" in typed context +[file foo.py] +def test_foo(x): pass +[file foobar.py] +def bad(x): pass +[file bar.py] +class A: + def meth(self): pass +class B: + def meth(self): pass +[file baz.py] +def test_baz(x): pass + +[case testDisallowUntypedCallsAllowListConfig] +# flags: --config-file tmp/mypy.ini +from foo import test_foo +from bar import A, B +from baz import test_baz + +test_foo(42) # OK +test_baz(42) # E: Call to untyped function "test_baz" in typed context + +a: A +b: B +a.meth() # OK +b.meth() # E: Call to untyped function "meth" in typed context +[file foo.py] +def test_foo(x): pass +[file bar.py] +class A: + def meth(self): pass +class B: + def meth(self): pass +[file baz.py] +def test_baz(x): pass + +[file mypy.ini] +\[mypy] +disallow_untyped_calls = True +untyped_calls_exclude = foo, bar.A + [case testPerModuleErrorCodes] # flags: --config-file tmp/mypy.ini import tests.foo @@ -2101,12 +2168,12 @@ import tests.foo import bar [file bar.py] def foo() -> int: ... -if foo: ... # E: Function "Callable[[], int]" could always be true in boolean context +if foo: ... # E: Function "foo" could always be true in boolean context 42 + "no" # type: ignore # E: "type: ignore" comment without error code (consider "type: ignore[operator]" instead) [file tests/__init__.py] [file tests/foo.py] def foo() -> int: ... -if foo: ... # E: Function "Callable[[], int]" could always be true in boolean context +if foo: ... # E: Function "foo" could always be true in boolean context 42 + "no" # type: ignore [file mypy.ini] \[mypy] @@ -2123,18 +2190,6 @@ x: int = "" # E: Incompatible types in assignment (expression has type "str", v # flags: --hide-error-codes x: int = "" # E: Incompatible types in assignment (expression has type "str", variable has type "int") -[case testTypeVarTupleDisabled_no_incomplete] -from typing_extensions import TypeVarTuple -Ts = TypeVarTuple("Ts") # E: "TypeVarTuple" support is experimental, use --enable-incomplete-feature=TypeVarTuple to enable -[builtins fixtures/tuple.pyi] - -[case testTypeVarTupleEnabled_no_incomplete] -# flags: --enable-incomplete-feature=TypeVarTuple -from typing_extensions import TypeVarTuple -Ts = TypeVarTuple("Ts") # OK -[builtins fixtures/tuple.pyi] - - [case testDisableBytearrayPromotion] # flags: --disable-bytearray-promotion def f(x: bytes) -> None: ... @@ -2174,3 +2229,58 @@ def f(x: bytes, y: bytearray, z: memoryview) -> None: x in y x in z [builtins fixtures/primitives.pyi] + +[case testNoCrashFollowImportsForStubs] +# flags: --config-file tmp/mypy.ini +{**{"x": "y"}} + +[file mypy.ini] +\[mypy] +follow_imports = skip +follow_imports_for_stubs = true +[builtins fixtures/dict.pyi] + +[case testReturnAnyLambda] +# flags: --warn-return-any +from typing import Any, Callable + +def cb(f: Callable[[int], int]) -> None: ... +a: Any +cb(lambda x: a) # OK + +fn = lambda x: a +cb(fn) + +[case testShowErrorCodeLinks] +# flags: --show-error-codes --show-error-code-links + +x: int = "" # E: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment] +list(1) # E: No overload variant of "list" matches argument type "int" [call-overload] \ + # N: Possible overload variants: \ + # N: def [T] __init__(self) -> List[T] \ + # N: def [T] __init__(self, x: Iterable[T]) -> List[T] \ + # N: See https://mypy.rtfd.io/en/stable/_refs.html#code-call-overload for more info +list(2) # E: No overload variant of "list" matches argument type "int" [call-overload] \ + # N: Possible overload variants: \ + # N: def [T] __init__(self) -> List[T] \ + # N: def [T] __init__(self, x: Iterable[T]) -> List[T] +[builtins fixtures/list.pyi] + +[case testNestedGenericInAliasDisallow] +# flags: --disallow-any-generics +from typing import TypeVar, Generic, List, Union + +class C(Generic[T]): ... + +A = Union[C, List] # E: Missing type parameters for generic type "C" \ + # E: Missing type parameters for generic type "List" +[builtins fixtures/list.pyi] + +[case testNestedGenericInAliasAllow] +# flags: --allow-any-generics +from typing import TypeVar, Generic, List, Union + +class C(Generic[T]): ... + +A = Union[C, List] # OK +[builtins fixtures/list.pyi] diff --git a/test-data/unit/check-formatting.test b/test-data/unit/check-formatting.test index 5c0d0ed65292..83ae9b526f22 100644 --- a/test-data/unit/check-formatting.test +++ b/test-data/unit/check-formatting.test @@ -4,7 +4,10 @@ [case testStringInterpolationType] from typing import Tuple -i, f, s, t = None, None, None, None # type: (int, float, str, Tuple[int]) +i: int +f: float +s: str +t: Tuple[int] '%d' % i '%f' % f '%s' % s @@ -21,7 +24,9 @@ i, f, s, t = None, None, None, None # type: (int, float, str, Tuple[int]) [case testStringInterpolationSAcceptsAnyType] from typing import Any -i, o, s = None, None, None # type: (int, object, str) +i: int +o: object +s: str '%s %s %s' % (i, o, s) [builtins fixtures/primitives.pyi] @@ -30,8 +35,8 @@ xb: bytes xs: str '%s' % xs # OK -'%s' % xb # E: On Python 3 formatting "b'abc'" with "%s" produces "b'abc'", not "abc"; use "%r" if this is desired behavior -'%(name)s' % {'name': b'value'} # E: On Python 3 formatting "b'abc'" with "%s" produces "b'abc'", not "abc"; use "%r" if this is desired behavior +'%s' % xb # E: If x = b'abc' then "%s" % x produces "b'abc'", not "abc". If this is desired behavior use "%r" % x. Otherwise, decode the bytes +'%(name)s' % {'name': b'value'} # E: If x = b'abc' then "%s" % x produces "b'abc'", not "abc". If this is desired behavior use "%r" % x. Otherwise, decode the bytes [builtins fixtures/primitives.pyi] [case testStringInterpolationCount] @@ -98,7 +103,6 @@ a = None # type: Any [typing fixtures/typing-medium.pyi] [case testStringInterpolationC] -# flags: --python-version 3.6 '%c' % 1 '%c' % 1.0 # E: "%c" requires int or char (expression has type "float") '%c' % 's' @@ -125,14 +129,31 @@ b'%(x)s' % {b'x': b'data'} [typing fixtures/typing-medium.pyi] [case testStringInterpolationMappingDictTypes] -from typing import Any, Dict -a = None # type: Any -ds, do, di = None, None, None # type: Dict[str, int], Dict[object, int], Dict[int, int] -'%(a)' % 1 # E: Format requires a mapping (expression has type "int", expected type for mapping is "Mapping[str, Any]") +from typing import Any, Dict, Iterable + +class StringThing: + def keys(self) -> Iterable[str]: + ... + def __getitem__(self, __key: str) -> str: + ... + +class BytesThing: + def keys(self) -> Iterable[bytes]: + ... + def __getitem__(self, __key: bytes) -> str: + ... + +a: Any +ds: Dict[str, int] +do: Dict[object, int] +di: Dict[int, int] +'%(a)' % 1 # E: Format requires a mapping (expression has type "int", expected type for mapping is "SupportsKeysAndGetItem[str, Any]") '%()d' % a '%()d' % ds -'%()d' % do # E: Format requires a mapping (expression has type "Dict[object, int]", expected type for mapping is "Mapping[str, Any]") -b'%()d' % ds # E: Format requires a mapping (expression has type "Dict[str, int]", expected type for mapping is "Mapping[bytes, Any]") +'%()d' % do # E: Format requires a mapping (expression has type "Dict[object, int]", expected type for mapping is "SupportsKeysAndGetItem[str, Any]") +b'%()d' % ds # E: Format requires a mapping (expression has type "Dict[str, int]", expected type for mapping is "SupportsKeysAndGetItem[bytes, Any]") +'%()s' % StringThing() +b'%()s' % BytesThing() [builtins fixtures/primitives.pyi] [case testStringInterpolationMappingInvalidSpecifiers] @@ -210,18 +231,12 @@ t5: Iterable[str] = ('A', 'B') -- Bytes interpolation -- -------------------- - -[case testBytesInterpolationBefore35] -# flags: --python-version 3.4 -b'%b' % 1 # E: Unsupported left operand type for % ("bytes") - [case testBytesInterpolation] b'%b' % 1 # E: Incompatible types in string interpolation (expression has type "int", placeholder has type "bytes") b'%b' % b'1' b'%a' % 3 [case testBytesInterpolationC] -# flags: --python-version 3.6 b'%c' % 1 b'%c' % 1.0 # E: "%c" requires an integer in range(256) or a single byte (expression has type "float") b'%c' % 's' # E: "%c" requires an integer in range(256) or a single byte (expression has type "str") @@ -435,21 +450,21 @@ N = NewType('N', bytes) n: N '{}'.format(a) -'{}'.format(b) # E: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior -'{}'.format(x) # E: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior -'{}'.format(n) # E: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior +'{}'.format(b) # E: If x = b'abc' then f"{x}" or "{}".format(x) produces "b'abc'", not "abc". If this is desired behavior, use f"{x!r}" or "{!r}".format(x). Otherwise, decode the bytes +'{}'.format(x) # E: If x = b'abc' then f"{x}" or "{}".format(x) produces "b'abc'", not "abc". If this is desired behavior, use f"{x!r}" or "{!r}".format(x). Otherwise, decode the bytes +'{}'.format(n) # E: If x = b'abc' then f"{x}" or "{}".format(x) produces "b'abc'", not "abc". If this is desired behavior, use f"{x!r}" or "{!r}".format(x). Otherwise, decode the bytes -f'{b}' # E: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior -f'{x}' # E: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior -f'{n}' # E: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior +f'{b}' # E: If x = b'abc' then f"{x}" or "{}".format(x) produces "b'abc'", not "abc". If this is desired behavior, use f"{x!r}" or "{!r}".format(x). Otherwise, decode the bytes +f'{x}' # E: If x = b'abc' then f"{x}" or "{}".format(x) produces "b'abc'", not "abc". If this is desired behavior, use f"{x!r}" or "{!r}".format(x). Otherwise, decode the bytes +f'{n}' # E: If x = b'abc' then f"{x}" or "{}".format(x) produces "b'abc'", not "abc". If this is desired behavior, use f"{x!r}" or "{!r}".format(x). Otherwise, decode the bytes class C(Generic[B]): x: B def meth(self) -> None: - '{}'.format(self.x) # E: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior + '{}'.format(self.x) # E: If x = b'abc' then f"{x}" or "{}".format(x) produces "b'abc'", not "abc". If this is desired behavior, use f"{x!r}" or "{!r}".format(x). Otherwise, decode the bytes def func(x: A) -> A: - '{}'.format(x) # E: On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior + '{}'.format(x) # E: If x = b'abc' then f"{x}" or "{}".format(x) produces "b'abc'", not "abc". If this is desired behavior, use f"{x!r}" or "{!r}".format(x). Otherwise, decode the bytes return x '{!r}'.format(a) @@ -469,6 +484,23 @@ class D(bytes): '{}'.format(D()) [builtins fixtures/primitives.pyi] +[case testNoSpuriousFormattingErrorsDuringFailedOverlodMatch] +from typing import overload, Callable + +@overload +def sub(pattern: str, repl: Callable[[str], str]) -> str: ... +@overload +def sub(pattern: bytes, repl: Callable[[bytes], bytes]) -> bytes: ... +def sub(pattern: object, repl: object) -> object: + pass + +def better_snakecase(text: str) -> str: + # Mypy used to emit a spurious error here + # warning about interpolating bytes into an f-string: + text = sub(r"([A-Z])([A-Z]+)([A-Z](?:[^A-Z]|$))", lambda match: f"{match}") + return text +[builtins fixtures/primitives.pyi] + [case testFormatCallFinal] from typing_extensions import Final @@ -556,3 +588,45 @@ class S: '{:%}'.format(0.001) [builtins fixtures/primitives.pyi] [typing fixtures/typing-medium.pyi] + +[case testEnumWithStringToFormatValue] +from enum import Enum + +class Responses(str, Enum): + TEMPLATED = 'insert {} here' + TEMPLATED_WITH_KW = 'insert {value} here' + NORMAL = 'something' + +Responses.TEMPLATED.format(42) +Responses.TEMPLATED_WITH_KW.format(value=42) +Responses.TEMPLATED.format() # E: Cannot find replacement for positional format specifier 0 +Responses.TEMPLATED_WITH_KW.format() # E: Cannot find replacement for named format specifier "value" +Responses.NORMAL.format(42) # E: Not all arguments converted during string formatting +Responses.NORMAL.format(value=42) # E: Not all arguments converted during string formatting +[builtins fixtures/primitives.pyi] + +[case testNonStringEnumToFormatValue] +from enum import Enum + +class Responses(Enum): + TEMPLATED = 'insert {value} here' + +Responses.TEMPLATED.format(value=42) # E: "Responses" has no attribute "format" +[builtins fixtures/primitives.pyi] + +[case testStrEnumWithStringToFormatValue] +# flags: --python-version 3.11 +from enum import StrEnum + +class Responses(StrEnum): + TEMPLATED = 'insert {} here' + TEMPLATED_WITH_KW = 'insert {value} here' + NORMAL = 'something' + +Responses.TEMPLATED.format(42) +Responses.TEMPLATED_WITH_KW.format(value=42) +Responses.TEMPLATED.format() # E: Cannot find replacement for positional format specifier 0 +Responses.TEMPLATED_WITH_KW.format() # E: Cannot find replacement for named format specifier "value" +Responses.NORMAL.format(42) # E: Not all arguments converted during string formatting +Responses.NORMAL.format(value=42) # E: Not all arguments converted during string formatting +[builtins fixtures/primitives.pyi] diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index c23bbb77f643..3aecbe065c27 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -10,8 +10,9 @@ [case testCallingVariableWithFunctionType] from typing import Callable -f = None # type: Callable[[A], B] -a, b = None, None # type: (A, B) +f: Callable[[A], B] +a: A +b: B if int(): a = f(a) # E: Incompatible types in assignment (expression has type "B", variable has type "A") if int(): @@ -82,9 +83,9 @@ from typing import Callable class A: pass class B(A): pass -f = None # type: Callable[[B], A] -g = None # type: Callable[[A], A] # subtype of f -h = None # type: Callable[[B], B] # subtype of f +f: Callable[[B], A] +g: Callable[[A], A] # subtype of f +h: Callable[[B], B] # subtype of f if int(): g = h # E: Incompatible types in assignment (expression has type "Callable[[B], B]", variable has type "Callable[[A], A]") if int(): @@ -132,7 +133,7 @@ ff = g from typing import Callable def f(a: int, b: str) -> None: pass -f_nonames = None # type: Callable[[int, str], None] +f_nonames: Callable[[int, str], None] def g(a: int, b: str = "") -> None: pass def h(aa: int, b: str = "") -> None: pass @@ -160,7 +161,7 @@ if int(): from typing import Any, Callable def everything(*args: Any, **kwargs: Any) -> None: pass -everywhere = None # type: Callable[..., None] +everywhere: Callable[..., None] def specific_1(a: int, b: str) -> None: pass def specific_2(a: int, *, b: str) -> None: pass @@ -238,6 +239,7 @@ if int(): gg = f # E: Incompatible types in assignment (expression has type "Callable[[int, str], None]", variable has type "Callable[[Arg(int, 'a'), Arg(str, 'b')], None]") [case testFunctionTypeCompatibilityWithOtherTypes] +# flags: --no-strict-optional from typing import Callable f = None # type: Callable[[], None] a, o = None, None # type: (A, object) @@ -248,7 +250,7 @@ if int(): if int(): f = o # E: Incompatible types in assignment (expression has type "object", variable has type "Callable[[], None]") if int(): - f = f() # E: Function does not return a value + f = f() # E: Function does not return a value (it only ever returns None) if int(): f = f @@ -272,8 +274,8 @@ def g(x: int) -> Tuple[()]: [case testFunctionSubtypingWithVoid] from typing import Callable -f = None # type: Callable[[], None] -g = None # type: Callable[[], object] +f: Callable[[], None] +g: Callable[[], object] if int(): f = g # E: Incompatible types in assignment (expression has type "Callable[[], object]", variable has type "Callable[[], None]") if int(): @@ -286,9 +288,9 @@ if int(): [case testFunctionSubtypingWithMultipleArgs] from typing import Callable -f = None # type: Callable[[A, A], None] -g = None # type: Callable[[A, B], None] -h = None # type: Callable[[B, B], None] +f: Callable[[A, A], None] +g: Callable[[A, B], None] +h: Callable[[B, B], None] if int(): f = g # E: Incompatible types in assignment (expression has type "Callable[[A, B], None]", variable has type "Callable[[A, A], None]") if int(): @@ -313,9 +315,9 @@ class B(A): pass [case testFunctionTypesWithDifferentArgumentCounts] from typing import Callable -f = None # type: Callable[[], None] -g = None # type: Callable[[A], None] -h = None # type: Callable[[A, A], None] +f: Callable[[], None] +g: Callable[[A], None] +h: Callable[[A, A], None] if int(): f = g # E: Incompatible types in assignment (expression has type "Callable[[A], None]", variable has type "Callable[[], None]") @@ -342,8 +344,8 @@ class A: def f() -> None: pass -t = None # type: type -a = None # type: A +t: type +a: A if int(): a = A # E: Incompatible types in assignment (expression has type "Type[A]", variable has type "A") @@ -356,9 +358,9 @@ if int(): from foo import * [file foo.pyi] from typing import Callable, overload -f = None # type: Callable[[AA], A] -g = None # type: Callable[[B], B] -h = None # type: Callable[[A], AA] +f: Callable[[AA], A] +g: Callable[[B], B] +h: Callable[[A], AA] if int(): h = i # E: Incompatible types in assignment (expression has type overloaded function, variable has type "Callable[[A], AA]") @@ -395,11 +397,13 @@ def j(x: A) -> AA: from foo import * [file foo.pyi] from typing import Callable, overload -g1 = None # type: Callable[[A], A] -g2 = None # type: Callable[[B], B] -g3 = None # type: Callable[[C], C] -g4 = None # type: Callable[[A], B] -a, b, c = None, None, None # type: (A, B, C) +g1: Callable[[A], A] +g2: Callable[[B], B] +g3: Callable[[C], C] +g4: Callable[[A], B] +a: A +b: B +c: C if int(): b = f(a) # E: Incompatible types in assignment (expression has type "A", variable has type "B") @@ -448,15 +452,15 @@ f([D]) # E: List item 0 has incompatible type "Type[D]"; expected "Callable[[An [case testSubtypingTypeTypeAsCallable] from typing import Callable, Type class A: pass -x = None # type: Callable[..., A] -y = None # type: Type[A] +x: Callable[..., A] +y: Type[A] x = y [case testSubtypingCallableAsTypeType] from typing import Callable, Type class A: pass -x = None # type: Callable[..., A] -y = None # type: Type[A] +x: Callable[..., A] +y: Type[A] if int(): y = x # E: Incompatible types in assignment (expression has type "Callable[..., A]", variable has type "Type[A]") @@ -491,62 +495,61 @@ if int(): [case testDefaultArgumentExpressions] import typing +class B: pass +class A: pass + def f(x: 'A' = A()) -> None: b = x # type: B # E: Incompatible types in assignment (expression has type "A", variable has type "B") a = x # type: A - -class B: pass -class A: pass [out] [case testDefaultArgumentExpressions2] import typing -def f(x: 'A' = B()) -> None: # E: Incompatible default for argument "x" (default has type "B", argument has type "A") - b = x # type: B # E: Incompatible types in assignment (expression has type "A", variable has type "B") - a = x # type: A - class B: pass class A: pass +def f(x: 'A' = B()) -> None: # E: Incompatible default for argument "x" (default has type "B", argument has type "A") + b = x # type: B # E: Incompatible types in assignment (expression has type "A", variable has type "B") + a = x # type: A [case testDefaultArgumentExpressionsGeneric] from typing import TypeVar T = TypeVar('T', bound='A') -def f(x: T = B()) -> None: # E: Incompatible default for argument "x" (default has type "B", argument has type "T") - b = x # type: B # E: Incompatible types in assignment (expression has type "T", variable has type "B") - a = x # type: A class B: pass class A: pass +def f(x: T = B()) -> None: # E: Incompatible default for argument "x" (default has type "B", argument has type "T") + b = x # type: B # E: Incompatible types in assignment (expression has type "T", variable has type "B") + a = x # type: A [case testDefaultArgumentsWithSubtypes] import typing +class A: pass +class B(A): pass + def f(x: 'B' = A()) -> None: # E: Incompatible default for argument "x" (default has type "A", argument has type "B") pass def g(x: 'A' = B()) -> None: pass - -class A: pass -class B(A): pass [out] [case testMultipleDefaultArgumentExpressions] import typing +class A: pass +class B: pass + def f(x: 'A' = B(), y: 'B' = B()) -> None: # E: Incompatible default for argument "x" (default has type "B", argument has type "A") pass def h(x: 'A' = A(), y: 'B' = B()) -> None: pass - -class A: pass -class B: pass [out] [case testMultipleDefaultArgumentExpressions2] import typing -def g(x: 'A' = A(), y: 'B' = A()) -> None: # E: Incompatible default for argument "y" (default has type "A", argument has type "B") - pass - class A: pass class B: pass + +def g(x: 'A' = A(), y: 'B' = A()) -> None: # E: Incompatible default for argument "y" (default has type "A", argument has type "B") + pass [out] [case testDefaultArgumentsAndSignatureAsComment] @@ -574,11 +577,11 @@ A().f('') # E: Argument 1 to "f" of "A" has incompatible type "str"; expected "i [case testMethodAsDataAttribute] from typing import Any, Callable, ClassVar class B: pass -x = None # type: Any +x: Any class A: f = x # type: ClassVar[Callable[[A], None]] g = x # type: ClassVar[Callable[[A, B], None]] -a = None # type: A +a: A a.f() a.g(B()) a.f(a) # E: Too many arguments @@ -587,21 +590,21 @@ a.g() # E: Too few arguments [case testMethodWithInvalidMethodAsDataAttribute] from typing import Any, Callable, ClassVar class B: pass -x = None # type: Any +x: Any class A: f = x # type: ClassVar[Callable[[], None]] g = x # type: ClassVar[Callable[[B], None]] -a = None # type: A +a: A a.f() # E: Attribute function "f" with type "Callable[[], None]" does not accept self argument a.g() # E: Invalid self argument "A" to attribute function "g" with type "Callable[[B], None]" [case testMethodWithDynamicallyTypedMethodAsDataAttribute] from typing import Any, Callable, ClassVar class B: pass -x = None # type: Any +x: Any class A: f = x # type: ClassVar[Callable[[Any], Any]] -a = None # type: A +a: A a.f() a.f(a) # E: Too many arguments @@ -628,7 +631,7 @@ class A: @overload def f(self, b: B) -> None: pass g = f -a = None # type: A +a: A a.g() a.g(B()) a.g(a) # E: No overload variant matches argument type "A" \ @@ -641,7 +644,7 @@ a.g(a) # E: No overload variant matches argument type "A" \ class A: def f(self, x): pass g = f -a = None # type: A +a: A a.g(object()) a.g(a, a) # E: Too many arguments a.g() # E: Too few arguments @@ -653,7 +656,7 @@ class B: pass class A(Generic[t]): def f(self, x: t) -> None: pass g = f -a = None # type: A[B] +a: A[B] a.g(B()) a.g(a) # E: Argument 1 has incompatible type "A[B]"; expected "B" @@ -662,11 +665,11 @@ from typing import Any, TypeVar, Generic, Callable, ClassVar t = TypeVar('t') class B: pass class C: pass -x = None # type: Any +x: Any class A(Generic[t]): f = x # type: ClassVar[Callable[[A[B]], None]] -ab = None # type: A[B] -ac = None # type: A[C] +ab: A[B] +ac: A[C] ab.f() ac.f() # E: Invalid self argument "A[C]" to attribute function "f" with type "Callable[[A[B]], None]" @@ -675,21 +678,21 @@ from typing import Any, TypeVar, Generic, Callable, ClassVar t = TypeVar('t') class B: pass class C: pass -x = None # type: Any +x: Any class A(Generic[t]): f = x # type: ClassVar[Callable[[A], None]] -ab = None # type: A[B] -ac = None # type: A[C] +ab: A[B] +ac: A[C] ab.f() ac.f() [case testCallableDataAttribute] from typing import Callable, ClassVar class A: - g = None # type: ClassVar[Callable[[A], None]] + g: ClassVar[Callable[[A], None]] def __init__(self, f: Callable[[], None]) -> None: self.f = f -a = A(None) +a = A(lambda: None) a.f() a.g() a.f(a) # E: Too many arguments @@ -896,7 +899,7 @@ def dec(x) -> Callable[[Any], None]: pass class A: @dec def f(self, a, b, c): pass -a = None # type: A +a: A a.f() a.f(None) # E: Too many arguments for "f" of "A" @@ -1946,9 +1949,9 @@ def a(f: Callable[[VarArg(int)], int]): from typing import Callable from mypy_extensions import Arg, DefaultArg -int_str_fun = None # type: Callable[[int, str], str] -int_opt_str_fun = None # type: Callable[[int, DefaultArg(str, None)], str] -int_named_str_fun = None # type: Callable[[int, Arg(str, 's')], str] +int_str_fun: Callable[[int, str], str] +int_opt_str_fun: Callable[[int, DefaultArg(str, None)], str] +int_named_str_fun: Callable[[int, Arg(str, 's')], str] def isf(ii: int, ss: str) -> str: return ss @@ -2141,6 +2144,7 @@ main:8: error: Cannot use a covariant type variable as a parameter from typing import TypeVar, Generic, Callable [case testRejectContravariantReturnType] +# flags: --no-strict-optional from typing import TypeVar, Generic t = TypeVar('t', contravariant=True) @@ -2149,9 +2153,10 @@ class A(Generic[t]): return None [builtins fixtures/bool.pyi] [out] -main:5: error: Cannot use a contravariant type variable as return type +main:6: error: Cannot use a contravariant type variable as return type [case testAcceptCovariantReturnType] +# flags: --no-strict-optional from typing import TypeVar, Generic t = TypeVar('t', covariant=True) @@ -2159,6 +2164,7 @@ class A(Generic[t]): def foo(self) -> t: return None [builtins fixtures/bool.pyi] + [case testAcceptContravariantArgument] from typing import TypeVar, Generic @@ -2324,7 +2330,7 @@ T = TypeVar('T') def deco() -> Callable[[T], T]: pass reveal_type(deco) # N: Revealed type is "def () -> def [T] (T`-1) -> T`-1" f = deco() -reveal_type(f) # N: Revealed type is "def [T] (T`-1) -> T`-1" +reveal_type(f) # N: Revealed type is "def [T] (T`1) -> T`1" i = f(3) reveal_type(i) # N: Revealed type is "builtins.int" @@ -2337,7 +2343,7 @@ U = TypeVar('U') def deco(x: U) -> Callable[[T, U], T]: pass reveal_type(deco) # N: Revealed type is "def [U] (x: U`-1) -> def [T] (T`-2, U`-1) -> T`-2" f = deco("foo") -reveal_type(f) # N: Revealed type is "def [T] (T`-2, builtins.str) -> T`-2" +reveal_type(f) # N: Revealed type is "def [T] (T`1, builtins.str) -> T`1" i = f(3, "eggs") reveal_type(i) # N: Revealed type is "builtins.int" @@ -2348,9 +2354,9 @@ T = TypeVar('T') R = TypeVar('R') def deco() -> Callable[[T], Callable[[T, R], R]]: pass f = deco() -reveal_type(f) # N: Revealed type is "def [T] (T`-1) -> def [R] (T`-1, R`-2) -> R`-2" +reveal_type(f) # N: Revealed type is "def [T] (T`2) -> def [R] (T`2, R`1) -> R`1" g = f(3) -reveal_type(g) # N: Revealed type is "def [R] (builtins.int, R`-2) -> R`-2" +reveal_type(g) # N: Revealed type is "def [R] (builtins.int, R`3) -> R`3" s = g(4, "foo") reveal_type(s) # N: Revealed type is "builtins.str" @@ -2539,7 +2545,6 @@ reveal_type(bar(None)) # N: Revealed type is "None" [out] [case testNoComplainOverloadNoneStrict] -# flags: --strict-optional from typing import overload, Optional @overload def bar(x: None) -> None: @@ -2568,7 +2573,6 @@ xx: Optional[int] = X(x_in) [out] [case testNoComplainInferredNoneStrict] -# flags: --strict-optional from typing import TypeVar, Optional T = TypeVar('T') def X(val: T) -> T: ... @@ -2612,7 +2616,7 @@ def f() -> int: ... [case testLambdaDefaultTypeErrors] lambda a=(1 + 'asdf'): a # E: Unsupported operand types for + ("int" and "str") lambda a=nonsense: a # E: Name "nonsense" is not defined -def f(x: int = i): # E: Name "i" is not defined # E: Name "i" is used before definition +def f(x: int = i): # E: Name "i" is not defined i = 42 [case testRevealTypeOfCallExpressionReturningNoneWorks] @@ -2725,3 +2729,502 @@ TS = TypeVar("TS", bound=str) f: Callable[[Sequence[TI]], None] g: Callable[[Union[Sequence[TI], Sequence[TS]]], None] f = g + +[case testOverrideDecoratedProperty] +class Base: + @property + def foo(self) -> int: ... + +class decorator: + def __init__(self, fn): + self.fn = fn + def __call__(self, decorated_self) -> int: + return self.fn(decorated_self) + +class Child(Base): + @property + @decorator + def foo(self) -> int: + return 42 + +reveal_type(Child().foo) # N: Revealed type is "builtins.int" + +class BadChild1(Base): + @decorator + def foo(self) -> int: # E: Signature of "foo" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: int \ + # N: Subclass: \ + # N: decorator + return 42 + +class not_a_decorator: + def __init__(self, fn): ... + +class BadChild2(Base): + @property + @not_a_decorator + def foo(self) -> int: # E: "not_a_decorator" not callable \ + # E: Signature of "foo" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: int \ + # N: Subclass: \ + # N: not_a_decorator + return 42 +[builtins fixtures/property.pyi] + +[case explicitOverride] +# flags: --python-version 3.12 +from typing import override + +class A: + def f(self, x: int) -> str: pass + @override + def g(self, x: int) -> str: pass # E: Method "g" is marked as an override, but no base method was found with this name + +class B(A): + @override + def f(self, x: int) -> str: pass + @override + def g(self, x: int) -> str: pass + +class C(A): + @override + def f(self, x: str) -> str: pass # E: Argument 1 of "f" is incompatible with supertype "A"; supertype defines the argument type as "int" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides + def g(self, x: int) -> str: pass + +class D(A): pass +class E(D): pass +class F(E): + @override + def f(self, x: int) -> str: pass +[typing fixtures/typing-override.pyi] + +[case explicitOverrideStaticmethod] +# flags: --python-version 3.12 +from typing import override + +class A: + @staticmethod + def f(x: int) -> str: pass + +class B(A): + @staticmethod + @override + def f(x: int) -> str: pass + @override + @staticmethod + def g(x: int) -> str: pass # E: Method "g" is marked as an override, but no base method was found with this name + +class C(A): # inverted order of decorators + @override + @staticmethod + def f(x: int) -> str: pass + @override + @staticmethod + def g(x: int) -> str: pass # E: Method "g" is marked as an override, but no base method was found with this name + +class D(A): + @staticmethod + @override + def f(x: str) -> str: pass # E: Argument 1 of "f" is incompatible with supertype "A"; supertype defines the argument type as "int" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides +[typing fixtures/typing-override.pyi] +[builtins fixtures/staticmethod.pyi] + +[case explicitOverrideClassmethod] +# flags: --python-version 3.12 +from typing import override + +class A: + @classmethod + def f(cls, x: int) -> str: pass + +class B(A): + @classmethod + @override + def f(cls, x: int) -> str: pass + @override + @classmethod + def g(cls, x: int) -> str: pass # E: Method "g" is marked as an override, but no base method was found with this name + +class C(A): # inverted order of decorators + @override + @classmethod + def f(cls, x: int) -> str: pass + @override + @classmethod + def g(cls, x: int) -> str: pass # E: Method "g" is marked as an override, but no base method was found with this name + +class D(A): + @classmethod + @override + def f(cls, x: str) -> str: pass # E: Argument 1 of "f" is incompatible with supertype "A"; supertype defines the argument type as "int" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides +[typing fixtures/typing-override.pyi] +[builtins fixtures/classmethod.pyi] + +[case explicitOverrideProperty] +# flags: --python-version 3.12 +from typing import override + +class A: + @property + def f(self) -> str: pass + +class B(A): + @property + @override + def f(self) -> str: pass + @override + @property + def g(self) -> str: pass # E: Method "g" is marked as an override, but no base method was found with this name + +class C(A): # inverted order of decorators + @override + @property + def f(self) -> str: pass + @override + @property + def g(self) -> str: pass # E: Method "g" is marked as an override, but no base method was found with this name + +class D(A): + @property + @override + def f(self) -> int: pass # E: Signature of "f" incompatible with supertype "A" \ + # N: Superclass: \ + # N: str \ + # N: Subclass: \ + # N: int +[typing fixtures/typing-override.pyi] +[builtins fixtures/property.pyi] + +[case explicitOverrideSettableProperty] +# flags: --python-version 3.12 +from typing import override + +class A: + @property + def f(self) -> str: pass + + @f.setter + def f(self, value: str) -> None: pass + +class B(A): + @property # E: Read-only property cannot override read-write property + @override + def f(self) -> str: pass + +class C(A): + @override + @property + def f(self) -> str: pass + + @f.setter + def f(self, value: str) -> None: pass + +class D(A): + @override # E: Signature of "f" incompatible with supertype "A" \ + # N: Superclass: \ + # N: str \ + # N: Subclass: \ + # N: int + @property + def f(self) -> int: pass + + @f.setter + def f(self, value: int) -> None: pass +[typing fixtures/typing-override.pyi] +[builtins fixtures/property.pyi] + +[case invalidExplicitOverride] +# flags: --python-version 3.12 +from typing import override + +@override # E: "override" used with a non-method +def f(x: int) -> str: pass + +@override # this should probably throw an error but the signature from typeshed should ensure this already +class A: pass + +def g() -> None: + @override # E: "override" used with a non-method + def h(b: bool) -> int: pass +[typing fixtures/typing-override.pyi] + +[case explicitOverrideSpecialMethods] +# flags: --python-version 3.12 +from typing import override + +class A: + def __init__(self, a: int) -> None: pass + +class B(A): + @override + def __init__(self, b: str) -> None: pass + +class C: + @override + def __init__(self, a: int) -> None: pass +[typing fixtures/typing-override.pyi] + +[case explicitOverrideFromExtensions] +from typing_extensions import override + +class A: + def f(self, x: int) -> str: pass + +class B(A): + @override + def f2(self, x: int) -> str: pass # E: Method "f2" is marked as an override, but no base method was found with this name +[builtins fixtures/tuple.pyi] + +[case explicitOverrideOverloads] +# flags: --python-version 3.12 +from typing import overload, override + +class A: + def f(self, x: int) -> str: pass + +class B(A): + @overload # E: Method "f2" is marked as an override, but no base method was found with this name + def f2(self, x: int) -> str: pass + @overload + def f2(self, x: str) -> str: pass + @override + def f2(self, x: int | str) -> str: pass +[typing fixtures/typing-override.pyi] + +[case explicitOverrideNotOnOverloadsImplementation] +# flags: --python-version 3.12 +from typing import overload, override + +class A: + def f(self, x: int) -> str: pass + +class B(A): + @overload # E: Method "f2" is marked as an override, but no base method was found with this name + def f2(self, x: int) -> str: pass + @override + @overload + def f2(self, x: str) -> str: pass + def f2(self, x: int | str) -> str: pass + +class C(A): + @overload + def f(self, y: int) -> str: pass + @override + @overload + def f(self, y: str) -> str: pass + def f(self, y: int | str) -> str: pass +[typing fixtures/typing-override.pyi] + +[case explicitOverrideOnMultipleOverloads] +# flags: --python-version 3.12 +from typing import overload, override + +class A: + def f(self, x: int) -> str: pass + +class B(A): + @override # E: Method "f2" is marked as an override, but no base method was found with this name + @overload + def f2(self, x: int) -> str: pass + @override + @overload + def f2(self, x: str) -> str: pass + def f2(self, x: int | str) -> str: pass + +class C(A): + @overload + def f(self, y: int) -> str: pass + @override + @overload + def f(self, y: str) -> str: pass + @override + def f(self, y: int | str) -> str: pass +[typing fixtures/typing-override.pyi] + +[case explicitOverrideCyclicDependency] +# flags: --python-version 3.12 +import b +[file a.py] +from typing import override +import b +import c + +class A(b.B): + @override # This is fine + @c.deco + def meth(self) -> int: ... +[file b.py] +import a +import c + +class B: + @c.deco + def meth(self) -> int: ... +[file c.py] +from typing import TypeVar, Tuple, Callable +T = TypeVar('T') +def deco(f: Callable[..., T]) -> Callable[..., Tuple[T, int]]: ... +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-override.pyi] + +[case requireExplicitOverrideMethod] +# flags: --enable-error-code explicit-override --python-version 3.12 +from typing import override + +class A: + def f(self, x: int) -> str: pass + +class B(A): + @override + def f(self, y: int) -> str: pass + +class C(A): + def f(self, y: int) -> str: pass # E: Method "f" is not using @override but is overriding a method in class "__main__.A" + +class D(B): + def f(self, y: int) -> str: pass # E: Method "f" is not using @override but is overriding a method in class "__main__.B" +[typing fixtures/typing-override.pyi] + +[case requireExplicitOverrideSpecialMethod] +# flags: --enable-error-code explicit-override --python-version 3.12 +from typing import Callable, Self, TypeVar, override, overload + +T = TypeVar('T') +def some_decorator(f: Callable[..., T]) -> Callable[..., T]: ... + +# Don't require override decorator for __init__ and __new__ +# See: https://github.com/python/typing/issues/1376 +class A: + def __init__(self) -> None: pass + def __new__(cls) -> Self: pass + +class B(A): + def __init__(self) -> None: pass + def __new__(cls) -> Self: pass + +class C(A): + @some_decorator + def __init__(self) -> None: pass + + @some_decorator + def __new__(cls) -> Self: pass + +class D(A): + @overload + def __init__(self, x: int) -> None: ... + @overload + def __init__(self, x: str) -> None: ... + def __init__(self, x): pass + + @overload + def __new__(cls, x: int) -> Self: pass + @overload + def __new__(cls, x: str) -> Self: pass + def __new__(cls, x): pass +[typing fixtures/typing-override.pyi] + +[case requireExplicitOverrideProperty] +# flags: --enable-error-code explicit-override --python-version 3.12 +from typing import override + +class A: + @property + def prop(self) -> int: pass + +class B(A): + @override + @property + def prop(self) -> int: pass + +class C(A): + @property + def prop(self) -> int: pass # E: Method "prop" is not using @override but is overriding a method in class "__main__.A" +[typing fixtures/typing-override.pyi] +[builtins fixtures/property.pyi] + +[case requireExplicitOverrideOverload] +# flags: --enable-error-code explicit-override --python-version 3.12 +from typing import overload, override + +class A: + @overload + def f(self, x: int) -> str: ... + @overload + def f(self, x: str) -> str: ... + def f(self, x): pass + +class B(A): + @overload + def f(self, y: int) -> str: ... + @overload + def f(self, y: str) -> str: ... + @override + def f(self, y): pass + +class C(A): + @overload + @override + def f(self, y: int) -> str: ... + @overload + def f(self, y: str) -> str: ... + def f(self, y): pass + +class D(A): + @overload + def f(self, y: int) -> str: ... + @overload + def f(self, y: str) -> str: ... + def f(self, y): pass # E: Method "f" is not using @override but is overriding a method in class "__main__.A" +[typing fixtures/typing-override.pyi] + +[case requireExplicitOverrideMultipleInheritance] +# flags: --enable-error-code explicit-override --python-version 3.12 +from typing import override + +class A: + def f(self, x: int) -> str: pass +class B: + def f(self, y: int) -> str: pass + +class C(A, B): + @override + def f(self, z: int) -> str: pass + +class D(A, B): + def f(self, z: int) -> str: pass # E: Method "f" is not using @override but is overriding a method in class "__main__.A" +[typing fixtures/typing-override.pyi] + +[case testExplicitOverrideAllowedForPrivate] +# flags: --enable-error-code explicit-override --python-version 3.12 + +class B: + def __f(self, y: int) -> str: pass + +class C(B): + def __f(self, y: int) -> str: pass # OK +[typing fixtures/typing-override.pyi] + +[case testCallableProperty] +from typing import Callable + +class something_callable: + def __call__(self, fn) -> str: ... + +def decorator(fn: Callable[..., int]) -> something_callable: ... + +class A: + @property + @decorator + def f(self) -> int: ... + +reveal_type(A.f) # N: Revealed type is "__main__.something_callable" +reveal_type(A().f) # N: Revealed type is "builtins.str" +[builtins fixtures/property.pyi] diff --git a/test-data/unit/check-functools.test b/test-data/unit/check-functools.test index f95b823a5291..e721a56850e1 100644 --- a/test-data/unit/check-functools.test +++ b/test-data/unit/check-functools.test @@ -20,7 +20,6 @@ Ord() <= 1 # E: Unsupported operand types for <= ("Ord" and "int") Ord() == 1 Ord() > 1 # E: Unsupported operand types for > ("Ord" and "int") Ord() >= 1 # E: Unsupported operand types for >= ("Ord" and "int") -[builtins fixtures/ops.pyi] [builtins fixtures/dict.pyi] [case testTotalOrderingLambda] @@ -43,7 +42,6 @@ Ord() <= 1 # E: Unsupported operand types for <= ("Ord" and "int") Ord() == 1 Ord() > 1 # E: Unsupported operand types for > ("Ord" and "int") Ord() >= 1 # E: Unsupported operand types for >= ("Ord" and "int") -[builtins fixtures/ops.pyi] [builtins fixtures/dict.pyi] [case testTotalOrderingNonCallable] @@ -59,8 +57,6 @@ class Ord(object): Ord() <= Ord() # E: Unsupported left operand type for <= ("Ord") Ord() > Ord() # E: "int" not callable Ord() >= Ord() # E: Unsupported left operand type for >= ("Ord") - -[builtins fixtures/ops.pyi] [builtins fixtures/dict.pyi] [case testTotalOrderingReturnNotBool] @@ -79,8 +75,6 @@ reveal_type(Ord() <= Ord()) # N: Revealed type is "Any" reveal_type(Ord() == Ord()) # N: Revealed type is "builtins.bool" reveal_type(Ord() > Ord()) # N: Revealed type is "Any" reveal_type(Ord() >= Ord()) # N: Revealed type is "Any" - -[builtins fixtures/ops.pyi] [builtins fixtures/dict.pyi] [case testTotalOrderingAllowsAny] @@ -105,11 +99,9 @@ Ord() <= 1 # E: Unsupported left operand type for <= ("Ord") Ord() == 1 Ord() > 1 Ord() >= 1 # E: Unsupported left operand type for >= ("Ord") -[builtins fixtures/ops.pyi] [builtins fixtures/dict.pyi] [case testCachedProperty] -# flags: --python-version 3.8 from functools import cached_property class Parent: @property @@ -151,5 +143,4 @@ def f(d: D[C]) -> None: reveal_type(d.__gt__) # N: Revealed type is "def (other: Any) -> builtins.bool" d: D[int] # E: Type argument "int" of "D" must be a subtype of "C" -[builtins fixtures/ops.pyi] [builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-generic-alias.test b/test-data/unit/check-generic-alias.test index 574a57607d11..3ae815a5cd48 100644 --- a/test-data/unit/check-generic-alias.test +++ b/test-data/unit/check-generic-alias.test @@ -1,7 +1,7 @@ -- Test cases for generic aliases [case testGenericBuiltinWarning] -# flags: --python-version 3.7 +# flags: --python-version 3.8 t1: list t2: list[int] # E: "list" is not subscriptable, use "typing.List" instead t3: list[str] # E: "list" is not subscriptable, use "typing.List" instead @@ -21,14 +21,14 @@ t11: type[int] # E: "type" expects no type arguments, but 1 given [case testGenericBuiltinSetWarning] -# flags: --python-version 3.7 +# flags: --python-version 3.8 t1: set t2: set[int] # E: "set" is not subscriptable, use "typing.Set" instead [builtins fixtures/set.pyi] [case testGenericCollectionsWarning] -# flags: --python-version 3.7 +# flags: --python-version 3.8 import collections t01: collections.deque @@ -44,7 +44,6 @@ t10: collections.ChainMap[int, str] # E: "ChainMap" is not subscriptable, use " [case testGenericBuiltinFutureAnnotations] -# flags: --python-version 3.7 from __future__ import annotations t1: list t2: list[int] @@ -64,7 +63,6 @@ t11: type[int] [case testGenericCollectionsFutureAnnotations] -# flags: --python-version 3.7 from __future__ import annotations import collections @@ -200,7 +198,6 @@ t23: collections.abc.ValuesView[str] [case testGenericBuiltinTupleTyping] -# flags: --python-version 3.6 from typing import Tuple t01: Tuple = () @@ -248,7 +245,6 @@ reveal_type(tuple[int, ...]()) # N: Revealed type is "builtins.tuple[builtins.i [builtins fixtures/tuple.pyi] [case testTypeAliasWithBuiltinTupleInStub] -# flags: --python-version 3.6 import m reveal_type(m.a) # N: Revealed type is "builtins.tuple[builtins.int, ...]" reveal_type(m.b) # N: Revealed type is "Tuple[builtins.int, builtins.str]" @@ -261,7 +257,6 @@ b: B [builtins fixtures/tuple.pyi] [case testTypeAliasWithBuiltinListInStub] -# flags: --python-version 3.6 import m reveal_type(m.a) # N: Revealed type is "builtins.list[builtins.int]" reveal_type(m.b) # N: Revealed type is "builtins.list[builtins.list[builtins.int]]" @@ -280,7 +275,6 @@ d: type[str] [case testTypeAliasWithBuiltinListAliasInStub] -# flags: --python-version 3.6 import m reveal_type(m.a()[0]) # N: Revealed type is "builtins.int" diff --git a/test-data/unit/check-generic-subtyping.test b/test-data/unit/check-generic-subtyping.test index 1f06bc7c540a..fd40f128ff4a 100644 --- a/test-data/unit/check-generic-subtyping.test +++ b/test-data/unit/check-generic-subtyping.test @@ -9,9 +9,9 @@ [case testSubtypingAndInheritingNonGenericTypeFromGenericType] from typing import TypeVar, Generic T = TypeVar('T') -ac = None # type: A[C] -ad = None # type: A[D] -b = None # type: B +ac: A[C] +ad: A[D] +b: B if int(): b = ad # E: Incompatible types in assignment (expression has type "A[D]", variable has type "B") @@ -31,9 +31,9 @@ class D: pass [case testSubtypingAndInheritingGenericTypeFromNonGenericType] from typing import TypeVar, Generic T = TypeVar('T') -a = None # type: A -bc = None # type: B[C] -bd = None # type: B[D] +a: A +bc: B[C] +bd: B[D] if int(): bc = bd # E: Incompatible types in assignment (expression has type "B[D]", variable has type "B[C]") @@ -56,10 +56,10 @@ class D: pass from typing import TypeVar, Generic T = TypeVar('T') S = TypeVar('S') -ac = None # type: A[C] -ad = None # type: A[D] -bcc = None # type: B[C, C] -bdc = None # type: B[D, C] +ac: A[C] +ad: A[D] +bcc: B[C, C] +bdc: B[D, C] if int(): ad = bcc # E: Incompatible types in assignment (expression has type "B[C, C]", variable has type "A[D]") @@ -86,12 +86,12 @@ T = TypeVar('T') S = TypeVar('S') X = TypeVar('X') Y = TypeVar('Y') -ae = None # type: A[A[E]] -af = None # type: A[A[F]] +ae: A[A[E]] +af: A[A[F]] -cef = None # type: C[E, F] -cff = None # type: C[F, F] -cfe = None # type: C[F, E] +cef: C[E, F] +cff: C[F, F] +cfe: C[F, E] if int(): ae = cef # E: Incompatible types in assignment (expression has type "C[E, F]", variable has type "A[A[E]]") @@ -125,8 +125,9 @@ class C: pass from typing import TypeVar, Generic T = TypeVar('T') S = TypeVar('S') -b = None # type: B[C, D] -c, d = None, None # type: (C, D) +b: B[C, D] +c: C +d: D b.f(c) # E: Argument 1 to "f" of "A" has incompatible type "C"; expected "D" b.f(d) @@ -142,7 +143,9 @@ class D: pass [case testAccessingMethodInheritedFromGenericTypeInNonGenericType] from typing import TypeVar, Generic T = TypeVar('T') -b, c, d = None, None, None # type: (B, C, D) +b: B +c: C +d: D b.f(c) # E: Argument 1 to "f" of "A" has incompatible type "C"; expected "D" b.f(d) @@ -163,8 +166,9 @@ class A(Generic[T]): def __init__(self, a: T) -> None: self.a = a -b = None # type: B[C, D] -c, d = None, None # type: (C, D) +b: B[C, D] +c: C +d: D b.a = c # E: Incompatible types in assignment (expression has type "C", variable has type "D") b.a = d @@ -311,9 +315,9 @@ main:14: note: def [T1 <: str, S] f(self, x: T1, y: S) -> None [case testInheritanceFromGenericWithImplicitDynamicAndSubtyping] from typing import TypeVar, Generic T = TypeVar('T') -a = None # type: A -bc = None # type: B[C] -bd = None # type: B[D] +a: A +bc: B[C] +bd: B[D] if int(): a = bc # E: Incompatible types in assignment (expression has type "B[C]", variable has type "A") @@ -337,9 +341,9 @@ class B(Generic[T]): class A(B): pass class C: pass -a = None # type: A -c = None # type: C -bc = None # type: B[C] +a: A +c: C +bc: B[C] a.x = c # E: Incompatible types in assignment (expression has type "C", variable has type "B[Any]") a.f(c) # E: Argument 1 to "f" of "B" has incompatible type "C"; expected "B[Any]" @@ -350,9 +354,9 @@ a.f(bc) [case testInheritanceFromGenericWithImplicitDynamic] from typing import TypeVar, Generic T = TypeVar('T') -a = None # type: A -c = None # type: C -bc = None # type: B[C] +a: A +c: C +bc: B[C] class B(Generic[T]): def f(self, a: 'B[T]') -> None: pass @@ -430,7 +434,7 @@ B(1) C(1) C('a') # E: Argument 1 to "C" has incompatible type "str"; expected "int" D(A(1)) -D(1) # E: Argument 1 to "D" has incompatible type "int"; expected "A[]" +D(1) # E: Argument 1 to "D" has incompatible type "int"; expected "A[Never]" [case testInheritedConstructor2] @@ -458,10 +462,10 @@ from typing import TypeVar, Generic from abc import abstractmethod T = TypeVar('T') S = TypeVar('S') -acd = None # type: A[C, D] -adc = None # type: A[D, C] -ic = None # type: I[C] -id = None # type: I[D] +acd: A[C, D] +adc: A[D, C] +ic: I[C] +id: I[D] if int(): ic = acd # E: Incompatible types in assignment (expression has type "A[C, D]", variable has type "I[C]") @@ -482,8 +486,11 @@ class D: pass [case testSubtypingWithTypeImplementingGenericABCViaInheritance] from typing import TypeVar, Generic S = TypeVar('S') -a, b = None, None # type: (A, B) -ic, id, ie = None, None, None # type: (I[C], I[D], I[E]) +a: A +b: B +ic: I[C] +id: I[D] +ie: I[E] class I(Generic[S]): pass class B(I[C]): pass @@ -523,7 +530,9 @@ main:5: error: Class "B" has base "I" duplicated inconsistently from typing import TypeVar, Generic from abc import abstractmethod, ABCMeta t = TypeVar('t') -a, i, j = None, None, None # type: (A[object], I[object], J[object]) +a: A[object] +i: I[object] +j: J[object] (ii, jj) = (i, j) if int(): ii = a @@ -573,8 +582,9 @@ class D: pass from typing import Any, TypeVar, Generic from abc import abstractmethod T = TypeVar('T') -a = None # type: A -ic, id = None, None # type: (I[C], I[D]) +a: A +ic: I[C] +id: I[D] if int(): id = a # E: Incompatible types in assignment (expression has type "A", variable has type "I[D]") @@ -625,9 +635,9 @@ class D: pass from typing import Any, TypeVar, Generic from abc import abstractmethod T = TypeVar('T') -a = None # type: Any -ic = None # type: I[C] -id = None # type: I[D] +a: Any +ic: I[C] +id: I[D] ic = a id = a @@ -645,9 +655,9 @@ class D: pass from typing import Any, TypeVar, Generic from abc import abstractmethod T = TypeVar('T') -a = None # type: Any -ic = None # type: I[C] -id = None # type: I[D] +a: Any +ic: I[C] +id: I[D] ic = a id = a @@ -666,9 +676,9 @@ class D: pass from typing import Any, TypeVar, Generic from abc import abstractmethod T = TypeVar('T') -a = None # type: Any -jc = None # type: J[C] -jd = None # type: J[D] +a: Any +jc: J[C] +jd: J[D] jc = a jd = a @@ -700,8 +710,9 @@ class I(Generic[T]): class A: pass class B: pass -a, b = None, None # type: (A, B) -ia = None # type: I[A] +a: A +b: B +ia: I[A] ia.f(b) # E: Argument 1 to "f" of "I" has incompatible type "B"; expected "A" ia.f(a) @@ -717,8 +728,9 @@ class J(Generic[T]): class I(J[T], Generic[T]): pass class A: pass class B: pass -a, b = None, None # type: (A, B) -ia = None # type: I[A] +a: A +b: B +ia: I[A] ia.f(b) # E: Argument 1 to "f" of "J" has incompatible type "B"; expected "A" ia.f(a) @@ -731,7 +743,8 @@ ia.f(a) [case testMultipleAssignmentAndGenericSubtyping] from typing import Iterable -n, s = None, None # type: int, str +n: int +s: str class Nums(Iterable[int]): def __iter__(self): pass def __next__(self): pass @@ -754,9 +767,9 @@ class A: pass class B(A): pass class C(B): pass -a = None # type: G[A] -b = None # type: G[B] -c = None # type: G[C] +a: G[A] +b: G[B] +c: G[C] if int(): b = a # E: Incompatible types in assignment (expression has type "G[A]", variable has type "G[B]") @@ -773,9 +786,9 @@ class A: pass class B(A): pass class C(B): pass -a = None # type: G[A] -b = None # type: G[B] -c = None # type: G[C] +a: G[A] +b: G[B] +c: G[C] if int(): b = a @@ -792,9 +805,9 @@ class A: pass class B(A): pass class C(B): pass -a = None # type: G[A] -b = None # type: G[B] -c = None # type: G[C] +a: G[A] +b: G[B] +c: G[C] if int(): b = a # E: Incompatible types in assignment (expression has type "G[A]", variable has type "G[B]") @@ -990,6 +1003,7 @@ main:13: note: Revealed type is "builtins.dict[builtins.int, builtins.str]" main:14: error: Keywords must be strings main:14: error: Argument 1 to "func_with_kwargs" has incompatible type "**X1[str, int]"; expected "int" [builtins fixtures/dict.pyi] +[typing fixtures/typing-medium.pyi] [case testSubtypingMappingUnpacking3] from typing import Generic, TypeVar, Mapping, Iterable diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index a62028ca94ea..b1d1ff3f46a1 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -5,7 +5,9 @@ [case testGenericMethodReturnType] from typing import TypeVar, Generic T = TypeVar('T') -a, b, c = None, None, None # type: (A[B], B, C) +a: A[B] +b: B +c: C if int(): c = a.f() # E: Incompatible types in assignment (expression has type "B", variable has type "C") b = a.f() @@ -24,9 +26,9 @@ T = TypeVar('T') class A(Generic[T]): def f(self, a: T) -> None: pass -a = None # type: A[B] -b = None # type: B -c = None # type: C +a: A[B] +b: B +c: C a.f(c) # E: Argument 1 to "f" of "A" has incompatible type "C"; expected "B" a.f(b) @@ -40,7 +42,9 @@ class A(Generic[T]): def __init__(self, v: T) -> None: self.v = v -a, b, c = None, None, None # type: (A[B], B, C) +a: A[B] +b: B +c: C a.v = c # Fail a.v = b @@ -48,27 +52,31 @@ class B: pass class C: pass [builtins fixtures/tuple.pyi] [out] -main:8: error: Incompatible types in assignment (expression has type "C", variable has type "B") +main:10: error: Incompatible types in assignment (expression has type "C", variable has type "B") [case testGenericMemberVariable2] from typing import TypeVar, Generic T = TypeVar('T') -a, b, c = None, None, None # type: (A[B], B, C) +a: A[B] +b: B +c: C a.v = c # Fail a.v = b class A(Generic[T]): - v = None # type: T + v: T class B: pass class C: pass [builtins fixtures/tuple.pyi] [out] -main:4: error: Incompatible types in assignment (expression has type "C", variable has type "B") +main:6: error: Incompatible types in assignment (expression has type "C", variable has type "B") [case testSimpleGenericSubtyping] from typing import TypeVar, Generic T = TypeVar('T') -b, bb, c = None, None, None # type: (A[B], A[B], A[C]) +b: A[B] +bb: A[B] +c: A[C] if int(): c = b # E: Incompatible types in assignment (expression has type "A[B]", variable has type "A[C]") b = c # E: Incompatible types in assignment (expression has type "A[C]", variable has type "A[B]") @@ -86,7 +94,9 @@ class C(B): pass [case testGenericTypeCompatibilityWithAny] from typing import Any, TypeVar, Generic T = TypeVar('T') -b, c, d = None, None, None # type: (A[B], A[C], A[Any]) +b: A[B] +c: A[C] +d: A[Any] b = d c = d @@ -102,9 +112,9 @@ class C(B): pass [case testTypeVariableAsTypeArgument] from typing import TypeVar, Generic T = TypeVar('T') -a = None # type: A[B] -b = None # type: A[B] -c = None # type: A[C] +a: A[B] +b: A[B] +c: A[C] a.v = c # E: Incompatible types in assignment (expression has type "A[C]", variable has type "A[B]") if int(): @@ -123,9 +133,9 @@ class C: pass from typing import TypeVar, Generic S = TypeVar('S') T = TypeVar('T') -a = None # type: A[B, C] -s = None # type: B -t = None # type: C +a: A[B, C] +s: B +t: C if int(): t = a.s # E: Incompatible types in assignment (expression has type "B", variable has type "C") @@ -136,8 +146,8 @@ if int(): t = a.t class A(Generic[S, T]): - s = None # type: S - t = None # type: T + s: S + t: T class B: pass class C: pass @@ -145,9 +155,9 @@ class C: pass from typing import TypeVar, Generic S = TypeVar('S') T = TypeVar('T') -a = None # type: A[B, C] -s = None # type: B -t = None # type: C +a: A[B, C] +s: B +t: C a.f(s, s) # Fail a.f(t, t) # Fail @@ -165,9 +175,9 @@ main:9: error: Argument 1 to "f" of "A" has incompatible type "C"; expected "B" from typing import TypeVar, Generic S = TypeVar('S') T = TypeVar('T') -bc = None # type: A[B, C] -bb = None # type: A[B, B] -cb = None # type: A[C, B] +bc: A[B, C] +bb: A[B, B] +cb: A[C, B] if int(): bb = bc # E: Incompatible types in assignment (expression has type "A[B, C]", variable has type "A[B, B]") @@ -180,8 +190,8 @@ if int(): bc = bc class A(Generic[S, T]): - s = None # type: S - t = None # type: T + s: S + t: T class B: pass class C(B):pass @@ -195,7 +205,7 @@ class C(B):pass from typing import TypeVar, Generic T = TypeVar('T') class A(Generic[T]): - a = None # type: T + a: T def f(self, b: T) -> T: self.f(x) # Fail @@ -203,7 +213,7 @@ class A(Generic[T]): self.a = self.f(self.a) return self.a c = self # type: A[T] -x = None # type: B +x: B class B: pass [out] main:7: error: Argument 1 to "f" of "A" has incompatible type "B"; expected "T" @@ -215,8 +225,8 @@ S = TypeVar('S') T = TypeVar('T') class A(Generic[S, T]): def f(self) -> None: - s = None # type: S - t = None # type: T + s: S + t: T if int(): s = t # E: Incompatible types in assignment (expression has type "T", variable has type "S") t = s # E: Incompatible types in assignment (expression has type "S", variable has type "T") @@ -230,6 +240,7 @@ class B: pass [out] [case testCompatibilityOfNoneWithTypeVar] +# flags: --no-strict-optional from typing import TypeVar, Generic T = TypeVar('T') class A(Generic[T]): @@ -239,6 +250,7 @@ class A(Generic[T]): [out] [case testCompatibilityOfTypeVarWithObject] +# flags: --no-strict-optional from typing import TypeVar, Generic T = TypeVar('T') class A(Generic[T]): @@ -261,9 +273,9 @@ class A(Generic[T]): from typing import TypeVar, Generic S = TypeVar('S') T = TypeVar('T') -a = None # type: A[B, C] -b = None # type: B -c = None # type: C +a: A[B, C] +b: B +c: C if int(): b = a + b # E: Incompatible types in assignment (expression has type "C", variable has type "B") @@ -286,9 +298,9 @@ class C: pass [case testOperatorAssignmentWithIndexLvalue1] from typing import TypeVar, Generic T = TypeVar('T') -b = None # type: B -c = None # type: C -ac = None # type: A[C] +b: B +c: C +ac: A[C] ac[b] += b # Fail ac[c] += c # Fail @@ -309,9 +321,9 @@ main:8: error: Invalid index type "C" for "A[C]"; expected type "B" [case testOperatorAssignmentWithIndexLvalue2] from typing import TypeVar, Generic T = TypeVar('T') -b = None # type: B -c = None # type: C -ac = None # type: A[C] +b: B +c: C +ac: A[C] ac[b] += c # Fail ac[c] += c # Fail @@ -337,10 +349,10 @@ main:9: error: Invalid index type "B" for "A[C]"; expected type "C" [case testNestedGenericTypes] from typing import TypeVar, Generic T = TypeVar('T') -aab = None # type: A[A[B]] -aac = None # type: A[A[C]] -ab = None # type: A[B] -ac = None # type: A[C] +aab: A[A[B]] +aac: A[A[C]] +ab: A[B] +ac: A[C] if int(): ac = aab.x # E: Incompatible types in assignment (expression has type "A[B]", variable has type "A[C]") @@ -353,8 +365,8 @@ ab.y = aab ac.y = aac class A(Generic[T]): - x = None # type: T - y = None # type: A[A[T]] + x: T + y: A[A[T]] class B: pass @@ -377,12 +389,12 @@ def f(s: S, t: T) -> p[T, A]: a = t # type: S # E: Incompatible types in assignment (expression has type "T", variable has type "S") if int(): s = t # E: Incompatible types in assignment (expression has type "T", variable has type "S") - p_s_a = None # type: p[S, A] + p_s_a: p[S, A] if s: return p_s_a # E: Incompatible return value type (got "p[S, A]", expected "p[T, A]") b = t # type: T c = s # type: S - p_t_a = None # type: p[T, A] + p_t_a: p[T, A] return p_t_a [out] @@ -396,16 +408,16 @@ class A(Generic[T]): def f(self, s: S, t: T) -> p[S, T]: if int(): s = t # E: Incompatible types in assignment (expression has type "T", variable has type "S") - p_s_s = None # type: p[S, S] + p_s_s: p[S, S] if s: return p_s_s # E: Incompatible return value type (got "p[S, S]", expected "p[S, T]") - p_t_t = None # type: p[T, T] + p_t_t: p[T, T] if t: return p_t_t # E: Incompatible return value type (got "p[T, T]", expected "p[S, T]") if 1: t = t s = s - p_s_t = None # type: p[S, T] + p_s_t: p[S, T] return p_s_t [out] @@ -442,7 +454,7 @@ A[int, str, int]() # E: Type application has too many types (2 expected) [out] [case testInvalidTypeApplicationType] -a = None # type: A +a: A class A: pass a[A]() # E: Value of type "A" is not indexable A[A]() # E: The type "Type[A]" is not generic and not indexable @@ -546,7 +558,7 @@ IntIntNode = Node[int, int] SameNode = Node[T, T] def output_bad() -> IntNode[str]: - return Node(1, 1) # Eroor - bad return type, see out + return Node(1, 1) # Error - bad return type, see out def input(x: IntNode[str]) -> None: pass @@ -561,7 +573,7 @@ def func(x: IntNode[T]) -> IntNode[T]: return x reveal_type(func) # N: Revealed type is "def [T] (x: __main__.Node[builtins.int, T`-1]) -> __main__.Node[builtins.int, T`-1]" -func(1) # E: Argument 1 to "func" has incompatible type "int"; expected "Node[int, ]" +func(1) # E: Argument 1 to "func" has incompatible type "int"; expected "Node[int, Never]" func(Node('x', 1)) # E: Argument 1 to "Node" has incompatible type "str"; expected "int" reveal_type(func(Node(1, 'x'))) # N: Revealed type is "__main__.Node[builtins.int, builtins.str]" @@ -576,7 +588,7 @@ reveal_type(y) # N: Revealed type is "__main__.Node[builtins.str, builtins.str]" def wrap(x: T) -> IntNode[T]: return Node(1, x) -z = None # type: str +z: str reveal_type(wrap(z)) # N: Revealed type is "__main__.Node[builtins.int, builtins.str]" [out] @@ -584,7 +596,7 @@ main:13: error: Argument 2 to "Node" has incompatible type "int"; expected "str" -- Error formatting is a bit different (and probably better) with new analyzer [case testGenericTypeAliasesWrongAliases] -# flags: --show-column-numbers --python-version 3.6 --no-strict-optional +# flags: --show-column-numbers --no-strict-optional from typing import TypeVar, Generic, List, Callable, Tuple, Union T = TypeVar('T') S = TypeVar('S') @@ -618,7 +630,7 @@ main:11:5: error: "Node" expects 2 type arguments, but 3 given main:15:10: error: "list" expects 1 type argument, but 2 given main:16:19: error: "list" expects 1 type argument, but 2 given main:17:25: error: "Node" expects 2 type arguments, but 1 given -main:19:5: error: Bad number of arguments for type alias, expected: 1, given: 2 +main:19:5: error: Bad number of arguments for type alias, expected 1, given 2 main:22:13: note: Revealed type is "__main__.Node[builtins.int, builtins.str]" main:24:13: note: Revealed type is "__main__.Node[__main__.Node[builtins.int, builtins.int], builtins.list[builtins.int]]" main:26:5: error: Type variable "__main__.T" is invalid as target for type alias @@ -686,7 +698,7 @@ def output() -> IntNode[str]: return Node(1, 'x') x = output() # type: IntNode # This is OK (implicit Any) -y = None # type: IntNode +y: IntNode y.x = 1 y.x = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "int") y.y = 1 # Both are OK (implicit Any) @@ -707,7 +719,7 @@ class Node(Generic[T]): return self.x ListedNode = Node[List[T]] -l = None # type: ListedNode[int] +l: ListedNode[int] l.x.append(1) l.meth().append(1) reveal_type(l.meth()) # N: Revealed type is "builtins.list[builtins.int]" @@ -822,7 +834,7 @@ reveal_type(x) # N: Revealed type is "builtins.int" def f2(x: IntTP[T]) -> IntTP[T]: return x -f2((1, 2, 3)) # E: Argument 1 to "f2" has incompatible type "Tuple[int, int, int]"; expected "Tuple[int, ]" +f2((1, 2, 3)) # E: Argument 1 to "f2" has incompatible type "Tuple[int, int, int]"; expected "Tuple[int, Never]" reveal_type(f2((1, 'x'))) # N: Revealed type is "Tuple[builtins.int, builtins.str]" [builtins fixtures/for.pyi] @@ -848,7 +860,7 @@ def use_cb(arg: T, cb: C2[T]) -> Node[T]: return cb(arg, arg) use_cb(1, 1) # E: Argument 2 to "use_cb" has incompatible type "int"; expected "Callable[[int, int], Node[int]]" -my_cb = None # type: C2[int] +my_cb: C2[int] use_cb('x', my_cb) # E: Argument 2 to "use_cb" has incompatible type "Callable[[int, int], Node[int]]"; expected "Callable[[str, str], Node[str]]" reveal_type(use_cb(1, my_cb)) # N: Revealed type is "__main__.Node[builtins.int]" [builtins fixtures/tuple.pyi] @@ -884,7 +896,7 @@ from typing import TypeVar from a import Node, TupledNode T = TypeVar('T') -n = None # type: TupledNode[int] +n: TupledNode[int] n.x = 1 n.y = (1, 1) n.y = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "Tuple[int, int]") @@ -892,7 +904,7 @@ n.y = 'x' # E: Incompatible types in assignment (expression has type "str", vari def f(x: Node[T, T]) -> TupledNode[T]: return Node(x.x, (x.x, x.x)) -f(1) # E: Argument 1 to "f" has incompatible type "int"; expected "Node[, ]" +f(1) # E: Argument 1 to "f" has incompatible type "int"; expected "Node[Never, Never]" f(Node(1, 'x')) # E: Cannot infer type argument 1 of "f" reveal_type(Node('x', 'x')) # N: Revealed type is "a.Node[builtins.str, builtins.str]" @@ -932,7 +944,7 @@ Transform = Callable[[T, int], Tuple[T, R]] [case testGenericTypeAliasesImportingWithoutTypeVarError] from a import Alias -x: Alias[int, str] # E: Bad number of arguments for type alias, expected: 1, given: 2 +x: Alias[int, str] # E: Bad number of arguments for type alias, expected 1, given 2 reveal_type(x) # N: Revealed type is "builtins.list[builtins.list[Any]]" [file a.py] @@ -941,7 +953,6 @@ T = TypeVar('T') Alias = List[List[T]] [builtins fixtures/list.pyi] -[out] [case testGenericAliasWithTypeVarsFromDifferentModules] from mod import Alias, TypeVar @@ -988,8 +999,8 @@ reveal_type(x) # N: Revealed type is "Union[builtins.int, None]" reveal_type(y) # N: Revealed type is "builtins.int" U[int] # E: Type application targets a non-generic function or class -O[int] # E: Bad number of arguments for type alias, expected: 0, given: 1 # E: Type application is only supported for generic classes -[out] +O[int] # E: Bad number of arguments for type alias, expected 0, given 1 \ + # E: Type application is only supported for generic classes [case testAliasesInClassBodyNormalVsSubscripted] @@ -1282,9 +1293,9 @@ from typing import List class A: pass class B: pass class B2(B): pass -a = None # type: A -b = None # type: B -b2 = None # type: B2 +a: A +b: B +b2: B2 list_a = [a] list_b = [b] @@ -1313,8 +1324,8 @@ e, f = list_a # type: (A, object) [case testMultipleAssignmentWithListAndIndexing] from typing import List -a = None # type: List[A] -b = None # type: List[int] +a: List[A] +b: List[int] a[1], b[1] = a # E: Incompatible types in assignment (expression has type "A", target has type "int") a[1], a[2] = a @@ -1335,8 +1346,8 @@ class dict: pass [case testMultipleAssignmentWithIterable] from typing import Iterable, TypeVar -a = None # type: int -b = None # type: str +a: int +b: str T = TypeVar('T') def f(x: T) -> Iterable[T]: pass @@ -1380,7 +1391,7 @@ X = TypeVar('X') Y = TypeVar('Y') Z = TypeVar('Z') class OO: pass -a = None # type: A[object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object] +a: A[object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object] def f(a: OO) -> None: pass @@ -1393,7 +1404,7 @@ class A(Generic[B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W from typing import TypeVar, Generic S = TypeVar('S') T = TypeVar('T') -a = None # type: A[object, B] +a: A[object, B] def f(a: 'B') -> None: pass f(a) # E: Argument 1 to "f" has incompatible type "A[object, B]"; expected "B" @@ -1405,7 +1416,7 @@ class B: pass from typing import Callable, TypeVar, Generic S = TypeVar('S') T = TypeVar('T') -a = None # type: A[object, Callable[[], None]] +a: A[object, Callable[[], None]] def f(a: 'B') -> None: pass f(a) # E: Argument 1 to "f" has incompatible type "A[object, Callable[[], None]]"; expected "B" @@ -1424,7 +1435,8 @@ from foo import * from typing import overload, List class A: pass class B: pass -a, b = None, None # type: (A, B) +a: A +b: B @overload def f(a: List[A]) -> A: pass @@ -1452,7 +1464,8 @@ def f(a: B) -> B: pass @overload def f(a: List[T]) -> T: pass -a, b = None, None # type: (A, B) +a: A +b: B if int(): b = f([a]) # E: Incompatible types in assignment (expression has type "A", variable has type "B") @@ -1467,6 +1480,30 @@ if int(): b = f(b) [builtins fixtures/list.pyi] +[case testGenericDictWithOverload] +from typing import Dict, Generic, TypeVar, Any, overload +T = TypeVar("T") + +class Key(Generic[T]): ... +class CustomDict(dict): + @overload # type: ignore[override] + def __setitem__(self, key: Key[T], value: T) -> None: ... + @overload + def __setitem__(self, key: str, value: Any) -> None: ... + def __setitem__(self, key, value): + return super().__setitem__(key, value) + +def a1(d: Dict[str, Any]) -> None: + if (var := d.get("arg")) is None: + var = d["arg"] = {} + reveal_type(var) # N: Revealed type is "builtins.dict[Any, Any]" + +def a2(d: CustomDict) -> None: + if (var := d.get("arg")) is None: + var = d["arg"] = {} + reveal_type(var) # N: Revealed type is "builtins.dict[Any, Any]" +[builtins fixtures/dict.pyi] + -- Type variable scoping -- --------------------- @@ -2265,7 +2302,7 @@ class Box(Generic[T]): class IteratorBox(Box[Iterator[T]]): ... -@IteratorBox.wrap # E: Argument 1 to "wrap" of "Box" has incompatible type "Callable[[], int]"; expected "Callable[[], Iterator[]]" +@IteratorBox.wrap # E: Argument 1 to "wrap" of "Box" has incompatible type "Callable[[], int]"; expected "Callable[[], Iterator[Never]]" def g() -> int: ... [builtins fixtures/classmethod.pyi] @@ -2283,6 +2320,19 @@ def func(x: S) -> S: return C[S].get() [builtins fixtures/classmethod.pyi] +[case testGenericStaticMethodInGenericFunction] +from typing import Generic, TypeVar +T = TypeVar('T') +S = TypeVar('S') + +class C(Generic[T]): + @staticmethod + def get() -> T: ... + +def func(x: S) -> S: + return C[S].get() +[builtins fixtures/staticmethod.pyi] + [case testMultipleAssignmentFromAnyIterable] from typing import Any class A: @@ -2309,7 +2359,6 @@ class B(A): [builtins fixtures/classmethod.pyi] [case testSubclassingGenericSelfClassMethodOptional] -# flags: --strict-optional from typing import TypeVar, Type, Optional AT = TypeVar('AT', bound='A') @@ -2698,3 +2747,699 @@ def func(var: T) -> T: reveal_type(func(1)) # N: Revealed type is "builtins.int" [builtins fixtures/tuple.pyi] + +[case testGenericLambdaGenericMethodNoCrash] +# flags: --new-type-inference +from typing import TypeVar, Union, Callable, Generic + +S = TypeVar("S") +T = TypeVar("T") + +def f(x: Callable[[G[T]], int]) -> T: ... + +class G(Generic[T]): + def g(self, x: S) -> Union[S, T]: ... + +reveal_type(f(lambda x: x.g(0))) # N: Revealed type is "builtins.int" + +[case testDictStarInference] +class B: ... +class C1(B): ... +class C2(B): ... + +dict1 = {"a": C1()} +dict2 = {"a": C2(), **dict1} +reveal_type(dict2) # N: Revealed type is "builtins.dict[builtins.str, __main__.B]" +[builtins fixtures/dict.pyi] + +[case testDictStarAnyKeyJoinValue] +from typing import Any + +class B: ... +class C1(B): ... +class C2(B): ... + +dict1: Any +dict2 = {"a": C1(), **{x: C2() for x in dict1}} +reveal_type(dict2) # N: Revealed type is "builtins.dict[Any, __main__.B]" +[builtins fixtures/dict.pyi] + +-- Type inference for generic decorators applied to generic callables +-- ------------------------------------------------------------------ + +[case testInferenceAgainstGenericCallable] +# flags: --new-type-inference +from typing import TypeVar, Callable, List + +X = TypeVar('X') +T = TypeVar('T') + +def foo(x: Callable[[int], X]) -> List[X]: + ... +def bar(x: Callable[[X], int]) -> List[X]: + ... + +def id(x: T) -> T: + ... +reveal_type(foo(id)) # N: Revealed type is "builtins.list[builtins.int]" +reveal_type(bar(id)) # N: Revealed type is "builtins.list[builtins.int]" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericCallableNoLeak] +# flags: --new-type-inference +from typing import TypeVar, Callable + +T = TypeVar('T') + +def f(x: Callable[..., T]) -> T: + return x() + +def tpl(x: T) -> T: + return x + +# This is valid because of "..." +reveal_type(f(tpl)) # N: Revealed type is "Any" +[out] + +[case testInferenceAgainstGenericCallableChain] +# flags: --new-type-inference +from typing import TypeVar, Callable, List + +X = TypeVar('X') +T = TypeVar('T') + +def chain(f: Callable[[X], T], g: Callable[[T], int]) -> Callable[[X], int]: ... +def id(x: T) -> T: + ... +reveal_type(chain(id, id)) # N: Revealed type is "def (builtins.int) -> builtins.int" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericCallableGeneric] +# flags: --new-type-inference +from typing import TypeVar, Callable, List + +S = TypeVar('S') +T = TypeVar('T') +U = TypeVar('U') + +def dec(f: Callable[[S], T]) -> Callable[[S], List[T]]: + ... +def id(x: U) -> U: + ... +reveal_type(dec(id)) # N: Revealed type is "def [S] (S`1) -> builtins.list[S`1]" + +@dec +def same(x: U) -> U: + ... +reveal_type(same) # N: Revealed type is "def [S] (S`3) -> builtins.list[S`3]" +reveal_type(same(42)) # N: Revealed type is "builtins.list[builtins.int]" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericCallableGenericReverse] +# flags: --new-type-inference +from typing import TypeVar, Callable, List + +S = TypeVar('S') +T = TypeVar('T') +U = TypeVar('U') + +def dec(f: Callable[[S], List[T]]) -> Callable[[S], T]: + ... +def id(x: U) -> U: + ... +reveal_type(dec(id)) # N: Revealed type is "def [T] (builtins.list[T`2]) -> T`2" + +@dec +def same(x: U) -> U: + ... +reveal_type(same) # N: Revealed type is "def [T] (builtins.list[T`4]) -> T`4" +reveal_type(same([42])) # N: Revealed type is "builtins.int" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericCallableGenericArg] +# flags: --new-type-inference +from typing import TypeVar, Callable, List + +S = TypeVar('S') +T = TypeVar('T') +U = TypeVar('U') + +def dec(f: Callable[[S], T]) -> Callable[[S], T]: + ... +def test(x: U) -> List[U]: + ... +reveal_type(dec(test)) # N: Revealed type is "def [S] (S`1) -> builtins.list[S`1]" + +@dec +def single(x: U) -> List[U]: + ... +reveal_type(single) # N: Revealed type is "def [S] (S`3) -> builtins.list[S`3]" +reveal_type(single(42)) # N: Revealed type is "builtins.list[builtins.int]" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericCallableGenericChain] +# flags: --new-type-inference +from typing import TypeVar, Callable, List + +S = TypeVar('S') +T = TypeVar('T') +U = TypeVar('U') + +def comb(f: Callable[[T], S], g: Callable[[S], U]) -> Callable[[T], U]: ... +def id(x: U) -> U: + ... +reveal_type(comb(id, id)) # N: Revealed type is "def [T] (T`1) -> T`1" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericCallableGenericNonLinear] +# flags: --new-type-inference +from typing import TypeVar, Callable, List + +S = TypeVar('S') +T = TypeVar('T') +U = TypeVar('U') + +def mix(fs: List[Callable[[S], T]]) -> Callable[[S], List[T]]: + def inner(x: S) -> List[T]: + return [f(x) for f in fs] + return inner + +# Errors caused by arg *name* mismatch are truly cryptic, but this is a known issue :/ +def id(__x: U) -> U: + ... +fs = [id, id, id] +reveal_type(mix(fs)) # N: Revealed type is "def [S] (S`3) -> builtins.list[S`3]" +reveal_type(mix([id, id, id])) # N: Revealed type is "def [S] (S`5) -> builtins.list[S`5]" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericCurry] +# flags: --new-type-inference +from typing import Callable, List, TypeVar + +S = TypeVar("S") +T = TypeVar("T") +U = TypeVar("U") +V = TypeVar("V") + +def dec1(f: Callable[[T], S]) -> Callable[[], Callable[[T], S]]: ... +def dec2(f: Callable[[T, U], S]) -> Callable[[U], Callable[[T], S]]: ... + +def test1(x: V) -> V: ... +def test2(x: V, y: V) -> V: ... + +reveal_type(dec1(test1)) # N: Revealed type is "def () -> def [T] (T`1) -> T`1" +reveal_type(dec2(test2)) # N: Revealed type is "def [T] (T`3) -> def (T`3) -> T`3" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericCallableNewVariable] +# flags: --new-type-inference +from typing import TypeVar, Callable, List + +S = TypeVar('S') +T = TypeVar('T') +U = TypeVar('U') + +def dec(f: Callable[[S], T]) -> Callable[[S], T]: + ... +def test(x: List[U]) -> List[U]: + ... +reveal_type(dec(test)) # N: Revealed type is "def [U] (builtins.list[U`-1]) -> builtins.list[U`-1]" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericCallableGenericAlias] +# flags: --new-type-inference +from typing import TypeVar, Callable, List + +S = TypeVar('S') +T = TypeVar('T') +U = TypeVar('U') + +A = Callable[[S], T] +B = Callable[[S], List[T]] + +def dec(f: A[S, T]) -> B[S, T]: + ... +def id(x: U) -> U: + ... +reveal_type(dec(id)) # N: Revealed type is "def [S] (S`1) -> builtins.list[S`1]" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericCallableGenericProtocol] +# flags: --new-type-inference +from typing import TypeVar, Protocol, Generic, Optional + +T = TypeVar('T') + +class F(Protocol[T]): + def __call__(self, __x: T) -> T: ... + +def lift(f: F[T]) -> F[Optional[T]]: ... +def g(x: T) -> T: + return x + +reveal_type(lift(g)) # N: Revealed type is "def [T] (Union[T`1, None]) -> Union[T`1, None]" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericSplitOrder] +# flags: --new-type-inference +from typing import TypeVar, Callable, List + +S = TypeVar('S') +T = TypeVar('T') +U = TypeVar('U') + +def dec(f: Callable[[T], S], g: Callable[[T], int]) -> Callable[[T], List[S]]: ... +def id(x: U) -> U: + ... + +reveal_type(dec(id, id)) # N: Revealed type is "def (builtins.int) -> builtins.list[builtins.int]" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericSplitOrderGeneric] +# flags: --new-type-inference +from typing import TypeVar, Callable, Tuple + +S = TypeVar('S') +T = TypeVar('T') +U = TypeVar('U') +V = TypeVar('V') + +def dec(f: Callable[[T], S], g: Callable[[T], U]) -> Callable[[T], Tuple[S, U]]: ... +def id(x: V) -> V: + ... + +reveal_type(dec(id, id)) # N: Revealed type is "def [T] (T`1) -> Tuple[T`1, T`1]" +[builtins fixtures/tuple.pyi] + +[case testInferenceAgainstGenericEllipsisSelfSpecialCase] +# flags: --new-type-inference +from typing import Self, Callable, TypeVar + +T = TypeVar("T") +def dec(f: Callable[..., T]) -> Callable[..., T]: ... + +class C: + @dec + def test(self) -> Self: ... + +c: C +reveal_type(c.test()) # N: Revealed type is "__main__.C" + +[case testInferenceAgainstGenericBoundsAndValues] +# flags: --new-type-inference +from typing import TypeVar, Callable, List + +class B: ... +class C(B): ... + +S = TypeVar('S') +T = TypeVar('T') +UB = TypeVar('UB', bound=B) +UC = TypeVar('UC', bound=C) +V = TypeVar('V', int, str) + +def dec1(f: Callable[[S], T]) -> Callable[[S], List[T]]: + ... +def dec2(f: Callable[[UC], T]) -> Callable[[UC], List[T]]: + ... +def id1(x: UB) -> UB: + ... +def id2(x: V) -> V: + ... + +reveal_type(dec1(id1)) # N: Revealed type is "def [S <: __main__.B] (S`1) -> builtins.list[S`1]" +reveal_type(dec1(id2)) # N: Revealed type is "def [S in (builtins.int, builtins.str)] (S`3) -> builtins.list[S`3]" +reveal_type(dec2(id1)) # N: Revealed type is "def [UC <: __main__.C] (UC`5) -> builtins.list[UC`5]" +reveal_type(dec2(id2)) # N: Revealed type is "def (Never) -> builtins.list[Never]" \ + # E: Argument 1 to "dec2" has incompatible type "Callable[[V], V]"; expected "Callable[[Never], Never]" + +[case testInferenceAgainstGenericLambdas] +# flags: --new-type-inference +from typing import TypeVar, Callable, List + +S = TypeVar('S') +T = TypeVar('T') + +def dec1(f: Callable[[T], T]) -> Callable[[T], List[T]]: + ... +def dec2(f: Callable[[S], T]) -> Callable[[S], List[T]]: + ... +def dec3(f: Callable[[List[S]], T]) -> Callable[[S], T]: + def g(x: S) -> T: + return f([x]) + return g +def dec4(f: Callable[[S], List[T]]) -> Callable[[S], T]: + ... +def dec5(f: Callable[[int], T]) -> Callable[[int], List[T]]: + def g(x: int) -> List[T]: + return [f(x)] * x + return g + +I = TypeVar("I", bound=int) +def dec4_bound(f: Callable[[I], List[T]]) -> Callable[[I], T]: + ... + +reveal_type(dec1(lambda x: x)) # N: Revealed type is "def [T] (T`3) -> builtins.list[T`3]" +reveal_type(dec2(lambda x: x)) # N: Revealed type is "def [S] (S`4) -> builtins.list[S`4]" +reveal_type(dec3(lambda x: x[0])) # N: Revealed type is "def [S] (S`6) -> S`6" +reveal_type(dec4(lambda x: [x])) # N: Revealed type is "def [S] (S`9) -> S`9" +reveal_type(dec1(lambda x: 1)) # N: Revealed type is "def (builtins.int) -> builtins.list[builtins.int]" +reveal_type(dec5(lambda x: x)) # N: Revealed type is "def (builtins.int) -> builtins.list[builtins.int]" +reveal_type(dec3(lambda x: x)) # N: Revealed type is "def [S] (S`16) -> builtins.list[S`16]" +reveal_type(dec4(lambda x: x)) # N: Revealed type is "def [T] (builtins.list[T`19]) -> T`19" +dec4_bound(lambda x: x) # E: Value of type variable "I" of "dec4_bound" cannot be "List[T]" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericParamSpecBasicInList] +# flags: --new-type-inference +from typing import TypeVar, Callable, List, Tuple +from typing_extensions import ParamSpec + +T = TypeVar('T') +P = ParamSpec('P') +U = TypeVar('U') +V = TypeVar('V') + +def dec(f: Callable[P, T]) -> Callable[P, List[T]]: ... +def id(x: U) -> U: ... +def either(x: U, y: U) -> U: ... +def pair(x: U, y: V) -> Tuple[U, V]: ... +reveal_type(dec(id)) # N: Revealed type is "def [T] (x: T`3) -> builtins.list[T`3]" +reveal_type(dec(either)) # N: Revealed type is "def [T] (x: T`5, y: T`5) -> builtins.list[T`5]" +reveal_type(dec(pair)) # N: Revealed type is "def [U, V] (x: U`-1, y: V`-2) -> builtins.list[Tuple[U`-1, V`-2]]" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericParamSpecBasicDeList] +# flags: --new-type-inference +from typing import TypeVar, Callable, List, Tuple +from typing_extensions import ParamSpec + +T = TypeVar('T') +P = ParamSpec('P') +U = TypeVar('U') +V = TypeVar('V') + +def dec(f: Callable[P, List[T]]) -> Callable[P, T]: ... +def id(x: U) -> U: ... +def either(x: U, y: U) -> U: ... +reveal_type(dec(id)) # N: Revealed type is "def [T] (x: builtins.list[T`3]) -> T`3" +reveal_type(dec(either)) # N: Revealed type is "def [T] (x: builtins.list[T`5], y: builtins.list[T`5]) -> T`5" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericParamSpecPopOff] +# flags: --new-type-inference +from typing import TypeVar, Callable, List, Tuple +from typing_extensions import ParamSpec, Concatenate + +T = TypeVar('T') +S = TypeVar('S') +P = ParamSpec('P') +U = TypeVar('U') +V = TypeVar('V') + +def dec(f: Callable[Concatenate[T, P], S]) -> Callable[P, Callable[[T], S]]: ... +def id(x: U) -> U: ... +def either(x: U, y: U) -> U: ... +def pair(x: U, y: V) -> Tuple[U, V]: ... +reveal_type(dec(id)) # N: Revealed type is "def () -> def [T] (T`2) -> T`2" +reveal_type(dec(either)) # N: Revealed type is "def [T] (y: T`5) -> def (T`5) -> T`5" +reveal_type(dec(pair)) # N: Revealed type is "def [V] (y: V`-2) -> def [T] (T`8) -> Tuple[T`8, V`-2]" +reveal_type(dec(dec)) # N: Revealed type is "def () -> def [T, P, S] (def (T`-1, *P.args, **P.kwargs) -> S`-3) -> def (*P.args, **P.kwargs) -> def (T`-1) -> S`-3" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericParamSpecPopOn] +# flags: --new-type-inference +from typing import TypeVar, Callable, List, Tuple +from typing_extensions import ParamSpec, Concatenate + +T = TypeVar('T') +S = TypeVar('S') +P = ParamSpec('P') +U = TypeVar('U') +V = TypeVar('V') + +def dec(f: Callable[P, Callable[[T], S]]) -> Callable[Concatenate[T, P], S]: ... +def id() -> Callable[[U], U]: ... +def either(x: U) -> Callable[[U], U]: ... +def pair(x: U) -> Callable[[V], Tuple[V, U]]: ... +reveal_type(dec(id)) # N: Revealed type is "def [T] (T`3) -> T`3" +reveal_type(dec(either)) # N: Revealed type is "def [T] (T`6, x: T`6) -> T`6" +reveal_type(dec(pair)) # N: Revealed type is "def [T, U] (T`9, x: U`-1) -> Tuple[T`9, U`-1]" +# This is counter-intuitive but looks correct, dec matches itself only if P can be empty +reveal_type(dec(dec)) # N: Revealed type is "def [T, S] (T`12, f: def () -> def (T`12) -> S`13) -> S`13" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericParamSpecVsParamSpec] +# flags: --new-type-inference +from typing import TypeVar, Callable, List, Tuple, Generic +from typing_extensions import ParamSpec, Concatenate + +T = TypeVar('T') +P = ParamSpec('P') +Q = ParamSpec('Q') + +class Foo(Generic[P]): ... +class Bar(Generic[P, T]): ... + +def dec(f: Callable[P, T]) -> Callable[P, List[T]]: ... +def f(*args: Q.args, **kwargs: Q.kwargs) -> Foo[Q]: ... +reveal_type(dec(f)) # N: Revealed type is "def [P] (*P.args, **P.kwargs) -> builtins.list[__main__.Foo[P`2]]" +g: Callable[Concatenate[int, Q], Foo[Q]] +reveal_type(dec(g)) # N: Revealed type is "def [Q] (builtins.int, *Q.args, **Q.kwargs) -> builtins.list[__main__.Foo[Q`-1]]" +h: Callable[Concatenate[T, Q], Bar[Q, T]] +reveal_type(dec(h)) # N: Revealed type is "def [T, Q] (T`-1, *Q.args, **Q.kwargs) -> builtins.list[__main__.Bar[Q`-2, T`-1]]" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericParamSpecVsParamSpecConcatenate] +# flags: --new-type-inference +from typing import TypeVar, Callable, List, Tuple, Generic +from typing_extensions import ParamSpec, Concatenate + +T = TypeVar('T') +P = ParamSpec('P') +Q = ParamSpec('Q') + +class Foo(Generic[P]): ... + +def dec(f: Callable[P, int]) -> Callable[P, Foo[P]]: ... +h: Callable[Concatenate[T, Q], int] +g: Callable[Concatenate[T, Q], int] +h = g +reveal_type(dec(h)) # N: Revealed type is "def [T, Q] (T`-1, *Q.args, **Q.kwargs) -> __main__.Foo[[T`-1, **Q`-2]]" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericParamSpecSecondary] +# flags: --new-type-inference +from typing import TypeVar, Callable, List, Tuple, Generic +from typing_extensions import ParamSpec, Concatenate + +T = TypeVar('T') +P = ParamSpec('P') +Q = ParamSpec('Q') + +class Foo(Generic[P]): ... + +def dec(f: Callable[P, Foo[P]]) -> Callable[P, Foo[P]]: ... +g: Callable[[T], Foo[[int]]] +reveal_type(dec(g)) # N: Revealed type is "def (builtins.int) -> __main__.Foo[[builtins.int]]" +h: Callable[Q, Foo[[int]]] +reveal_type(dec(g)) # N: Revealed type is "def (builtins.int) -> __main__.Foo[[builtins.int]]" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericParamSpecSecondOrder] +# flags: --new-type-inference +from typing import TypeVar, Callable +from typing_extensions import ParamSpec, Concatenate + +T = TypeVar('T') +S = TypeVar('S') +P = ParamSpec('P') +Q = ParamSpec('Q') +U = TypeVar('U') +W = ParamSpec('W') + +def transform( + dec: Callable[[Callable[P, T]], Callable[Q, S]] +) -> Callable[[Callable[Concatenate[int, P], T]], Callable[Concatenate[int, Q], S]]: ... + +def dec(f: Callable[W, U]) -> Callable[W, U]: ... +def dec2(f: Callable[Concatenate[str, W], U]) -> Callable[Concatenate[bytes, W], U]: ... +reveal_type(transform(dec)) # N: Revealed type is "def [P, T] (def (builtins.int, *P.args, **P.kwargs) -> T`3) -> def (builtins.int, *P.args, **P.kwargs) -> T`3" +reveal_type(transform(dec2)) # N: Revealed type is "def [W, T] (def (builtins.int, builtins.str, *W.args, **W.kwargs) -> T`7) -> def (builtins.int, builtins.bytes, *W.args, **W.kwargs) -> T`7" +[builtins fixtures/tuple.pyi] + +[case testNoAccidentalVariableClashInNestedGeneric] +# flags: --new-type-inference +from typing import TypeVar, Callable, Generic, Tuple + +T = TypeVar('T') +S = TypeVar('S') +U = TypeVar('U') + +def pipe(x: T, f1: Callable[[T], S], f2: Callable[[S], U]) -> U: ... +def and_then(a: T) -> Callable[[S], Tuple[S, T]]: ... + +def apply(a: S, b: T) -> None: + v1 = and_then(b) + v2: Callable[[Tuple[S, T]], None] + return pipe(a, v1, v2) +[builtins fixtures/tuple.pyi] + +[case testInferenceAgainstGenericParamSpecSpuriousBoundsNotUsed] +# flags: --new-type-inference +from typing import TypeVar, Callable, Generic +from typing_extensions import ParamSpec, Concatenate + +Q = ParamSpec("Q") +class Foo(Generic[Q]): ... + +T1 = TypeVar("T1", bound=Foo[...]) +T2 = TypeVar("T2", bound=Foo[...]) +P = ParamSpec("P") +def pop_off(fn: Callable[Concatenate[T1, P], T2]) -> Callable[P, Callable[[T1], T2]]: + ... + +@pop_off +def test(command: Foo[Q]) -> Foo[Q]: ... +reveal_type(test) # N: Revealed type is "def () -> def [Q] (__main__.Foo[Q`-1]) -> __main__.Foo[Q`-1]" +[builtins fixtures/tuple.pyi] + +[case testInferenceAgainstGenericVariadicBasicInList] +# flags: --new-type-inference +from typing import Tuple, TypeVar, List, Callable +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +Ts = TypeVarTuple("Ts") +def dec(f: Callable[[Unpack[Ts]], T]) -> Callable[[Unpack[Ts]], List[T]]: ... + +U = TypeVar("U") +V = TypeVar("V") +def id(x: U) -> U: ... +def either(x: U, y: U) -> U: ... +def pair(x: U, y: V) -> Tuple[U, V]: ... + +reveal_type(dec(id)) # N: Revealed type is "def [T] (T`3) -> builtins.list[T`3]" +reveal_type(dec(either)) # N: Revealed type is "def [T] (T`5, T`5) -> builtins.list[T`5]" +reveal_type(dec(pair)) # N: Revealed type is "def [U, V] (U`-1, V`-2) -> builtins.list[Tuple[U`-1, V`-2]]" +[builtins fixtures/tuple.pyi] + +[case testInferenceAgainstGenericVariadicBasicDeList] +# flags: --new-type-inference +from typing import Tuple, TypeVar, List, Callable +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +Ts = TypeVarTuple("Ts") +def dec(f: Callable[[Unpack[Ts]], List[T]]) -> Callable[[Unpack[Ts]], T]: ... + +U = TypeVar("U") +V = TypeVar("V") +def id(x: U) -> U: ... +def either(x: U, y: U) -> U: ... + +reveal_type(dec(id)) # N: Revealed type is "def [T] (builtins.list[T`3]) -> T`3" +reveal_type(dec(either)) # N: Revealed type is "def [T] (builtins.list[T`5], builtins.list[T`5]) -> T`5" +[builtins fixtures/tuple.pyi] + +[case testInferenceAgainstGenericVariadicPopOff] +# flags: --new-type-inference +from typing import TypeVar, Callable, List, Tuple +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") +def dec(f: Callable[[T, Unpack[Ts]], S]) -> Callable[[Unpack[Ts]], Callable[[T], S]]: ... + +U = TypeVar("U") +V = TypeVar("V") +def id(x: U) -> U: ... +def either(x: U, y: U) -> U: ... +def pair(x: U, y: V) -> Tuple[U, V]: ... + +reveal_type(dec(id)) # N: Revealed type is "def () -> def [T] (T`2) -> T`2" +reveal_type(dec(either)) # N: Revealed type is "def [T] (T`5) -> def (T`5) -> T`5" +reveal_type(dec(pair)) # N: Revealed type is "def [V] (V`-2) -> def [T] (T`8) -> Tuple[T`8, V`-2]" +reveal_type(dec(dec)) # N: Revealed type is "def () -> def [T, Ts, S] (def (T`-1, *Unpack[Ts`-2]) -> S`-3) -> def (*Unpack[Ts`-2]) -> def (T`-1) -> S`-3" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericVariadicPopOn] +# flags: --new-type-inference +from typing import TypeVar, Callable, List, Tuple +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") +def dec(f: Callable[[Unpack[Ts]], Callable[[T], S]]) -> Callable[[T, Unpack[Ts]], S]: ... + +U = TypeVar("U") +V = TypeVar("V") +def id() -> Callable[[U], U]: ... +def either(x: U) -> Callable[[U], U]: ... +def pair(x: U) -> Callable[[V], Tuple[V, U]]: ... + +reveal_type(dec(id)) # N: Revealed type is "def [T] (T`3) -> T`3" +reveal_type(dec(either)) # N: Revealed type is "def [T] (T`6, T`6) -> T`6" +reveal_type(dec(pair)) # N: Revealed type is "def [T, U] (T`9, U`-1) -> Tuple[T`9, U`-1]" +# This is counter-intuitive but looks correct, dec matches itself only if Ts is empty +reveal_type(dec(dec)) # N: Revealed type is "def [T, S] (T`12, def () -> def (T`12) -> S`13) -> S`13" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericVariadicVsVariadic] +# flags: --new-type-inference +from typing import TypeVar, Callable, List, Generic +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") +Us = TypeVarTuple("Us") + +class Foo(Generic[Unpack[Ts]]): ... +class Bar(Generic[Unpack[Ts], T]): ... + +def dec(f: Callable[[Unpack[Ts]], T]) -> Callable[[Unpack[Ts]], List[T]]: ... +def f(*args: Unpack[Us]) -> Foo[Unpack[Us]]: ... +reveal_type(dec(f)) # N: Revealed type is "def [Ts] (*Unpack[Ts`2]) -> builtins.list[__main__.Foo[Unpack[Ts`2]]]" +g: Callable[[Unpack[Us]], Foo[Unpack[Us]]] +reveal_type(dec(g)) # N: Revealed type is "def [Ts] (*Unpack[Ts`4]) -> builtins.list[__main__.Foo[Unpack[Ts`4]]]" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericVariadicVsVariadicConcatenate] +# flags: --new-type-inference +from typing import TypeVar, Callable, Generic +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") +Us = TypeVarTuple("Us") + +class Foo(Generic[Unpack[Ts]]): ... + +def dec(f: Callable[[Unpack[Ts]], int]) -> Callable[[Unpack[Ts]], Foo[Unpack[Ts]]]: ... +h: Callable[[T, Unpack[Us]], int] +g: Callable[[T, Unpack[Us]], int] +h = g +reveal_type(dec(h)) # N: Revealed type is "def [T, Us] (T`-1, *Unpack[Us`-2]) -> __main__.Foo[T`-1, Unpack[Us`-2]]" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericVariadicSecondary] +# flags: --new-type-inference +from typing import TypeVar, Callable, Generic +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +Ts = TypeVarTuple("Ts") +Us = TypeVarTuple("Us") + +class Foo(Generic[Unpack[Ts]]): ... + +def dec(f: Callable[[Unpack[Ts]], Foo[Unpack[Ts]]]) -> Callable[[Unpack[Ts]], Foo[Unpack[Ts]]]: ... +g: Callable[[T], Foo[int]] +reveal_type(dec(g)) # N: Revealed type is "def (builtins.int) -> __main__.Foo[builtins.int]" +h: Callable[[Unpack[Us]], Foo[int]] +reveal_type(dec(h)) # N: Revealed type is "def (builtins.int) -> __main__.Foo[builtins.int]" +[builtins fixtures/list.pyi] diff --git a/test-data/unit/check-ignore.test b/test-data/unit/check-ignore.test index 982fb67f4e7f..fa451f373e70 100644 --- a/test-data/unit/check-ignore.test +++ b/test-data/unit/check-ignore.test @@ -220,29 +220,24 @@ def f() -> None: pass yield # type: ignore # E: "yield" outside function [case testIgnoreWholeModule1] -# flags: --warn-unused-ignores -# type: ignore -IGNORE # type: ignore # E: Unused "type: ignore" comment - -[case testIgnoreWholeModule2] # type: ignore if True: IGNORE -[case testIgnoreWholeModule3] +[case testIgnoreWholeModule2] # type: ignore @d class C: ... IGNORE -[case testIgnoreWholeModule4] +[case testIgnoreWholeModule3] # type: ignore @d def f(): ... IGNORE -[case testIgnoreWholeModule5] +[case testIgnoreWholeModule4] # type: ignore import MISSING @@ -279,3 +274,19 @@ class CD(six.with_metaclass(M)): # E: Multiple metaclass definitions 42 + 'no way' # type: ignore [builtins fixtures/tuple.pyi] + +[case testUnusedIgnoreTryExcept] +# flags: --warn-unused-ignores +try: + import foo # type: ignore # E: Unused "type: ignore" comment + import bar # type: ignore[import] # E: Unused "type: ignore" comment + import foobar # type: ignore[unused-ignore] + import barfoo # type: ignore[import,unused-ignore] + import missing # type: ignore[import,unused-ignore] +except Exception: + pass +[file foo.py] +[file bar.py] +[file foobar.py] +[file barfoo.py] +[builtins fixtures/exception.pyi] diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index ec0c5d5e4805..a7f4fafc579e 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -1734,12 +1734,12 @@ class R: pass [file r/s.py] from . import m R = m.R -a = None # type: R +a: R [file r/s.py.2] from . import m R = m.R -a = None # type: R +a: R [case testIncrementalBaseClassAttributeConflict] class A: pass @@ -2603,7 +2603,7 @@ def output() -> IntNode[str]: return Node(1, 'x') x = output() # type: IntNode -y = None # type: IntNode +y: IntNode y.x = 1 y.y = 1 y.y = 'x' @@ -2625,7 +2625,7 @@ def output() -> IntNode[str]: return Node(1, 'x') x = output() # type: IntNode -y = None # type: IntNode +y: IntNode y.x = 1 y.y = 1 y.y = 'x' @@ -2901,7 +2901,6 @@ tmp/main.py:2: error: Expression has type "Any" tmp/main.py:2: error: Expression has type "Any" [case testIncrementalStrictOptional] -# flags: --strict-optional import a 1 + a.foo() [file a.py] @@ -2911,13 +2910,13 @@ from typing import Optional def foo() -> Optional[int]: return 0 [out1] [out2] -main:3: error: Unsupported operand types for + ("int" and "None") -main:3: note: Right operand is of type "Optional[int]" +main:2: error: Unsupported operand types for + ("int" and "None") +main:2: note: Right operand is of type "Optional[int]" [case testAttrsIncrementalSubclassingCached] from a import A -import attr -@attr.s(auto_attribs=True) +import attrs +@attrs.define class B(A): e: str = 'e' a = B(5, [5], 'foo') @@ -2928,15 +2927,14 @@ a._d = 22 a.e = 'hi' [file a.py] -import attr -import attr +import attrs from typing import List, ClassVar -@attr.s(auto_attribs=True) +@attrs.define class A: a: int _b: List[int] c: str = '18' - _d: int = attr.ib(validator=None, default=18) + _d: int = attrs.field(validator=None, default=18) E = 7 F: ClassVar[int] = 22 @@ -2946,8 +2944,8 @@ class A: [case testAttrsIncrementalSubclassingCachedConverter] from a import A -import attr -@attr.s +import attrs +@attrs.define class B(A): pass reveal_type(B) @@ -2956,10 +2954,10 @@ reveal_type(B) def converter(s:int) -> str: return 'hello' -import attr -@attr.s +import attrs +@attrs.define class A: - x: str = attr.ib(converter=converter) + x: str = attrs.field(converter=converter) [builtins fixtures/list.pyi] [out1] @@ -2970,17 +2968,17 @@ main:6: note: Revealed type is "def (x: builtins.int) -> __main__.B" [case testAttrsIncrementalSubclassingCachedType] from a import A -import attr -@attr.s +import attrs +@attrs.define class B(A): pass reveal_type(B) [file a.py] -import attr -@attr.s +import attrs +@attrs.define class A: - x = attr.ib(type=int) + x: int [builtins fixtures/list.pyi] [out1] @@ -3006,18 +3004,18 @@ NoCmp(1) > NoCmp(2) NoCmp(1) >= NoCmp(2) [file a.py] -import attr -@attr.s(frozen=True) +import attrs +@attrs.frozen class Frozen: - x: int = attr.ib() -@attr.s(init=False) + x: int +@attrs.define(init=False) class NoInit: - x: int = attr.ib() -@attr.s(eq=False) + x: int +@attrs.define(eq=False) class NoCmp: - x: int = attr.ib() + x: int -[builtins fixtures/list.pyi] +[builtins fixtures/plugin_attrs.pyi] [rechecked] [stale] [out1] @@ -3037,10 +3035,10 @@ main:15: error: Unsupported left operand type for >= ("NoCmp") [case testAttrsIncrementalDunder] from a import A reveal_type(A) # N: Revealed type is "def (a: builtins.int) -> a.A" -reveal_type(A.__lt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" -reveal_type(A.__le__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" -reveal_type(A.__gt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" -reveal_type(A.__ge__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" +reveal_type(A.__lt__) # N: Revealed type is "def [_AT] (self: _AT`3, other: _AT`3) -> builtins.bool" +reveal_type(A.__le__) # N: Revealed type is "def [_AT] (self: _AT`4, other: _AT`4) -> builtins.bool" +reveal_type(A.__gt__) # N: Revealed type is "def [_AT] (self: _AT`5, other: _AT`5) -> builtins.bool" +reveal_type(A.__ge__) # N: Revealed type is "def [_AT] (self: _AT`6, other: _AT`6) -> builtins.bool" A(1) < A(2) A(1) <= A(2) @@ -3069,15 +3067,15 @@ from attr import attrib, attrs class A: a: int -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [rechecked] [stale] [out2] main:2: note: Revealed type is "def (a: builtins.int) -> a.A" -main:3: note: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" -main:4: note: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" -main:5: note: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" -main:6: note: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" +main:3: note: Revealed type is "def [_AT] (self: _AT`1, other: _AT`1) -> builtins.bool" +main:4: note: Revealed type is "def [_AT] (self: _AT`2, other: _AT`2) -> builtins.bool" +main:5: note: Revealed type is "def [_AT] (self: _AT`3, other: _AT`3) -> builtins.bool" +main:6: note: Revealed type is "def [_AT] (self: _AT`4, other: _AT`4) -> builtins.bool" main:15: error: Unsupported operand types for < ("A" and "int") main:16: error: Unsupported operand types for <= ("A" and "int") main:17: error: Unsupported operand types for > ("A" and "int") @@ -3092,22 +3090,22 @@ from b import B B(5, 'foo') [file a.py] -import attr -@attr.s(auto_attribs=True) +import attrs +@attrs.define class A: x: int [file b.py] -import attr +import attrs from a import A -@attr.s(auto_attribs=True) +@attrs.define class B(A): y: str [file b.py.2] -import attr +import attrs from a import A -@attr.s(auto_attribs=True) +@attrs.define class B(A): y: int @@ -3121,22 +3119,22 @@ main:2: error: Argument 2 to "B" has incompatible type "str"; expected "int" from b import B B(5, 'foo') [file a.py] -import attr -@attr.s(auto_attribs=True) +import attrs +@attrs.define class A: x: int [file b.py] -import attr +import attrs from a import A -@attr.s(auto_attribs=True) +@attrs.define class B(A): y: int [file b.py.2] -import attr +import attrs from a import A -@attr.s(auto_attribs=True) +@attrs.define class B(A): y: str @@ -3152,24 +3150,24 @@ from c import C C(5, 'foo', True) [file a.py] -import attr -@attr.s +import attrs +@attrs.define class A: - a: int = attr.ib() + a: int [file b.py] -import attr -@attr.s +import attrs +@attrs.define class B: - b: str = attr.ib() + b: str [file c.py] from a import A from b import B -import attr -@attr.s +import attrs +@attrs.define class C(A, B): - c: bool = attr.ib() + c: bool [builtins fixtures/list.pyi] [out1] @@ -3184,10 +3182,10 @@ from typing import Optional def converter(s:Optional[int]) -> int: ... -import attr -@attr.s +import attrs +@attrs.define class A: - x: int = attr.ib(converter=converter) + x: int = attrs.field(converter=converter) [builtins fixtures/list.pyi] [out1] @@ -3254,80 +3252,80 @@ def maybe_bool(x: Optional[bool]) -> bool: ... [file base.py] from typing import Optional -import attr +import attrs import bar from foo import maybe_int def maybe_str(x: Optional[str]) -> str: ... -@attr.s +@attrs.define class Base: - x: int = attr.ib(converter=maybe_int) - y: str = attr.ib(converter=maybe_str) - z: bool = attr.ib(converter=bar.maybe_bool) + x: int = attrs.field(converter=maybe_int) + y: str = attrs.field(converter=maybe_str) + z: bool = attrs.field(converter=bar.maybe_bool) [file subclass.py] from typing import Optional -import attr +import attrs from base import Base -@attr.s +@attrs.define class A(Base): pass import bar from foo import maybe_int def maybe_str(x: Optional[str]) -> str: ... -@attr.s +@attrs.define class B(Base): - xx: int = attr.ib(converter=maybe_int) - yy: str = attr.ib(converter=maybe_str) - zz: bool = attr.ib(converter=bar.maybe_bool) + xx: int = attrs.field(converter=maybe_int) + yy: str = attrs.field(converter=maybe_str) + zz: bool = attrs.field(converter=bar.maybe_bool) [file submodule/__init__.py] [file submodule/base.py] from typing import Optional -import attr +import attrs import bar from foo import maybe_int def maybe_str(x: Optional[str]) -> str: ... -@attr.s +@attrs.define class SubBase: - x: int = attr.ib(converter=maybe_int) - y: str = attr.ib(converter=maybe_str) - z: bool = attr.ib(converter=bar.maybe_bool) + x: int = attrs.field(converter=maybe_int) + y: str = attrs.field(converter=maybe_str) + z: bool = attrs.field(converter=bar.maybe_bool) [file submodule/subclass.py] from typing import Optional -import attr +import attrs from base import Base -@attr.s +@attrs.define class AA(Base): pass import bar from foo import maybe_int def maybe_str(x: Optional[str]) -> str: ... -@attr.s +@attrs.define class BB(Base): - xx: int = attr.ib(converter=maybe_int) - yy: str = attr.ib(converter=maybe_str) - zz: bool = attr.ib(converter=bar.maybe_bool) + xx: int = attrs.field(converter=maybe_int) + yy: str = attrs.field(converter=maybe_str) + zz: bool = attrs.field(converter=bar.maybe_bool) [file submodule/subsubclass.py] from typing import Optional -import attr +import attrs from .base import SubBase -@attr.s +@attrs.define class SubAA(SubBase): pass import bar from foo import maybe_int def maybe_str(x: Optional[str]) -> str: ... -@attr.s +@attrs.define class SubBB(SubBase): - xx: int = attr.ib(converter=maybe_int) - yy: str = attr.ib(converter=maybe_str) - zz: bool = attr.ib(converter=bar.maybe_bool) + xx: int = attrs.field(converter=maybe_int) + yy: str = attrs.field(converter=maybe_str) + zz: bool = attrs.field(converter=bar.maybe_bool) [builtins fixtures/list.pyi] [out1] [out2] @@ -3341,13 +3339,13 @@ tmp/a.py:17: error: Argument 2 to "SubAA" has incompatible type "int"; expected tmp/a.py:18: error: Argument 5 to "SubBB" has incompatible type "int"; expected "Optional[str]" [case testAttrsIncrementalConverterInFunction] -import attr +import attrs def foo() -> None: def foo(x: str) -> int: ... - @attr.s + @attrs.define class A: - x: int = attr.ib(converter=foo) + x: int = attrs.field(converter=foo) reveal_type(A) [builtins fixtures/list.pyi] [out1] @@ -3366,10 +3364,10 @@ from typing import List def converter(s:F) -> int: ... -import attr -@attr.s +import attrs +@attrs.define class A: - x: int = attr.ib(converter=converter) + x: int = attrs.field(converter=converter) F = List[int] @@ -3383,18 +3381,18 @@ main:3: note: Revealed type is "def (x: builtins.list[builtins.int]) -> a.a.A" [case testAttrsIncrementalConverterType-skip] from a import C -import attr +import attrs o = C("1", "2", "3", "4") o = C(1, 2, "3", 4) reveal_type(C) -@attr.s +@attrs.define class D(C): - x: str = attr.ib() + x: str reveal_type(D) [file a.py] from typing import overload -import attr -@attr.dataclass +import attrs +@attrs.define class A: x: str @overload @@ -3404,13 +3402,13 @@ def parse(x: int) -> int: def parse(x: str, y: str = '') -> int: ... def parse(x, y): ... -@attr.s +@attrs.define class C: - a: complex = attr.ib(converter=complex) - b: int = attr.ib(converter=int) - c: A = attr.ib(converter=A) - d: int = attr.ib(converter=parse) -[builtins fixtures/attr.pyi] + a: complex = attrs.field(converter=complex) + b: int = attrs.field(converter=int) + c: A = attrs.field(converter=A) + d: int = attrs.field(converter=parse) +[builtins fixtures/plugin_attrs.pyi] [out1] main:6: note: Revealed type is "def (a: Union[builtins.float, builtins.str], b: Union[builtins.str, builtins.bytes, builtins.int], c: builtins.str, d: Union[builtins.int, builtins.str]) -> a.C" main:10: note: Revealed type is "def (a: Union[builtins.float, builtins.str], b: Union[builtins.str, builtins.bytes, builtins.int], c: builtins.str, d: Union[builtins.int, builtins.str], x: builtins.str) -> __main__.D" @@ -3423,20 +3421,20 @@ from a import A A(5) [file a.py] -import attr -@attr.s(auto_attribs=True) +import attrs +@attrs.define class A: a: int [file a.py.2] -import attr -@attr.s(auto_attribs=True) +import attrs +@attrs.define class A: a: str [file a.py.3] -import attr -@attr.s(auto_attribs=True) +import attrs +@attrs.define class A: a: int = 6 @@ -3458,7 +3456,6 @@ main:2: error: Cannot find implementation or library stub for module named "a" main:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports [case testIncrementalInheritanceAddAnnotation] -# flags: --strict-optional import a [file a.py] import b @@ -3699,8 +3696,8 @@ cache_fine_grained = False [file mypy.ini.2] \[mypy] cache_fine_grained = True -[rechecked a, builtins, typing] -[stale a, builtins, typing] +[rechecked _typeshed, a, builtins, typing] +[stale _typeshed, a, builtins, typing] [builtins fixtures/tuple.pyi] [case testIncrementalPackageNameOverload] @@ -3741,7 +3738,7 @@ import b [file b.py] -- This is a heinous hack, but we simulate having a invalid cache by clobbering -- the proto deps file with something with mtime mismatches. -[file ../.mypy_cache/3.7/@deps.meta.json.2] +[file ../.mypy_cache/3.8/@deps.meta.json.2] {"snapshot": {"__main__": "a7c958b001a45bd6a2a320f4e53c4c16", "a": "d41d8cd98f00b204e9800998ecf8427e", "b": "d41d8cd98f00b204e9800998ecf8427e", "builtins": "c532c89da517a4b779bcf7a964478d67"}, "deps_meta": {"@root": {"path": "@root.deps.json", "mtime": 0}, "__main__": {"path": "__main__.deps.json", "mtime": 0}, "a": {"path": "a.deps.json", "mtime": 0}, "b": {"path": "b.deps.json", "mtime": 0}, "builtins": {"path": "builtins.deps.json", "mtime": 0}}} [file ../.mypy_cache/.gitignore] # Another hack to not trigger a .gitignore creation failure "false positive" @@ -3751,8 +3748,8 @@ Signature: 8a477f597d28d172789f06886806bc55 [file b.py.2] # uh -- Every file should get reloaded, since the cache was invalidated -[stale a, b, builtins, typing] -[rechecked a, b, builtins, typing] +[stale _typeshed, a, b, builtins, typing] +[rechecked _typeshed, a, b, builtins, typing] [builtins fixtures/tuple.pyi] [case testIncrementalBustedFineGrainedCache2] @@ -3764,8 +3761,8 @@ import b [file b.py.2] # uh -- Every file should get reloaded, since the settings changed -[stale a, b, builtins, typing] -[rechecked a, b, builtins, typing] +[stale _typeshed, a, b, builtins, typing] +[rechecked _typeshed, a, b, builtins, typing] [builtins fixtures/tuple.pyi] [case testIncrementalBustedFineGrainedCache3] @@ -3776,12 +3773,12 @@ import b [file b.py] -- This is a heinous hack, but we simulate having a invalid cache by deleting -- the proto deps file. -[delete ../.mypy_cache/3.7/@deps.meta.json.2] +[delete ../.mypy_cache/3.8/@deps.meta.json.2] [file b.py.2] # uh -- Every file should get reloaded, since the cache was invalidated -[stale a, b, builtins, typing] -[rechecked a, b, builtins, typing] +[stale _typeshed, a, b, builtins, typing] +[rechecked _typeshed, a, b, builtins, typing] [builtins fixtures/tuple.pyi] [case testIncrementalWorkingFineGrainedCache] @@ -3799,7 +3796,6 @@ import b [rechecked b] [case testIncrementalDataclassesSubclassingCached] -# flags: --python-version 3.7 from a import A from dataclasses import dataclass @@ -3832,7 +3828,6 @@ class A: [out2] [case testIncrementalDataclassesSubclassingCachedType] -# flags: --python-version 3.7 import b [file b.py] @@ -3866,7 +3861,6 @@ class A: tmp/b.py:8: note: Revealed type is "def (x: builtins.int) -> b.B" [case testIncrementalDataclassesArguments] -# flags: --python-version 3.7 import b [file b.py] @@ -3915,7 +3909,6 @@ tmp/b.py:15: error: Unsupported left operand type for > ("NoCmp") tmp/b.py:16: error: Unsupported left operand type for >= ("NoCmp") [case testIncrementalDataclassesDunder] -# flags: --python-version 3.7 import b [file b.py] @@ -3966,10 +3959,10 @@ class A: tmp/b.py:3: note: Revealed type is "def (a: builtins.int) -> a.A" tmp/b.py:4: note: Revealed type is "def (builtins.object, builtins.object) -> builtins.bool" tmp/b.py:5: note: Revealed type is "def (builtins.object, builtins.object) -> builtins.bool" -tmp/b.py:6: note: Revealed type is "def [_DT] (self: _DT`-1, other: _DT`-1) -> builtins.bool" -tmp/b.py:7: note: Revealed type is "def [_DT] (self: _DT`-1, other: _DT`-1) -> builtins.bool" -tmp/b.py:8: note: Revealed type is "def [_DT] (self: _DT`-1, other: _DT`-1) -> builtins.bool" -tmp/b.py:9: note: Revealed type is "def [_DT] (self: _DT`-1, other: _DT`-1) -> builtins.bool" +tmp/b.py:6: note: Revealed type is "def [_DT] (self: _DT`1, other: _DT`1) -> builtins.bool" +tmp/b.py:7: note: Revealed type is "def [_DT] (self: _DT`2, other: _DT`2) -> builtins.bool" +tmp/b.py:8: note: Revealed type is "def [_DT] (self: _DT`3, other: _DT`3) -> builtins.bool" +tmp/b.py:9: note: Revealed type is "def [_DT] (self: _DT`4, other: _DT`4) -> builtins.bool" tmp/b.py:18: error: Unsupported operand types for < ("A" and "int") tmp/b.py:19: error: Unsupported operand types for <= ("A" and "int") tmp/b.py:20: error: Unsupported operand types for > ("A" and "int") @@ -3980,7 +3973,6 @@ tmp/b.py:27: error: Unsupported operand types for < ("A" and "int") tmp/b.py:28: error: Unsupported operand types for <= ("A" and "int") [case testIncrementalDataclassesSubclassModified] -# flags: --python-version 3.7 from b import B B(5, 'foo') @@ -4010,11 +4002,10 @@ class B(A): [builtins fixtures/dataclasses.pyi] [out1] [out2] -main:3: error: Argument 2 to "B" has incompatible type "str"; expected "int" +main:2: error: Argument 2 to "B" has incompatible type "str"; expected "int" [rechecked b] [case testIncrementalDataclassesSubclassModifiedErrorFirst] -# flags: --python-version 3.7 from b import B B(5, 'foo') @@ -4043,13 +4034,12 @@ class B(A): [builtins fixtures/dataclasses.pyi] [out1] -main:3: error: Argument 2 to "B" has incompatible type "str"; expected "int" +main:2: error: Argument 2 to "B" has incompatible type "str"; expected "int" [out2] [rechecked b] [case testIncrementalDataclassesThreeFiles] -# flags: --python-version 3.7 from c import C C('foo', 5, True) @@ -4088,10 +4078,9 @@ class C(A, B): [out1] [out2] tmp/c.py:7: error: Incompatible types in assignment (expression has type "bool", base class "B" defined the type as "str") -main:3: error: Argument 2 to "C" has incompatible type "int"; expected "bool" +main:2: error: Argument 2 to "C" has incompatible type "int"; expected "bool" [case testIncrementalDataclassesThreeRuns] -# flags: --python-version 3.7 from a import A A(5) @@ -4119,7 +4108,7 @@ class A: [builtins fixtures/dataclasses.pyi] [out1] [out2] -main:3: error: Argument 1 to "A" has incompatible type "int"; expected "str" +main:2: error: Argument 1 to "A" has incompatible type "int"; expected "str" [out3] [case testParentPatchingMess] @@ -4597,7 +4586,6 @@ def outer() -> None: [out2] [case testRecursiveAliasImported] -# flags: --disable-recursive-aliases import a [file a.py] @@ -4623,16 +4611,10 @@ B = List[A] [builtins fixtures/list.pyi] [out] -tmp/lib.pyi:4: error: Module "other" has no attribute "B" -tmp/other.pyi:3: error: Cannot resolve name "B" (possible cyclic definition) [out2] -tmp/lib.pyi:4: error: Module "other" has no attribute "B" -tmp/other.pyi:3: error: Cannot resolve name "B" (possible cyclic definition) -tmp/a.py:3: note: Revealed type is "builtins.list[Any]" - -[case testRecursiveNamedTupleTypedDict-skip] -# https://github.com/python/mypy/issues/7125 +tmp/a.py:3: note: Revealed type is "builtins.list[builtins.list[...]]" +[case testRecursiveNamedTupleTypedDict] import a [file a.py] import lib @@ -4644,7 +4626,7 @@ reveal_type(x.x['x']) [file lib.pyi] from typing import NamedTuple from other import B -A = NamedTuple('A', [('x', B)]) # type: ignore +A = NamedTuple('A', [('x', B)]) [file other.pyi] from mypy_extensions import TypedDict from lib import A @@ -4652,7 +4634,7 @@ B = TypedDict('B', {'x': A}) [builtins fixtures/dict.pyi] [out] [out2] -tmp/a.py:3: note: Revealed type is "Tuple[TypedDict('other.B', {'x': Any}), fallback=lib.A]" +tmp/a.py:3: note: Revealed type is "Tuple[TypedDict('other.B', {'x': Tuple[..., fallback=lib.A]}), fallback=lib.A]" [case testFollowImportSkipNotInvalidatedOnPresent] # flags: --follow-imports=skip @@ -5145,7 +5127,6 @@ tmp/b.py:4: error: First argument to namedtuple() should be "NT", not "BadName" tmp/b.py:4: error: First argument to namedtuple() should be "NT", not "BadName" [case testNewAnalyzerIncrementalMethodNamedTuple] - import a [file a.py] from b import C @@ -5414,7 +5395,8 @@ reveal_type(z) [out] tmp/c.py:2: note: Revealed type is "a." [out2] -tmp/c.py:2: note: Revealed type is "a.A" +tmp/b.py:2: error: Cannot determine type of "y" +tmp/c.py:2: note: Revealed type is "Any" [case testIsInstanceAdHocIntersectionIncrementalUnreachaableToIntersection] import c @@ -5445,7 +5427,8 @@ from b import z reveal_type(z) [builtins fixtures/isinstance.pyi] [out] -tmp/c.py:2: note: Revealed type is "a.A" +tmp/b.py:2: error: Cannot determine type of "y" +tmp/c.py:2: note: Revealed type is "Any" [out2] tmp/c.py:2: note: Revealed type is "a." @@ -5507,7 +5490,6 @@ class Foo: class C: pass [case testIncrementalNestedNamedTuple] -# flags: --python-version 3.6 import a [file a.py] @@ -5674,7 +5656,6 @@ A().g() [out2] [case testIncrementalWithDifferentKindsOfNestedTypesWithinMethod] -# flags: --python-version 3.7 import a @@ -5757,7 +5738,6 @@ class C: [builtins fixtures/tuple.pyi] [case testNamedTupleUpdateNonRecursiveToRecursiveCoarse] -# flags: --strict-optional import c [file a.py] from b import M @@ -5800,7 +5780,6 @@ tmp/c.py:5: error: Incompatible types in assignment (expression has type "Option tmp/c.py:7: note: Revealed type is "Tuple[Union[Tuple[Union[..., None], builtins.int, fallback=b.M], None], builtins.int, fallback=a.N]" [case testTupleTypeUpdateNonRecursiveToRecursiveCoarse] -# flags: --strict-optional import c [file a.py] from b import M @@ -5833,7 +5812,6 @@ tmp/c.py:4: note: Revealed type is "Tuple[Union[Tuple[Union[..., None], builtins tmp/c.py:5: error: Incompatible types in assignment (expression has type "Optional[N]", variable has type "int") [case testTypeAliasUpdateNonRecursiveToRecursiveCoarse] -# flags: --strict-optional import c [file a.py] from b import M @@ -5866,7 +5844,6 @@ tmp/c.py:4: note: Revealed type is "Tuple[Union[Tuple[Union[..., None], builtins tmp/c.py:5: error: Incompatible types in assignment (expression has type "Optional[N]", variable has type "int") [case testTypedDictUpdateNonRecursiveToRecursiveCoarse] -# flags: --strict-optional import c [file a.py] from b import M @@ -5941,6 +5918,44 @@ tmp/b.py:4: note: Revealed type is "def ()" tmp/b.py:5: note: Revealed type is "def (builtins.int) -> builtins.str" tmp/b.py:6: note: Revealed type is "def ()" tmp/b.py:7: note: Revealed type is "def (builtins.int) -> builtins.str" + +[case testIncrementalAddOverloadedMethodPlugin] +# flags: --config-file tmp/mypy.ini +import b + +[file mypy.ini] +\[mypy] +plugins=/test-data/unit/plugins/add_overloaded_method.py + +[file a.py] +class AddOverloadedMethod: pass + +class MyClass(AddOverloadedMethod): + pass + +[file b.py] +import a + +[file b.py.2] +import a + +reveal_type(a.MyClass.method) +reveal_type(a.MyClass.clsmethod) +reveal_type(a.MyClass.stmethod) + +my_class = a.MyClass() +reveal_type(my_class.method) +reveal_type(my_class.clsmethod) +reveal_type(my_class.stmethod) +[rechecked b] +[out2] +tmp/b.py:3: note: Revealed type is "Overload(def (self: a.MyClass, arg: builtins.int) -> builtins.str, def (self: a.MyClass, arg: builtins.str) -> builtins.int)" +tmp/b.py:4: note: Revealed type is "Overload(def (arg: builtins.int) -> builtins.str, def (arg: builtins.str) -> builtins.int)" +tmp/b.py:5: note: Revealed type is "Overload(def (arg: builtins.int) -> builtins.str, def (arg: builtins.str) -> builtins.int)" +tmp/b.py:8: note: Revealed type is "Overload(def (arg: builtins.int) -> builtins.str, def (arg: builtins.str) -> builtins.int)" +tmp/b.py:9: note: Revealed type is "Overload(def (arg: builtins.int) -> builtins.str, def (arg: builtins.str) -> builtins.int)" +tmp/b.py:10: note: Revealed type is "Overload(def (arg: builtins.int) -> builtins.str, def (arg: builtins.str) -> builtins.int)" + [case testGenericNamedTupleSerialization] import b [file a.py] @@ -6061,7 +6076,6 @@ tmp/m.py:9: note: Got: tmp/m.py:9: note: def update() -> str [case testAbstractBodyTurnsEmptyCoarse] -# flags: --strict-optional from b import Base class Sub(Base): @@ -6081,7 +6095,7 @@ class Base: def meth(self) -> int: ... [out] [out2] -main:6: error: Call to abstract method "meth" of "Base" with trivial body via super() is unsafe +main:5: error: Call to abstract method "meth" of "Base" with trivial body via super() is unsafe [case testNoCrashDoubleReexportFunctionEmpty] import m @@ -6332,7 +6346,7 @@ reveal_type(D.meth) reveal_type(D().meth) [out] [out2] -tmp/m.py:4: note: Revealed type is "def [Self <: lib.C] (self: Self`0, other: Self`0) -> Self`0" +tmp/m.py:4: note: Revealed type is "def [Self <: lib.C] (self: Self`1, other: Self`1) -> Self`1" tmp/m.py:5: note: Revealed type is "def (other: m.D) -> m.D" [case testIncrementalNestedGenericCallableCrash] @@ -6403,3 +6417,236 @@ def g(d: Dict[TValue]) -> TValue: tmp/b.py:6: error: TypedDict "a.Dict[TValue]" has no key "x" [out2] tmp/b.py:6: error: TypedDict "a.Dict[TValue]" has no key "y" + +[case testParamSpecNoCrash] +import m +[file m.py] +from typing import Callable, TypeVar +from lib import C + +T = TypeVar("T") +def test(x: Callable[..., T]) -> T: ... +test(C) # type: ignore + +[file m.py.2] +from typing import Callable, TypeVar +from lib import C + +T = TypeVar("T") +def test(x: Callable[..., T]) -> T: ... +test(C) # type: ignore +# touch +[file lib.py] +from typing import ParamSpec, Generic, Callable + +P = ParamSpec("P") +class C(Generic[P]): + def __init__(self, fn: Callable[P, int]) -> None: ... +[builtins fixtures/dict.pyi] + +[case testVariadicClassIncrementalUpdateRegularToVariadic] +from typing import Any +from lib import C + +x: C[int, str] + +[file lib.py] +from typing import Generic, TypeVar + +T = TypeVar("T") +S = TypeVar("S") +class C(Generic[T, S]): ... + +[file lib.py.2] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Unpack[Ts]]): ... +[builtins fixtures/tuple.pyi] + +[case testVariadicClassIncrementalUpdateVariadicToRegular] +from typing import Any +from lib import C + +x: C[int, str, int] + +[file lib.py] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Unpack[Ts]]): ... +[file lib.py.2] +from typing import Generic, TypeVar + +T = TypeVar("T") +S = TypeVar("S") +class C(Generic[T, S]): ... +[builtins fixtures/tuple.pyi] +[out2] +main:4: error: "C" expects 2 type arguments, but 3 given + +[case testVariadicTupleIncrementalUpdateNoCrash] +import m +[file m.py] +from typing import Any +from lib import C + +x: C[Any] +[file m.py.2] +from lib import C + +x: C[int] +[file lib.py] +from typing import Generic, Tuple, TypeVar +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Tuple[Unpack[Ts]]): ... +[builtins fixtures/tuple.pyi] + +[case testNoIncrementalCrashOnInvalidTypedDict] +import m +[file m.py] +import counts +[file m.py.2] +import counts +# touch +[file counts.py] +from typing_extensions import TypedDict +Counts = TypedDict("Counts", {k: int for k in "abc"}) # type: ignore +[builtins fixtures/dict.pyi] + +[case testNoIncrementalCrashOnInvalidTypedDictFunc] +import m +[file m.py] +import counts +[file m.py.2] +import counts +# touch +[file counts.py] +from typing_extensions import TypedDict +def test() -> None: + Counts = TypedDict("Counts", {k: int for k in "abc"}) # type: ignore +[builtins fixtures/dict.pyi] + +[case testNoIncrementalCrashOnTypedDictMethod] +import a +[file a.py] +from b import C +x: C +[file a.py.2] +from b import C +x: C +reveal_type(x.h) +[file b.py] +from typing_extensions import TypedDict +class C: + def __init__(self) -> None: + self.h: Hidden + class Hidden(TypedDict): + x: int +[builtins fixtures/dict.pyi] +[out] +[out2] +tmp/a.py:3: note: Revealed type is "TypedDict('b.C.Hidden@5', {'x': builtins.int})" + +[case testNoIncrementalCrashOnInvalidEnumMethod] +import a +[file a.py] +from lib import TheClass +[file a.py.2] +from lib import TheClass +x: TheClass +reveal_type(x.enum_type) +[file lib.py] +import enum + +class TheClass: + def __init__(self) -> None: + names = ["foo"] + pyenum = enum.Enum('Blah', { # type: ignore[misc] + x.upper(): x + for x in names + }) + self.enum_type = pyenum +[out] +[out2] +tmp/a.py:3: note: Revealed type is "def (value: builtins.object) -> lib.TheClass.pyenum@6" + +[case testStartUsingTypeGuard] +import a +[file a.py] +from lib import guard +from typing import Union +from typing_extensions import assert_type +x: Union[int, str] + +[file a.py.2] +from lib import guard +from typing import Union +from typing_extensions import assert_type +x: Union[int, str] +if guard(x): + assert_type(x, int) +else: + assert_type(x, Union[int, str]) +[file lib.py] +from typing_extensions import TypeGuard +def guard(x: object) -> TypeGuard[int]: + pass +[builtins fixtures/tuple.pyi] + +[case testStartUsingTypeIs] +import a +[file a.py] +from lib import guard +from typing import Union +from typing_extensions import assert_type +x: Union[int, str] + +[file a.py.2] +from lib import guard +from typing import Union +from typing_extensions import assert_type +x: Union[int, str] +if guard(x): + assert_type(x, int) +else: + assert_type(x, str) +[file lib.py] +from typing_extensions import TypeIs +def guard(x: object) -> TypeIs[int]: + pass +[builtins fixtures/tuple.pyi] + +[case testTypeGuardToTypeIs] +import a +[file a.py] +from lib import guard +from typing import Union +from typing_extensions import assert_type +x: Union[int, str] +if guard(x): + assert_type(x, int) +else: + assert_type(x, Union[int, str]) +[file a.py.2] +from lib import guard +from typing import Union +from typing_extensions import assert_type +x: Union[int, str] +if guard(x): + assert_type(x, int) +else: + assert_type(x, str) +[file lib.py] +from typing_extensions import TypeGuard +def guard(x: object) -> TypeGuard[int]: + pass +[file lib.py.2] +from typing_extensions import TypeIs +def guard(x: object) -> TypeIs[int]: + pass +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-inference-context.test b/test-data/unit/check-inference-context.test index 625ab091a6a9..afe6548df2d4 100644 --- a/test-data/unit/check-inference-context.test +++ b/test-data/unit/check-inference-context.test @@ -13,31 +13,31 @@ def f() -> 'A[T]': pass class A(Generic[T]): pass class B: pass -ab = None # type: A[B] -ao = None # type: A[object] -b = None # type: B +ab: A[B] +ao: A[object] +b: B if int(): ao = f() if int(): ab = f() if int(): - b = f() # E: Incompatible types in assignment (expression has type "A[]", variable has type "B") + b = f() # E: Incompatible types in assignment (expression has type "A[Never]", variable has type "B") [case testBasicContextInferenceForConstructor] from typing import TypeVar, Generic T = TypeVar('T') class A(Generic[T]): pass class B: pass -ab = None # type: A[B] -ao = None # type: A[object] -b = None # type: B +ab: A[B] +ao: A[object] +b: B if int(): ao = A() if int(): ab = A() if int(): - b = A() # E: Incompatible types in assignment (expression has type "A[]", variable has type "B") + b = A() # E: Incompatible types in assignment (expression has type "A[Never]", variable has type "B") [case testIncompatibleContextInference] from typing import TypeVar, Generic T = TypeVar('T') @@ -48,11 +48,11 @@ class A(Generic[T]): pass class B: pass class C: pass -b = None # type: B -c = None # type: C -ab = None # type: A[B] -ao = None # type: A[object] -ac = None # type: A[C] +b: B +c: C +ab: A[B] +ao: A[object] +ac: A[C] if int(): ac = f(b) # E: Argument 1 to "f" has incompatible type "B"; expected "C" @@ -77,10 +77,10 @@ if int(): from typing import TypeVar, Generic T = TypeVar('T') def g() -> None: - ao = None # type: A[object] - ab = None # type: A[B] - o = None # type: object - b = None # type: B + ao: A[object] + ab: A[B] + o: object + b: B x = f(o) if int(): @@ -111,9 +111,9 @@ class A(Generic[T]): pass from typing import TypeVar, Generic T = TypeVar('T') def g() -> None: - ao = None # type: A[object] - ab = None # type: A[B] - b = None # type: B + ao: A[object] + ab: A[B] + b: B x, y = f(b), f(b) if int(): ao = x # E: Incompatible types in assignment (expression has type "A[B]", variable has type "A[object]") @@ -130,9 +130,9 @@ class B: pass from typing import TypeVar, List, Generic T = TypeVar('T') def h() -> None: - ao = None # type: A[object] - ab = None # type: A[B] - b = None # type: B + ao: A[object] + ab: A[B] + b: B x, y = g(f(b)) if int(): ao = x # E: Incompatible types in assignment (expression has type "A[B]", variable has type "A[object]") @@ -162,10 +162,10 @@ def f(a: T) -> 'Tuple[A[T], A[T]]': pass class A(Generic[T]): pass class B: pass -b = None # type: B -o = None # type: object -ab = None # type: A[B] -ao = None # type: A[object] +b: B +o: object +ab: A[B] +ao: A[object] if int(): ab, ao = f(b) # E: Incompatible types in assignment (expression has type "A[B]", variable has type "A[object]") @@ -192,10 +192,10 @@ def h(a: S, b: T) -> 'Tuple[A[S], A[S], A[T], A[T]]': pass class A(Generic[T]): pass class B: pass -b = None # type: B -o = None # type: object -ab = None # type: A[B] -ao = None # type: A[object] +b: B +o: object +ab: A[B] +ao: A[object] if int(): ao, ao, ab = f(b, b) # E: Incompatible types in assignment (expression has type "A[B]", variable has type "A[object]") @@ -229,12 +229,12 @@ class A(Generic[T]): pass class B: pass class C(B): pass -ac = None # type: A[C] -ab = None # type: A[B] -ao = None # type: A[object] -b = None # type: B -c = None # type: C -o = None # type: object +ac: A[C] +ab: A[B] +ao: A[object] +b: B +c: C +o: object if int(): ab = f(b, o) # E: Argument 2 to "f" has incompatible type "object"; expected "B" @@ -266,11 +266,11 @@ def f(a: T) -> 'A[T]': pass class A(Generic[T]): pass class B: pass -aab = None # type: A[A[B]] -aao = None # type: A[A[object]] -ao = None # type: A[object] -b = None # type: B -o = None # type: object +aab: A[A[B]] +aao: A[A[object]] +ao: A[object] +b: B +o: object if int(): aab = f(f(o)) # E: Argument 1 to "f" has incompatible type "object"; expected "B" @@ -279,6 +279,7 @@ if int(): aab = f(f(b)) aao = f(f(b)) ao = f(f(b)) + [case testNestedGenericFunctionCall2] from typing import TypeVar, Generic T = TypeVar('T') @@ -289,10 +290,10 @@ def g(a: T) -> 'A[T]': pass class A(Generic[T]): pass class B: pass -ab = None # type: A[B] -ao = None # type: A[object] -b = None # type: B -o = None # type: object +ab: A[B] +ao: A[object] +b: B +o: object if int(): ab = f(g(o)) # E: Argument 1 to "g" has incompatible type "object"; expected "B" @@ -300,6 +301,7 @@ if int(): if int(): ab = f(g(b)) ao = f(g(b)) + [case testNestedGenericFunctionCall3] from typing import TypeVar, Generic T = TypeVar('T') @@ -310,10 +312,10 @@ def g(a: T) -> 'A[T]': pass class A(Generic[T]): pass class B: pass -ab = None # type: A[B] -ao = None # type: A[object] -b = None # type: B -o = None # type: object +ab: A[B] +ao: A[object] +b: B +o: object if int(): ab = f(g(o), g(b)) # E: Argument 1 to "g" has incompatible type "object"; expected "B" @@ -334,9 +336,9 @@ if int(): [case testMethodCallWithContextInference] from typing import TypeVar, Generic T = TypeVar('T') -o = None # type: object -b = None # type: B -c = None # type: C +o: object +b: B +c: C def f(a: T) -> 'A[T]': pass class A(Generic[T]): @@ -344,9 +346,9 @@ class A(Generic[T]): class B: pass class C(B): pass -ao = None # type: A[object] -ab = None # type: A[B] -ac = None # type: A[C] +ao: A[object] +ab: A[B] +ac: A[C] ab.g(f(o)) # E: Argument 1 to "f" has incompatible type "object"; expected "B" if int(): @@ -365,12 +367,12 @@ ab.g(f(c)) [case testEmptyListExpression] from typing import List -aa = None # type: List[A] -ao = None # type: List[object] -a = None # type: A +aa: List[A] +ao: List[object] +a: A def f(): a, aa, ao # Prevent redefinition -a = [] # E: Incompatible types in assignment (expression has type "List[]", variable has type "A") +a = [] # E: Incompatible types in assignment (expression has type "List[Never]", variable has type "A") aa = [] ao = [] @@ -379,15 +381,15 @@ class A: pass [builtins fixtures/list.pyi] [case testSingleItemListExpressions] -from typing import List -aa = None # type: List[A] -ab = None # type: List[B] -ao = None # type: List[object] -a = None # type: A -b = None # type: B +from typing import List, Optional +aa: List[Optional[A]] +ab: List[B] +ao: List[object] +a: A +b: B def f(): aa, ab, ao # Prevent redefinition -aa = [b] # E: List item 0 has incompatible type "B"; expected "A" +aa = [b] # E: List item 0 has incompatible type "B"; expected "Optional[A]" ab = [a] # E: List item 0 has incompatible type "A"; expected "B" aa = [a] @@ -402,11 +404,11 @@ class B: pass [case testMultiItemListExpressions] from typing import List -aa = None # type: List[A] -ab = None # type: List[B] -ao = None # type: List[object] -a = None # type: A -b = None # type: B +aa: List[A] +ab: List[B] +ao: List[object] +a: A +b: B def f(): ab, aa, ao # Prevent redefinition ab = [b, a] # E: List item 1 has incompatible type "A"; expected "B" @@ -433,6 +435,7 @@ class B: pass [out] [case testNestedListExpressions] +# flags: --no-strict-optional from typing import List aao = None # type: List[List[object]] aab = None # type: List[List[B]] @@ -596,17 +599,17 @@ y = C() # type: Any [case testInferLambdaArgumentTypeUsingContext] from typing import Callable -f = None # type: Callable[[B], A] +f: Callable[[B], A] if int(): f = lambda x: x.o f = lambda x: x.x # E: "B" has no attribute "x" class A: pass class B: - o = None # type: A + o: A [case testInferLambdaReturnTypeUsingContext] from typing import List, Callable -f = None # type: Callable[[], List[A]] +f: Callable[[], List[A]] if int(): f = lambda: [] f = lambda: [B()] # E: List item 0 has incompatible type "B"; expected "A" @@ -622,8 +625,10 @@ reveal_type((lambda x, y: x + y)(1, 2)) # N: Revealed type is "builtins.int" reveal_type((lambda s, i: s)(i=0, s='x')) # N: Revealed type is "Literal['x']?" reveal_type((lambda s, i: i)(i=0, s='x')) # N: Revealed type is "Literal[0]?" reveal_type((lambda x, s, i: x)(1.0, i=0, s='x')) # N: Revealed type is "builtins.float" -(lambda x, s, i: x)() # E: Too few arguments -(lambda: 0)(1) # E: Too many arguments +if object(): + (lambda x, s, i: x)() # E: Too few arguments +if object(): + (lambda: 0)(1) # E: Too many arguments -- varargs are not handled, but it should not crash reveal_type((lambda *k, s, i: i)(type, i=0, s='x')) # N: Revealed type is "Any" reveal_type((lambda s, *k, i: i)(i=0, s='x')) # N: Revealed type is "Any" @@ -680,6 +685,7 @@ def foo(arg: Callable[..., T]) -> None: pass foo(lambda: 1) [case testLambdaNoneInContext] +# flags: --no-strict-optional from typing import Callable def f(x: Callable[[], None]) -> None: pass def g(x: Callable[[], int]) -> None: pass @@ -687,14 +693,15 @@ f(lambda: None) g(lambda: None) [case testIsinstanceInInferredLambda] -from typing import TypeVar, Callable +# flags: --new-type-inference +from typing import TypeVar, Callable, Optional T = TypeVar('T') S = TypeVar('S') class A: pass class B(A): pass class C(A): pass -def f(func: Callable[[T], S], *z: T, r: S = None) -> S: pass -f(lambda x: 0 if isinstance(x, B) else 1) # E: Cannot infer type argument 1 of "f" +def f(func: Callable[[T], S], *z: T, r: Optional[S] = None) -> S: pass +reveal_type(f(lambda x: 0 if isinstance(x, B) else 1)) # N: Revealed type is "builtins.int" f(lambda x: 0 if isinstance(x, B) else 1, A())() # E: "int" not callable f(lambda x: x if isinstance(x, B) else B(), A(), r=B())() # E: "B" not callable f( @@ -739,7 +746,9 @@ from typing import List class A: pass class B: pass class C(B): pass -a, b, c = None, None, None # type: (List[A], List[B], List[C]) +a: List[A] +b: List[B] +c: List[C] if int(): a = a or [] if int(): @@ -762,7 +771,7 @@ from typing import List, TypeVar t = TypeVar('t') s = TypeVar('s') # Some type variables can be inferred using context, but not all of them. -a = None # type: List[A] +a: List[A] def f(a: s, b: t) -> List[s]: pass class A: pass class B: pass @@ -780,7 +789,7 @@ def f(a: s, b: t) -> List[s]: pass class A: pass class B: pass # Like testSomeTypeVarsInferredFromContext, but tvars in different order. -a = None # type: List[A] +a: List[A] if int(): a = f(A(), B()) if int(): @@ -800,8 +809,8 @@ map( [case testChainedAssignmentInferenceContexts] from typing import List -i = None # type: List[int] -s = None # type: List[str] +i: List[int] +s: List[str] if int(): i = i = [] if int(): @@ -821,10 +830,10 @@ a.x = [''] # E: List item 0 has incompatible type "str"; expected "int" [case testListMultiplyInContext] from typing import List -a = None # type: List[int] +a: List[int] if int(): - a = [None] * 3 - a = [''] * 3 # E: List item 0 has incompatible type "str"; expected "int" + a = [None] * 3 # E: List item 0 has incompatible type "None"; expected "int" + a = [''] * 3 # E: List item 0 has incompatible type "str"; expected "int" [builtins fixtures/list.pyi] [case testUnionTypeContext] @@ -833,7 +842,7 @@ T = TypeVar('T') def f(x: Union[List[T], str]) -> None: pass f([1]) f('') -f(1) # E: Argument 1 to "f" has incompatible type "int"; expected "Union[List[], str]" +f(1) # E: Argument 1 to "f" has incompatible type "int"; expected "Union[List[Never], str]" [builtins fixtures/isinstancelist.pyi] [case testIgnoringInferenceContext] @@ -847,7 +856,7 @@ g(f(a)) [case testStar2Context] from typing import Any, Dict, Tuple, Iterable -def f1(iterable: Iterable[Tuple[str, Any]] = None) -> None: +def f1(iterable: Iterable[Tuple[str, Any]] = ()) -> None: f2(**dict(iterable)) def f2(iterable: Iterable[Tuple[str, Any]], **kw: Any) -> None: pass @@ -902,7 +911,7 @@ from typing import TypeVar, Callable, Generic T = TypeVar('T') class A(Generic[T]): pass -reveal_type(A()) # N: Revealed type is "__main__.A[]" +reveal_type(A()) # N: Revealed type is "__main__.A[Never]" b = reveal_type(A()) # type: A[int] # N: Revealed type is "__main__.A[builtins.int]" [case testUnionWithGenericTypeItemContext] @@ -913,11 +922,10 @@ T = TypeVar('T') def f(x: Union[T, List[int]]) -> Union[T, List[int]]: pass reveal_type(f(1)) # N: Revealed type is "Union[builtins.int, builtins.list[builtins.int]]" reveal_type(f([])) # N: Revealed type is "builtins.list[builtins.int]" -reveal_type(f(None)) # N: Revealed type is "builtins.list[builtins.int]" +reveal_type(f(None)) # N: Revealed type is "Union[None, builtins.list[builtins.int]]" [builtins fixtures/list.pyi] [case testUnionWithGenericTypeItemContextAndStrictOptional] -# flags: --strict-optional from typing import TypeVar, Union, List T = TypeVar('T') @@ -941,11 +949,10 @@ c = C[List[int]]() reveal_type(c.f('')) # N: Revealed type is "Union[builtins.list[builtins.int], builtins.str]" reveal_type(c.f([1])) # N: Revealed type is "builtins.list[builtins.int]" reveal_type(c.f([])) # N: Revealed type is "builtins.list[builtins.int]" -reveal_type(c.f(None)) # N: Revealed type is "builtins.list[builtins.int]" +reveal_type(c.f(None)) # N: Revealed type is "Union[builtins.list[builtins.int], None]" [builtins fixtures/list.pyi] [case testGenericMethodCalledInGenericContext] -# flags: --strict-optional from typing import TypeVar, Generic _KT = TypeVar('_KT') @@ -1213,7 +1220,6 @@ x: Iterable[Union[A, B]] = f(B()) [builtins fixtures/list.pyi] [case testWideOuterContextOptional] -# flags: --strict-optional from typing import Optional, Type, TypeVar class Custom: @@ -1227,7 +1233,6 @@ def b(x: T) -> Optional[T]: return a(x) [case testWideOuterContextOptionalGenericReturn] -# flags: --strict-optional from typing import Optional, Type, TypeVar, Iterable class Custom: @@ -1241,7 +1246,6 @@ def b(x: T) -> Iterable[Optional[T]]: return a(x) [case testWideOuterContextOptionalMethod] -# flags: --strict-optional from typing import Optional, Type, TypeVar class A: pass @@ -1274,7 +1278,6 @@ def bar(xs: List[S]) -> S: [builtins fixtures/list.pyi] [case testWideOuterContextOptionalTypeVarReturn] -# flags: --strict-optional from typing import Callable, Iterable, List, Optional, TypeVar class C: @@ -1290,7 +1293,6 @@ def g(l: List[C], x: str) -> Optional[C]: [builtins fixtures/list.pyi] [case testWideOuterContextOptionalTypeVarReturnLambda] -# flags: --strict-optional from typing import Callable, Iterable, List, Optional, TypeVar class C: @@ -1303,13 +1305,32 @@ def g(l: List[C], x: str) -> Optional[C]: return f(l, lambda c: reveal_type(c).x) # N: Revealed type is "__main__.C" [builtins fixtures/list.pyi] +[case testPartialTypeContextWithTwoLambdas] +from typing import Any, Generic, TypeVar, Callable + +def int_to_any(x: int) -> Any: ... +def any_to_int(x: Any) -> int: ... +def any_to_str(x: Any) -> str: ... + +T = TypeVar("T") +class W(Generic[T]): + def __init__( + self, serialize: Callable[[T], Any], deserialize: Callable[[Any], T] + ) -> None: + ... +reveal_type(W(lambda x: int_to_any(x), lambda x: any_to_int(x))) # N: Revealed type is "__main__.W[builtins.int]" +W( + lambda x: int_to_any(x), # E: Argument 1 to "int_to_any" has incompatible type "str"; expected "int" + lambda x: any_to_str(x) +) + [case testWideOuterContextEmpty] from typing import List, TypeVar T = TypeVar('T', bound=int) def f(x: List[T]) -> T: ... -# mypy infers List[] here, and is a subtype of str +# mypy infers List[Never] here, and Never is a subtype of str y: str = f([]) [builtins fixtures/list.pyi] @@ -1319,15 +1340,10 @@ from typing import List, TypeVar T = TypeVar('T', bound=int) def f(x: List[T]) -> List[T]: ... -# TODO: improve error message for such cases, see #3283 and #5706 -y: List[str] = f([]) \ - # E: Incompatible types in assignment (expression has type "List[]", variable has type "List[str]") \ - # N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \ - # N: Consider using "Sequence" instead, which is covariant +y: List[str] = f([]) [builtins fixtures/list.pyi] [case testWideOuterContextNoArgs] -# flags: --strict-optional from typing import TypeVar, Optional T = TypeVar('T', bound=int) @@ -1336,16 +1352,12 @@ def f(x: Optional[T] = None) -> T: ... y: str = f() [case testWideOuterContextNoArgsError] -# flags: --strict-optional from typing import TypeVar, Optional, List T = TypeVar('T', bound=int) def f(x: Optional[T] = None) -> List[T]: ... -y: List[str] = f() \ - # E: Incompatible types in assignment (expression has type "List[]", variable has type "List[str]") \ - # N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \ - # N: Consider using "Sequence" instead, which is covariant +y: List[str] = f() [builtins fixtures/list.pyi] [case testUseCovariantGenericOuterContext] @@ -1419,7 +1431,6 @@ bar({1: 2}) [builtins fixtures/dict.pyi] [case testOptionalTypeNarrowedByGenericCall] -# flags: --strict-optional from typing import Dict, Optional d: Dict[str, str] = {} @@ -1431,7 +1442,6 @@ def foo(arg: Optional[str] = None) -> None: [builtins fixtures/dict.pyi] [case testOptionalTypeNarrowedByGenericCall2] -# flags: --strict-optional from typing import Dict, Optional d: Dict[str, str] = {} @@ -1443,7 +1453,6 @@ if x: [builtins fixtures/dict.pyi] [case testOptionalTypeNarrowedByGenericCall3] -# flags: --strict-optional from typing import Generic, TypeVar, Union T = TypeVar("T") @@ -1456,7 +1465,6 @@ def foo(arg: Union[str, int]) -> None: [builtins fixtures/isinstance.pyi] [case testOptionalTypeNarrowedByGenericCall4] -# flags: --strict-optional from typing import Optional, List, Generic, TypeVar T = TypeVar("T", covariant=True) @@ -1474,3 +1482,16 @@ b: Any i = i if isinstance(i, int) else b reveal_type(i) # N: Revealed type is "Union[Any, builtins.int]" [builtins fixtures/isinstance.pyi] + +[case testLambdaInferenceUsesNarrowedTypes] +from typing import Optional, Callable + +def f1(key: Callable[[], str]) -> None: ... +def f2(key: object) -> None: ... + +def g(b: Optional[str]) -> None: + if b: + f1(lambda: reveal_type(b)) # N: Revealed type is "builtins.str" + z: Callable[[], str] = lambda: reveal_type(b) # N: Revealed type is "builtins.str" + f2(lambda: reveal_type(b)) # N: Revealed type is "builtins.str" + lambda: reveal_type(b) # N: Revealed type is "builtins.str" diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index fc8113766f1a..08b53ab16972 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -33,8 +33,8 @@ class B: pass [case testLvarInitializedToVoid] import typing def f() -> None: - a = g() # E: "g" does not return a value - #b, c = g() # "g" does not return a value TODO + a = g() # E: "g" does not return a value (it only ever returns None) + #b, c = g() # "g" does not return a value (it only ever returns None) TODO def g() -> None: pass [out] @@ -54,7 +54,7 @@ class B: pass [case testInferringLvarTypeFromGvar] -g = None # type: B +g: B def f() -> None: a = g @@ -78,7 +78,7 @@ def g(): pass [case testInferringExplicitDynamicTypeForLvar] from typing import Any -g = None # type: Any +g: Any def f(a: Any) -> None: b = g @@ -95,8 +95,8 @@ def f(a: Any) -> None: def f() -> None: a = A(), B() - aa = None # type: A - bb = None # type: B + aa: A + bb: B if int(): bb = a[0] # E: Incompatible types in assignment (expression has type "A", variable has type "B") aa = a[1] # E: Incompatible types in assignment (expression has type "B", variable has type "A") @@ -122,8 +122,8 @@ class A: pass from typing import TypeVar, Generic T = TypeVar('T') class A(Generic[T]): pass -a_i = None # type: A[int] -a_s = None # type: A[str] +a_i: A[int] +a_s: A[str] def f() -> None: a_int = A() # type: A[int] @@ -182,7 +182,7 @@ class B: pass [case testInferringLvarTypesInTupleAssignment] from typing import Tuple def f() -> None: - t = None # type: Tuple[A, B] + t: Tuple[A, B] a, b = t if int(): a = b # E: Incompatible types in assignment (expression has type "B", variable has type "A") @@ -200,7 +200,7 @@ class B: pass [case testInferringLvarTypesInNestedTupleAssignment1] from typing import Tuple def f() -> None: - t = None # type: Tuple[A, B] + t: Tuple[A, B] a1, (a, b) = A(), t if int(): a = b # E: Incompatible types in assignment (expression has type "B", variable has type "A") @@ -270,9 +270,123 @@ def f() -> None: class A: pass [out] +[case testClassObjectsNotUnpackableWithoutIterableMetaclass] +from typing import Type + +class Foo: ... +A: Type[Foo] = Foo +a, b = Foo # E: "Type[Foo]" object is not iterable +c, d = A # E: "Type[Foo]" object is not iterable + +class Meta(type): ... +class Bar(metaclass=Meta): ... +B: Type[Bar] = Bar +e, f = Bar # E: "Type[Bar]" object is not iterable +g, h = B # E: "Type[Bar]" object is not iterable + +reveal_type(a) # E: Cannot determine type of "a" # N: Revealed type is "Any" +reveal_type(b) # E: Cannot determine type of "b" # N: Revealed type is "Any" +reveal_type(c) # E: Cannot determine type of "c" # N: Revealed type is "Any" +reveal_type(d) # E: Cannot determine type of "d" # N: Revealed type is "Any" +reveal_type(e) # E: Cannot determine type of "e" # N: Revealed type is "Any" +reveal_type(f) # E: Cannot determine type of "f" # N: Revealed type is "Any" +reveal_type(g) # E: Cannot determine type of "g" # N: Revealed type is "Any" +reveal_type(h) # E: Cannot determine type of "h" # N: Revealed type is "Any" +[out] + +[case testInferringLvarTypesUnpackedFromIterableClassObject] +from typing import Iterator, Type, TypeVar, Union, overload +class Meta(type): + def __iter__(cls) -> Iterator[int]: + yield from [1, 2, 3] + +class Meta2(type): + def __iter__(cls) -> Iterator[str]: + yield from ["foo", "bar", "baz"] + +class Meta3(type): ... + +class Foo(metaclass=Meta): ... +class Bar(metaclass=Meta2): ... +class Baz(metaclass=Meta3): ... +class Spam: ... + +class Eggs(metaclass=Meta): + @overload + def __init__(self, x: int) -> None: ... + @overload + def __init__(self, x: int, y: int, z: int) -> None: ... + def __init__(self, x: int, y: int = ..., z: int = ...) -> None: ... + +A: Type[Foo] = Foo +B: Type[Union[Foo, Bar]] = Foo +C: Union[Type[Foo], Type[Bar]] = Foo +D: Type[Union[Foo, Baz]] = Foo +E: Type[Union[Foo, Spam]] = Foo +F: Type[Eggs] = Eggs +G: Type[Union[Foo, Eggs]] = Foo + +a, b, c = Foo +d, e, f = A +g, h, i = B +j, k, l = C +m, n, o = D # E: "Type[Baz]" object is not iterable +p, q, r = E # E: "Type[Spam]" object is not iterable +s, t, u = Eggs +v, w, x = F +y, z, aa = G + +for var in [a, b, c, d, e, f, s, t, u, v, w, x, y, z, aa]: + reveal_type(var) # N: Revealed type is "builtins.int" + +for var2 in [g, h, i, j, k, l]: + reveal_type(var2) # N: Revealed type is "Union[builtins.int, builtins.str]" + +for var3 in [m, n, o, p, q, r]: + reveal_type(var3) # N: Revealed type is "Union[builtins.int, Any]" + +T = TypeVar("T", bound=Type[Foo]) + +def check(x: T) -> T: + a, b, c = x + for var in [a, b, c]: + reveal_type(var) # N: Revealed type is "builtins.int" + return x + +T2 = TypeVar("T2", bound=Type[Union[Foo, Bar]]) + +def check2(x: T2) -> T2: + a, b, c = x + for var in [a, b, c]: + reveal_type(var) # N: Revealed type is "Union[builtins.int, builtins.str]" + return x + +T3 = TypeVar("T3", bound=Union[Type[Foo], Type[Bar]]) + +def check3(x: T3) -> T3: + a, b, c = x + for var in [a, b, c]: + reveal_type(var) # N: Revealed type is "Union[builtins.int, builtins.str]" + return x +[out] + +[case testInferringLvarTypesUnpackedFromIterableClassObjectWithGenericIter] +from typing import Iterator, Type, TypeVar + +T = TypeVar("T") +class Meta(type): + def __iter__(self: Type[T]) -> Iterator[T]: ... +class Foo(metaclass=Meta): ... + +A, B, C = Foo +reveal_type(A) # N: Revealed type is "__main__.Foo" +reveal_type(B) # N: Revealed type is "__main__.Foo" +reveal_type(C) # N: Revealed type is "__main__.Foo" +[out] + [case testInferringLvarTypesInMultiDefWithInvalidTuple] from typing import Tuple -t = None # type: Tuple[object, object, object] +t: Tuple[object, object, object] def f() -> None: a, b = t # Fail @@ -380,6 +494,23 @@ class Nums(Iterable[int]): def __iter__(self): pass def __next__(self): pass a, b = Nums() +reveal_type(a) # N: Revealed type is "builtins.int" +reveal_type(b) # N: Revealed type is "builtins.int" +if int(): + a = b = 1 +if int(): + a = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +if int(): + b = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +[builtins fixtures/for.pyi] + +[case testInferringTypesFromIterableStructuralSubtyping1] +from typing import Iterator +class Nums: + def __iter__(self) -> Iterator[int]: pass +a, b = Nums() +reveal_type(a) # N: Revealed type is "builtins.int" +reveal_type(b) # N: Revealed type is "builtins.int" if int(): a = b = 1 if int(): @@ -388,6 +519,22 @@ if int(): b = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") [builtins fixtures/for.pyi] +[case testInferringTypesFromIterableStructuralSubtyping2] +from typing import Self +class Nums: + def __iter__(self) -> Self: pass + def __next__(self) -> int: pass +a, b = Nums() +reveal_type(a) # N: Revealed type is "builtins.int" +reveal_type(b) # N: Revealed type is "builtins.int" +if int(): + a = b = 1 +if int(): + a = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +if int(): + b = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +[builtins fixtures/tuple.pyi] + -- Type variable inference for generic functions -- --------------------------------------------- @@ -396,9 +543,9 @@ if int(): [case testInferSimpleGenericFunction] from typing import Tuple, TypeVar T = TypeVar('T') -a = None # type: A -b = None # type: B -c = None # type: Tuple[A, object] +a: A +b: B +c: Tuple[A, object] def id(a: T) -> T: pass @@ -422,8 +569,8 @@ from typing import TypeVar T = TypeVar('T') def f() -> None: a = id - b = None # type: int - c = None # type: str + b: int + c: str if int(): b = a(c) # E: Incompatible types in assignment (expression has type "str", variable has type "int") b = a(b) @@ -437,7 +584,7 @@ def id(x: T) -> T: from typing import TypeVar T = TypeVar('T') class A: pass -a = None # type: A +a: A def ff() -> None: x = f() # E: Need type annotation for "x" @@ -458,8 +605,8 @@ class A: pass class B(A): pass T = TypeVar('T') -a = None # type: A -b = None # type: B +a: A +b: B def f(a: T, b: T) -> T: pass @@ -482,10 +629,11 @@ def f(a: T, b: S) -> Tuple[T, S]: pass class A: pass class B: pass -a, b = None, None # type: (A, B) -taa = None # type: Tuple[A, A] -tab = None # type: Tuple[A, B] -tba = None # type: Tuple[B, A] +a: A +b: B +taa: Tuple[A, A] +tab: Tuple[A, B] +tba: Tuple[B, A] if int(): taa = f(a, b) # E: Argument 2 to "f" has incompatible type "B"; expected "A" @@ -504,9 +652,9 @@ if int(): [case testConstraintSolvingWithSimpleGenerics] from typing import TypeVar, Generic T = TypeVar('T') -ao = None # type: A[object] -ab = None # type: A[B] -ac = None # type: A[C] +ao: A[object] +ab: A[B] +ac: A[C] def f(a: 'A[T]') -> 'A[T]': pass @@ -536,8 +684,8 @@ if int(): [case testConstraintSolvingFailureWithSimpleGenerics] from typing import TypeVar, Generic T = TypeVar('T') -ao = None # type: A[object] -ab = None # type: A[B] +ao: A[object] +ab: A[B] def f(a: 'A[T]', b: 'A[T]') -> None: pass @@ -549,7 +697,9 @@ f(ao, ab) # E: Cannot infer type argument 1 of "f" f(ab, ao) # E: Cannot infer type argument 1 of "f" f(ao, ao) f(ab, ab) + [case testTypeInferenceWithCalleeDefaultArgs] +# flags: --no-strict-optional from typing import TypeVar T = TypeVar('T') a = None # type: A @@ -662,7 +812,9 @@ class C(A, I, J): pass def f(a: T, b: T) -> T: pass T = TypeVar('T') -a, i, j = None, None, None # type: (A, I, J) +a: A +i: I +j: J a = f(B(), C()) @@ -699,7 +851,7 @@ from typing import Callable, Type, TypeVar T = TypeVar('T') def f(x: Callable[..., T]) -> T: return x() class A: pass -x = None # type: Type[A] +x: Type[A] y = f(x) reveal_type(y) # N: Revealed type is "__main__.A" @@ -724,10 +876,10 @@ g('a')() # E: "List[str]" not callable # The next line is a case where there are multiple ways to satisfy a constraint # involving a Union. Either T = List[str] or T = str would turn out to be valid, # but mypy doesn't know how to branch on these two options (and potentially have -# to backtrack later) and defaults to T = . The result is an +# to backtrack later) and defaults to T = Never. The result is an # awkward error message. Either a better error message, or simply accepting the # call, would be preferable here. -g(['a']) # E: Argument 1 to "g" has incompatible type "List[str]"; expected "List[]" +g(['a']) # E: Argument 1 to "g" has incompatible type "List[str]"; expected "List[Never]" h(g(['a'])) @@ -761,7 +913,6 @@ def call(c: Callable[[int], Any], i: int) -> None: [out] [case testCallableMeetAndJoin] -# flags: --python-version 3.6 from typing import Callable, Any, TypeVar class A: ... @@ -821,7 +972,7 @@ from typing import TypeVar, Union, List T = TypeVar('T') def f() -> List[T]: pass d1 = f() # type: Union[List[int], str] -d2 = f() # type: Union[int, str] # E: Incompatible types in assignment (expression has type "List[]", variable has type "Union[int, str]") +d2 = f() # type: Union[int, str] # E: Incompatible types in assignment (expression has type "List[Never]", variable has type "Union[int, str]") def g(x: T) -> List[T]: pass d3 = g(1) # type: Union[List[int], List[str]] [builtins fixtures/list.pyi] @@ -884,7 +1035,8 @@ class A: pass class B: pass def d_ab() -> Dict[A, B]: return {} def d_aa() -> Dict[A, A]: return {} -a, b = None, None # type: (A, B) +a: A +b: B d = {a:b} if int(): d = d_ab() @@ -894,7 +1046,8 @@ if int(): [case testSetLiteral] from typing import Any, Set -a, x = None, None # type: (int, Any) +a: int +x: Any def s_i() -> Set[int]: return set() def s_s() -> Set[str]: return set() s = {a} @@ -966,7 +1119,8 @@ list_2 = [f, h] [case testInferenceOfFor1] -a, b = None, None # type: (A, B) +a: A +b: B class A: pass class B: pass @@ -985,7 +1139,9 @@ class A: pass class B: pass class C: pass -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C for x, (y, z) in [(A(), (B(), C()))]: b = x # E: Incompatible types in assignment (expression has type "A", variable has type "B") c = y # E: Incompatible types in assignment (expression has type "B", variable has type "C") @@ -1005,7 +1161,8 @@ for xxx, yyy in [(None, None)]: class A: pass class B: pass -a, b = None, None # type: (A, B) +a: A +b: B for x, y in [[A()]]: b = x # E: Incompatible types in assignment (expression has type "A", variable has type "B") @@ -1023,7 +1180,7 @@ for e, f in [[]]: # E: Need type annotation for "e" \ [case testForStatementInferenceWithVoid] def f() -> None: pass -for x in f(): # E: "f" does not return a value +for x in f(): # E: "f" does not return a value (it only ever returns None) pass [builtins fixtures/for.pyi] @@ -1087,7 +1244,7 @@ class B: pass [case testMultipleAssignmentWithPartialDefinition] -a = None # type: A +a: A if int(): x, a = a, a if int(): @@ -1099,7 +1256,7 @@ if int(): class A: pass [case testMultipleAssignmentWithPartialDefinition2] -a = None # type: A +a: A if int(): a, x = [a, a] if int(): @@ -1113,7 +1270,7 @@ class A: pass [case testMultipleAssignmentWithPartialDefinition3] from typing import Any, cast -a = None # type: A +a: A if int(): x, a = cast(Any, a) if int(): @@ -1128,7 +1285,7 @@ class A: pass class A: pass class B: pass -if A: +if int(): a = A() if int(): a = A() @@ -1218,21 +1375,22 @@ class B: pass [builtins fixtures/list.pyi] [case testUninferableLambda] +# flags: --new-type-inference from typing import TypeVar, Callable X = TypeVar('X') def f(x: Callable[[X], X]) -> X: pass -y = f(lambda x: x) # E: Cannot infer type argument 1 of "f" +y = f(lambda x: x) # E: Need type annotation for "y" [case testUninferableLambdaWithTypeError] +# flags: --new-type-inference from typing import TypeVar, Callable X = TypeVar('X') def f(x: Callable[[X], X], y: str) -> X: pass -y = f(lambda x: x, 1) # Fail -[out] -main:4: error: Cannot infer type argument 1 of "f" -main:4: error: Argument 2 to "f" has incompatible type "int"; expected "str" +y = f(lambda x: x, 1) # E: Need type annotation for "y" \ + # E: Argument 2 to "f" has incompatible type "int"; expected "str" [case testInferLambdaNone] +# flags: --no-strict-optional from typing import Callable def f(x: Callable[[], None]) -> None: pass def g(x: Callable[[], int]) -> None: pass @@ -1244,7 +1402,6 @@ f(b) g(b) [case testLambdaDefaultContext] -# flags: --strict-optional from typing import Callable def f(a: Callable[..., None] = lambda *a, **k: None): pass @@ -1295,8 +1452,8 @@ def g(cond: bool) -> Any: [case testOrOperationWithGenericOperands] from typing import List -a = None # type: List[A] -o = None # type: List[object] +a: List[A] +o: List[object] a2 = a or [] if int(): a = a2 @@ -1317,7 +1474,7 @@ x.y # E: "object" has no attribute "y" [case testAccessDataAttributeBeforeItsTypeIsAvailable] -a = None # type: A +a: A a.x.y # E: Cannot determine type of "x" class A: def __init__(self) -> None: @@ -1334,7 +1491,7 @@ from typing import List, _promote class A: pass @_promote(A) class B: pass -a = None # type: List[A] +a: List[A] x1 = [A(), B()] x2 = [B(), A()] x3 = [B(), B()] @@ -1357,7 +1514,7 @@ class A: pass class B: pass @_promote(B) class C: pass -a = None # type: List[A] +a: List[A] x1 = [A(), C()] x2 = [C(), A()] x3 = [B(), C()] @@ -1524,7 +1681,9 @@ a() # E: "Dict[str, int]" not callable [case testInferDictInitializedToEmptyUsingUpdateError] a = {} # E: Need type annotation for "a" (hint: "a: Dict[, ] = ...") -a.update([1, 2]) # E: Argument 1 to "update" of "dict" has incompatible type "List[int]"; expected "Mapping[Any, Any]" +a.update([1, 2]) # E: Argument 1 to "update" of "dict" has incompatible type "List[int]"; expected "SupportsKeysAndGetItem[Any, Any]" \ + # N: "list" is missing following "SupportsKeysAndGetItem" protocol member: \ + # N: keys a() # E: "Dict[Any, Any]" not callable [builtins fixtures/dict.pyi] @@ -1651,7 +1810,6 @@ reveal_type(C().a) # N: Revealed type is "builtins.dict[builtins.int, builtins. [builtins fixtures/dict.pyi] [case testInferAttributeInitializedToNoneAndAssigned] -# flags: --strict-optional class C: def __init__(self) -> None: self.a = None @@ -1698,7 +1856,6 @@ reveal_type(C().a) # N: Revealed type is "builtins.dict[Any, Any]" [builtins fixtures/dict.pyi] [case testInferAttributeInitializedToNoneAndAssignedOtherMethod] -# flags: --strict-optional class C: def __init__(self) -> None: self.a = None @@ -1731,7 +1888,6 @@ reveal_type(C().a) # N: Revealed type is "builtins.dict[Any, Any]" [builtins fixtures/dict.pyi] [case testInferAttributeInitializedToNoneAndAssignedClassBody] -# flags: --strict-optional class C: a = None def __init__(self) -> None: @@ -1766,6 +1922,7 @@ reveal_type(dd) # N: Revealed type is "builtins.dict[builtins.str, builtins.int [builtins fixtures/dict.pyi] [case testInferFromEmptyDictWhenUsingInSpecialCase] +# flags: --no-strict-optional d = None if 'x' in d: # E: "None" has no attribute "__iter__" (not iterable) pass @@ -1788,6 +1945,7 @@ reveal_type(a) # N: Revealed type is "builtins.list[builtins.int]" [builtins fixtures/list.pyi] [case testInferSetTypeFromInplaceOr] +# flags: --no-strict-optional a = set() a |= {'x'} reveal_type(a) # N: Revealed type is "builtins.set[builtins.str]" @@ -1804,7 +1962,8 @@ def f() -> None: x = None else: x = 1 - x() # E: "int" not callable + x() # E: "int" not callable \ + # E: "None" not callable [out] [case testLocalVariablePartiallyTwiceInitializedToNone] @@ -1815,7 +1974,8 @@ def f() -> None: x = None else: x = 1 - x() # E: "int" not callable + x() # E: "int" not callable \ + # E: "None" not callable [out] [case testLvarInitializedToNoneWithoutType] @@ -1829,7 +1989,8 @@ def f() -> None: x = None if object(): x = 1 -x() # E: "int" not callable +x() # E: "int" not callable \ + # E: "None" not callable [case testPartiallyInitializedToNoneAndThenToPartialList] x = None @@ -1957,7 +2118,7 @@ arr = [] arr.append(arr.append(1)) [builtins fixtures/list.pyi] [out] -main:3: error: "append" of "list" does not return a value +main:3: error: "append" of "list" does not return a value (it only ever returns None) -- Multipass -- --------- @@ -2190,8 +2351,8 @@ reveal_type(A.g) # N: Revealed type is "def (x: builtins.str)" [case testUnificationRedundantUnion] from typing import Union -a = None # type: Union[int, str] -b = None # type: Union[str, tuple] +a: Union[int, str] +b: Union[str, tuple] def f(): pass def g(x: Union[int, str]): pass c = a if f() else b @@ -2373,7 +2534,6 @@ if bool(): [out] [case testDontMarkUnreachableAfterInferenceUninhabited2] -# flags: --strict-optional from typing import TypeVar, Optional T = TypeVar('T') def f(x: Optional[T] = None) -> T: pass @@ -2444,7 +2604,7 @@ x = '' reveal_type(x) # N: Revealed type is "builtins.str" [case testLocalPartialTypesWithGlobalInitializedToNoneStrictOptional] -# flags: --local-partial-types --strict-optional +# flags: --local-partial-types x = None def f() -> None: @@ -2596,7 +2756,7 @@ class B(A): reveal_type(B.x) # N: Revealed type is "None" [case testLocalPartialTypesWithInheritance2] -# flags: --local-partial-types --strict-optional +# flags: --local-partial-types class A: x: str @@ -2604,7 +2764,7 @@ class B(A): x = None # E: Incompatible types in assignment (expression has type "None", base class "A" defined the type as "str") [case testLocalPartialTypesWithAnyBaseClass] -# flags: --local-partial-types --strict-optional +# flags: --local-partial-types from typing import Any A: Any @@ -2616,7 +2776,7 @@ class C(B): y = None [case testLocalPartialTypesInMultipleMroItems] -# flags: --local-partial-types --strict-optional +# flags: --local-partial-types from typing import Optional class A: @@ -2941,7 +3101,6 @@ class B(A): x = 2 # E: Incompatible types in assignment (expression has type "int", base class "A" defined the type as "str") [case testInheritedAttributeStrictOptional] -# flags: --strict-optional class A: x: str @@ -2967,7 +3126,7 @@ T = TypeVar('T') def f() -> Callable[..., NoReturn]: ... x = f() -reveal_type(x) # N: Revealed type is "def (*Any, **Any) -> " +reveal_type(x) # N: Revealed type is "def (*Any, **Any) -> Never" [case testDeferralInNestedScopes] @@ -3044,7 +3203,6 @@ x: Inv[int] reveal_type(f(x)) # N: Revealed type is "builtins.int" [case testOptionalTypeVarAgainstOptional] -# flags: --strict-optional from typing import Optional, TypeVar, Iterable, Iterator, List _T = TypeVar('_T') @@ -3091,7 +3249,6 @@ reveal_type(b) # N: Revealed type is "collections.defaultdict[builtins.int, buil [builtins fixtures/dict.pyi] [case testPartialDefaultDictListValueStrictOptional] -# flags: --strict-optional from collections import defaultdict a = defaultdict(list) a['x'].append(1) @@ -3168,7 +3325,6 @@ def g() -> None: pass reveal_type(f(g)) # N: Revealed type is "None" [case testInferCallableReturningNone2] -# flags: --strict-optional from typing import Callable, TypeVar T = TypeVar("T") @@ -3239,7 +3395,6 @@ def collection_from_dict_value(model: Type[T2]) -> None: [builtins fixtures/isinstancelist.pyi] [case testRegression11705_Strict] -# flags: --strict-optional # See: https://github.com/python/mypy/issues/11705 from typing import Dict, Optional, NamedTuple class C(NamedTuple): @@ -3289,7 +3444,6 @@ foo(("a", {"a": "b"}, "b")) [builtins fixtures/dict.pyi] [case testUseSupertypeAsInferenceContext] -# flags: --strict-optional from typing import List, Optional class B: @@ -3388,6 +3542,14 @@ T = TypeVar("T") def type_or_callable(value: T, tp: Union[Type[T], Callable[[int], T]]) -> T: ... reveal_type(type_or_callable(A("test"), A)) # N: Revealed type is "__main__.A" +[case testUpperBoundAsInferenceFallback] +from typing import Callable, TypeVar, Any, Mapping, Optional +T = TypeVar("T", bound=Mapping[str, Any]) +def raises(opts: Optional[T]) -> T: pass +def assertRaises(cb: Callable[..., object]) -> None: pass +assertRaises(raises) # OK +[builtins fixtures/dict.pyi] + [case testJoinWithAnyFallback] from unknown import X # type: ignore[import] @@ -3399,3 +3561,289 @@ class E(D): ... reveal_type([E(), D()]) # N: Revealed type is "builtins.list[__main__.D]" reveal_type([D(), E()]) # N: Revealed type is "builtins.list[__main__.D]" + +[case testCallableInferenceAgainstCallablePosVsStar] +from typing import TypeVar, Callable, Tuple + +T = TypeVar('T') +S = TypeVar('S') + +def f(x: Callable[[T, S], None]) -> Tuple[T, S]: ... +def g(*x: int) -> None: ... +reveal_type(f(g)) # N: Revealed type is "Tuple[builtins.int, builtins.int]" +[builtins fixtures/list.pyi] + +[case testCallableInferenceAgainstCallableStarVsPos] +from typing import TypeVar, Callable, Tuple, Protocol + +T = TypeVar('T', contravariant=True) +S = TypeVar('S', contravariant=True) + +class Call(Protocol[T, S]): + def __call__(self, __x: T, *args: S) -> None: ... + +def f(x: Call[T, S]) -> Tuple[T, S]: ... +def g(*x: int) -> None: ... +reveal_type(f(g)) # N: Revealed type is "Tuple[builtins.int, builtins.int]" +[builtins fixtures/list.pyi] + +[case testCallableInferenceAgainstCallableNamedVsStar] +from typing import TypeVar, Callable, Tuple, Protocol + +T = TypeVar('T', contravariant=True) +S = TypeVar('S', contravariant=True) + +class Call(Protocol[T, S]): + def __call__(self, *, x: T, y: S) -> None: ... + +def f(x: Call[T, S]) -> Tuple[T, S]: ... +def g(**kwargs: int) -> None: ... +reveal_type(f(g)) # N: Revealed type is "Tuple[builtins.int, builtins.int]" +[builtins fixtures/list.pyi] + +[case testCallableInferenceAgainstCallableStarVsNamed] +from typing import TypeVar, Callable, Tuple, Protocol + +T = TypeVar('T', contravariant=True) +S = TypeVar('S', contravariant=True) + +class Call(Protocol[T, S]): + def __call__(self, *, x: T, **kwargs: S) -> None: ... + +def f(x: Call[T, S]) -> Tuple[T, S]: ... +def g(**kwargs: int) -> None: pass +reveal_type(f(g)) # N: Revealed type is "Tuple[builtins.int, builtins.int]" +[builtins fixtures/list.pyi] + +[case testCallableInferenceAgainstCallableNamedVsNamed] +from typing import TypeVar, Callable, Tuple, Protocol + +T = TypeVar('T', contravariant=True) +S = TypeVar('S', contravariant=True) + +class Call(Protocol[T, S]): + def __call__(self, *, x: T, y: S) -> None: ... + +def f(x: Call[T, S]) -> Tuple[T, S]: ... + +# Note: order of names is different w.r.t. protocol +def g(*, y: int, x: str) -> None: pass +reveal_type(f(g)) # N: Revealed type is "Tuple[builtins.str, builtins.int]" +[builtins fixtures/list.pyi] + +[case testCallableInferenceAgainstCallablePosOnlyVsNamed] +from typing import TypeVar, Callable, Tuple, Protocol + +T = TypeVar('T', contravariant=True) +S = TypeVar('S', contravariant=True) + +class Call(Protocol[T]): + def __call__(self, *, x: T) -> None: ... + +def f(x: Call[T]) -> Tuple[T, T]: ... + +def g(__x: str) -> None: pass +reveal_type(f(g)) # N: Revealed type is "Tuple[Never, Never]" \ + # E: Argument 1 to "f" has incompatible type "Callable[[str], None]"; expected "Call[Never]" +[builtins fixtures/list.pyi] + +[case testCallableInferenceAgainstCallableNamedVsPosOnly] +from typing import TypeVar, Callable, Tuple, Protocol + +T = TypeVar('T', contravariant=True) +S = TypeVar('S', contravariant=True) + +class Call(Protocol[T]): + def __call__(self, __x: T) -> None: ... + +def f(x: Call[T]) -> Tuple[T, T]: ... + +def g(*, x: str) -> None: pass +reveal_type(f(g)) # N: Revealed type is "Tuple[Never, Never]" \ + # E: Argument 1 to "f" has incompatible type "Callable[[NamedArg(str, 'x')], None]"; expected "Call[Never]" +[builtins fixtures/list.pyi] + +[case testCallableInferenceAgainstCallablePosOnlyVsKwargs] +from typing import TypeVar, Callable, Tuple, Protocol + +T = TypeVar('T', contravariant=True) +S = TypeVar('S', contravariant=True) + +class Call(Protocol[T]): + def __call__(self, __x: T) -> None: ... + +def f(x: Call[T]) -> Tuple[T, T]: ... + +def g(**x: str) -> None: pass +reveal_type(f(g)) # N: Revealed type is "Tuple[Never, Never]" \ + # E: Argument 1 to "f" has incompatible type "Callable[[KwArg(str)], None]"; expected "Call[Never]" +[builtins fixtures/list.pyi] + +[case testCallableInferenceAgainstCallableNamedVsArgs] +from typing import TypeVar, Callable, Tuple, Protocol + +T = TypeVar('T', contravariant=True) +S = TypeVar('S', contravariant=True) + +class Call(Protocol[T]): + def __call__(self, *, x: T) -> None: ... + +def f(x: Call[T]) -> Tuple[T, T]: ... + +def g(*args: str) -> None: pass +reveal_type(f(g)) # N: Revealed type is "Tuple[Never, Never]" \ + # E: Argument 1 to "f" has incompatible type "Callable[[VarArg(str)], None]"; expected "Call[Never]" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstTypeVarActualBound] +from typing import Callable, TypeVar + +T = TypeVar("T") +S = TypeVar("S") +def test(f: Callable[[T], S]) -> Callable[[T], S]: ... + +F = TypeVar("F", bound=Callable[..., object]) +def dec(f: F) -> F: + reveal_type(test(f)) # N: Revealed type is "def (Any) -> builtins.object" + return f + +[case testInferenceAgainstTypeVarActualUnionBound] +from typing import Protocol, TypeVar, Union + +T_co = TypeVar("T_co", covariant=True) +class SupportsFoo(Protocol[T_co]): + def foo(self) -> T_co: ... + +class A: + def foo(self) -> A: ... +class B: + def foo(self) -> B: ... + +def foo(f: SupportsFoo[T_co]) -> T_co: ... + +ABT = TypeVar("ABT", bound=Union[A, B]) +def simpler(k: ABT): + foo(k) + +[case testInferenceWorksWithEmptyCollectionsNested] +from typing import List, TypeVar, NoReturn +T = TypeVar('T') +def f(a: List[T], b: List[T]) -> T: pass +x = ["yes"] +reveal_type(f(x, [])) # N: Revealed type is "builtins.str" +reveal_type(f(["yes"], [])) # N: Revealed type is "builtins.str" + +empty: List[NoReturn] +f(x, empty) # E: Cannot infer type argument 1 of "f" +f(["no"], empty) # E: Cannot infer type argument 1 of "f" +[builtins fixtures/list.pyi] + +[case testInferenceWorksWithEmptyCollectionsUnion] +from typing import Any, Dict, NoReturn, NoReturn, Union + +def foo() -> Union[Dict[str, Any], Dict[int, Any]]: + return {} + +empty: Dict[NoReturn, NoReturn] +def bar() -> Union[Dict[str, Any], Dict[int, Any]]: + return empty +[builtins fixtures/dict.pyi] + +[case testUpperBoundInferenceFallbackNotOverused] +from typing import TypeVar, Protocol, List + +S = TypeVar("S", covariant=True) +class Foo(Protocol[S]): + def foo(self) -> S: ... +def foo(x: Foo[S]) -> S: ... + +T = TypeVar("T", bound="Base") +class Base: + def foo(self: T) -> T: ... +class C(Base): + pass + +def f(values: List[T]) -> T: ... +x = foo(f([C()])) +reveal_type(x) # N: Revealed type is "__main__.C" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericCallableUnion] +from typing import Callable, TypeVar, List, Union + +T = TypeVar("T") +S = TypeVar("S") + +def dec(f: Callable[[S], T]) -> Callable[[S], List[T]]: ... +@dec +def func(arg: T) -> Union[T, str]: + ... +reveal_type(func) # N: Revealed type is "def [S] (S`1) -> builtins.list[Union[S`1, builtins.str]]" +reveal_type(func(42)) # N: Revealed type is "builtins.list[Union[builtins.int, builtins.str]]" + +def dec2(f: Callable[[S], List[T]]) -> Callable[[S], T]: ... +@dec2 +def func2(arg: T) -> List[Union[T, str]]: + ... +reveal_type(func2) # N: Revealed type is "def [S] (S`4) -> Union[S`4, builtins.str]" +reveal_type(func2(42)) # N: Revealed type is "Union[builtins.int, builtins.str]" +[builtins fixtures/list.pyi] + +[case testInferenceAgainstGenericCallbackProtoMultiple] +from typing import Callable, Protocol, TypeVar +from typing_extensions import Concatenate, ParamSpec + +V_co = TypeVar("V_co", covariant=True) +class Metric(Protocol[V_co]): + def __call__(self) -> V_co: ... + +T = TypeVar("T") +P = ParamSpec("P") +def simple_metric(func: Callable[Concatenate[int, P], T]) -> Callable[P, T]: ... + +@simple_metric +def Negate(count: int, /, metric: Metric[float]) -> float: ... +@simple_metric +def Combine(count: int, m1: Metric[T], m2: Metric[T], /, *more: Metric[T]) -> T: ... + +reveal_type(Negate) # N: Revealed type is "def (metric: __main__.Metric[builtins.float]) -> builtins.float" +reveal_type(Combine) # N: Revealed type is "def [T] (def () -> T`5, def () -> T`5, *more: def () -> T`5) -> T`5" + +def m1() -> float: ... +def m2() -> float: ... +reveal_type(Combine(m1, m2)) # N: Revealed type is "builtins.float" +[builtins fixtures/list.pyi] + +[case testInferenceWithUninhabitedType] +from typing import Dict, Generic, List, Never, TypeVar + +T = TypeVar("T") + +class A(Generic[T]): ... +class B(Dict[T, T]): ... + +def func1(a: A[T], b: T) -> T: ... +def func2(a: T, b: A[T]) -> T: ... + +def a1(a: A[Dict[str, int]]) -> None: + reveal_type(func1(a, {})) # N: Revealed type is "builtins.dict[builtins.str, builtins.int]" + reveal_type(func2({}, a)) # N: Revealed type is "builtins.dict[builtins.str, builtins.int]" + +def a2(check: bool, a: B[str]) -> None: + reveal_type(a if check else {}) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]" + +def a3() -> None: + a = {} # E: Need type annotation for "a" (hint: "a: Dict[, ] = ...") + b = {1: {}} # E: Need type annotation for "b" + c = {1: {}, 2: {"key": {}}} # E: Need type annotation for "c" + reveal_type(a) # N: Revealed type is "builtins.dict[Any, Any]" + reveal_type(b) # N: Revealed type is "builtins.dict[builtins.int, builtins.dict[Any, Any]]" + reveal_type(c) # N: Revealed type is "builtins.dict[builtins.int, builtins.dict[builtins.str, builtins.dict[Any, Any]]]" + +def a4(x: List[str], y: List[Never]) -> None: + z1 = [x, y] + z2 = [y, x] + reveal_type(z1) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(z2) # N: Revealed type is "builtins.list[builtins.object]" + z1[1].append("asdf") # E: "object" has no attribute "append" +[builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-inline-config.test b/test-data/unit/check-inline-config.test index 1b2085e33e91..bedba811d95b 100644 --- a/test-data/unit/check-inline-config.test +++ b/test-data/unit/check-inline-config.test @@ -4,8 +4,8 @@ # mypy: disallow-any-generics, no-warn-no-return -from typing import List -def foo() -> List: # E: Missing type parameters for generic type "List" +from typing import List, Optional +def foo() -> Optional[List]: # E: Missing type parameters for generic type "List" 20 [builtins fixtures/list.pyi] @@ -15,8 +15,8 @@ def foo() -> List: # E: Missing type parameters for generic type "List" # mypy: disallow-any-generics # mypy: no-warn-no-return -from typing import List -def foo() -> List: # E: Missing type parameters for generic type "List" +from typing import List, Optional +def foo() -> Optional[List]: # E: Missing type parameters for generic type "List" 20 [builtins fixtures/list.pyi] @@ -25,8 +25,8 @@ def foo() -> List: # E: Missing type parameters for generic type "List" # mypy: disallow-any-generics=true, warn-no-return=0 -from typing import List -def foo() -> List: # E: Missing type parameters for generic type "List" +from typing import List, Optional +def foo() -> Optional[List]: # E: Missing type parameters for generic type "List" 20 [builtins fixtures/list.pyi] @@ -36,8 +36,8 @@ def foo() -> List: # E: Missing type parameters for generic type "List" # mypy: disallow-any-generics = true, warn-no-return = 0 -from typing import List -def foo() -> List: # E: Missing type parameters for generic type "List" +from typing import List, Optional +def foo() -> Optional[List]: # E: Missing type parameters for generic type "List" 20 [builtins fixtures/list.pyi] @@ -84,20 +84,20 @@ import a [file a.py] # mypy: disallow-any-generics, no-warn-no-return -from typing import List -def foo() -> List: +from typing import List, Optional +def foo() -> Optional[List]: 20 [file a.py.2] # mypy: no-warn-no-return -from typing import List -def foo() -> List: +from typing import List, Optional +def foo() -> Optional[List]: 20 [file a.py.3] -from typing import List -def foo() -> List: +from typing import List, Optional +def foo() -> Optional[List]: 20 [out] tmp/a.py:4: error: Missing type parameters for generic type "List" @@ -131,8 +131,9 @@ tmp/a.py:4: error: Missing type parameters for generic type "List" import a, b [file a.py] # mypy: no-warn-no-return +from typing import Optional -def foo() -> int: +def foo() -> Optional[int]: 20 [file b.py] @@ -164,7 +165,6 @@ main:1: error: Unrecognized option: skip_file = True main:1: error: Setting "strict" not supported in inline configuration: specify it in a configuration file instead, or set individual inline flags (see "mypy -h" for the list of flags enabled in strict mode) [case testInlineErrorCodes] -# flags: --strict-optional # mypy: enable-error-code="ignore-without-code,truthy-bool" class Foo: pass @@ -174,7 +174,7 @@ if foo: ... # E: "__main__.foo" has type "Foo" which does not implement __bool_ 42 + "no" # type: ignore # E: "type: ignore" comment without error code (consider "type: ignore[operator]" instead) [case testInlineErrorCodesOverrideConfig] -# flags: --strict-optional --config-file tmp/mypy.ini +# flags: --config-file tmp/mypy.ini import foo import tests.bar import tests.baz @@ -192,7 +192,7 @@ if foo: ... # mypy: enable-error-code="ignore-without-code" def foo() -> int: ... -if foo: ... # E: Function "Callable[[], int]" could always be true in boolean context +if foo: ... # E: Function "foo" could always be true in boolean context 42 + "no" # type: ignore # E: "type: ignore" comment without error code (consider "type: ignore[operator]" instead) [file tests/baz.py] @@ -210,3 +210,116 @@ enable_error_code = ignore-without-code, truthy-bool \[mypy-tests.*] disable_error_code = ignore-without-code + +[case testIgnoreErrorsSimple] +# mypy: ignore-errors=True + +def f() -> None: + while 1(): + pass + +[case testIgnoreErrorsInImportedModule] +from m import C +c = C() +reveal_type(c.x) # N: Revealed type is "builtins.int" + +[file m.py] +# mypy: ignore-errors=True + +class C: + def f(self) -> None: + self.x = 1 + +[case testIgnoreErrorsWithLambda] +# mypy: ignore-errors=True + +def f(self, x=lambda: 1) -> None: + pass + +class C: + def f(self) -> None: + l = lambda: 1 + self.x = 1 + +[case testIgnoreErrorsWithUnsafeSuperCall_no_empty] + +from m import C + +class D(C): + def m(self) -> None: + super().m1() + super().m2() \ + # E: Call to abstract method "m2" of "C" with trivial body via super() is unsafe + super().m3() \ + # E: Call to abstract method "m3" of "C" with trivial body via super() is unsafe + super().m4() \ + # E: Call to abstract method "m4" of "C" with trivial body via super() is unsafe + super().m5() \ + # E: Call to abstract method "m5" of "C" with trivial body via super() is unsafe + super().m6() \ + # E: Call to abstract method "m6" of "C" with trivial body via super() is unsafe + super().m7() + + def m1(self) -> int: + return 0 + + def m2(self) -> int: + return 0 + + def m3(self) -> int: + return 0 + + def m4(self) -> int: + return 0 + + def m5(self) -> int: + return 0 + + def m6(self) -> int: + return 0 + +[file m.py] +# mypy: ignore-errors=True +import abc + +class C: + @abc.abstractmethod + def m1(self) -> int: + """x""" + return 0 + + @abc.abstractmethod + def m2(self) -> int: + """doc""" + + @abc.abstractmethod + def m3(self) -> int: + pass + + @abc.abstractmethod + def m4(self) -> int: ... + + @abc.abstractmethod + def m5(self) -> int: + """doc""" + ... + + @abc.abstractmethod + def m6(self) -> int: + raise NotImplementedError() + + @abc.abstractmethod + def m7(self) -> int: + raise NotImplementedError() + pass + +[builtins fixtures/exception.pyi] + +[case testInlineErrorCodesMultipleCodes] +# mypy: disable-error-code="truthy-bool, ignore-without-code" +class Foo: + pass + +foo = Foo() +if foo: ... +42 + "no" # type: ignore diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 0722ee8d91e5..b7ee38b69d00 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -10,7 +10,7 @@ y = x [case testJoinAny] from typing import List, Any -x = None # type: List[Any] +x: List[Any] def foo() -> List[int]: pass def bar() -> List[str]: pass @@ -51,9 +51,9 @@ def f(x: Union[int, str, List]) -> None: [case testClassAttributeInitialization] class A: - x = None # type: int + x: int def __init__(self) -> None: - self.y = None # type: int + self.y: int z = self.x w = self.y @@ -71,7 +71,7 @@ def foo(x: Union[str, int]): y + [1] # E: List item 0 has incompatible type "int"; expected "str" z = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "str") -x = None # type: int +x: int y = [x] [builtins fixtures/isinstancelist.pyi] @@ -124,7 +124,7 @@ x = 'a' [case testUnionMultiAssignment] from typing import Union -x = None # type: Union[int, str] +x: Union[int, str] if int(): x = 1 x = 'a' @@ -488,7 +488,7 @@ x.y # OK: x is known to be a B [case testIsInstanceBasic] from typing import Union -x = None # type: Union[int, str] +x: Union[int, str] if isinstance(x, str): x = x + 1 # E: Unsupported operand types for + ("str" and "int") x = x + 'a' @@ -499,7 +499,7 @@ else: [case testIsInstanceIndexing] from typing import Union -x = None # type: Union[int, str] +x: Union[int, str] j = [x] if isinstance(j[0], str): j[0] = j[0] + 'a' @@ -671,7 +671,7 @@ foo() from typing import Union def foo() -> None: - x = None # type: Union[int, str] + x: Union[int, str] if isinstance(x, int): for z in [1,2]: break @@ -686,7 +686,7 @@ foo() [case testIsInstanceThreeUnion] from typing import Union, List -x = None # type: Union[int, str, List[int]] +x: Union[int, str, List[int]] while bool(): if isinstance(x, int): @@ -706,7 +706,7 @@ x + [1] # E: Unsupported operand types for + ("int" and "List[int] [case testIsInstanceThreeUnion2] from typing import Union, List -x = None # type: Union[int, str, List[int]] +x: Union[int, str, List[int]] while bool(): if isinstance(x, int): x + 1 @@ -725,7 +725,7 @@ x + [1] # E: Unsupported operand types for + ("int" and "List[int] from typing import Union, List while bool(): - x = None # type: Union[int, str, List[int]] + x: Union[int, str, List[int]] def f(): x # Prevent redefinition x = 1 if isinstance(x, int): @@ -1787,6 +1787,7 @@ issubclass(x, (int, Iterable[int])) # E: Parameterized generics cannot be used [typing fixtures/typing-full.pyi] [case testIssubclassWithMetaclasses] +# flags: --no-strict-optional class FooMetaclass(type): ... class Foo(metaclass=FooMetaclass): ... class Bar: ... @@ -1800,7 +1801,6 @@ if issubclass(fm, Bar): [builtins fixtures/isinstance.pyi] [case testIssubclassWithMetaclassesStrictOptional] -# flags: --strict-optional class FooMetaclass(type): ... class BarMetaclass(type): ... class Foo(metaclass=FooMetaclass): ... @@ -1812,9 +1812,9 @@ reveal_type(fm) # N: Revealed type is "__main__.FooMetaclass" if issubclass(fm, Foo): reveal_type(fm) # N: Revealed type is "Type[__main__.Foo]" if issubclass(fm, Bar): - reveal_type(fm) # N: Revealed type is "" + reveal_type(fm) # N: Revealed type is "Never" if issubclass(fm, Baz): - reveal_type(fm) # N: Revealed type is "" + reveal_type(fm) # N: Revealed type is "Never" [builtins fixtures/isinstance.pyi] [case testIsinstanceAndNarrowTypeVariable] @@ -1905,7 +1905,6 @@ def narrow_any_to_str_then_reassign_to_int() -> None: [builtins fixtures/isinstance.pyi] [case testNarrowTypeAfterInList] -# flags: --strict-optional from typing import List, Optional x: List[int] @@ -1923,7 +1922,6 @@ else: [out] [case testNarrowTypeAfterInListOfOptional] -# flags: --strict-optional from typing import List, Optional x: List[Optional[int]] @@ -1937,7 +1935,6 @@ else: [out] [case testNarrowTypeAfterInListNonOverlapping] -# flags: --strict-optional from typing import List, Optional x: List[str] @@ -1951,7 +1948,6 @@ else: [out] [case testNarrowTypeAfterInListNested] -# flags: --strict-optional from typing import List, Optional, Any x: Optional[int] @@ -1966,7 +1962,6 @@ if x in nested_any: [out] [case testNarrowTypeAfterInTuple] -# flags: --strict-optional from typing import Optional class A: pass class B(A): pass @@ -1981,7 +1976,6 @@ else: [out] [case testNarrowTypeAfterInNamedTuple] -# flags: --strict-optional from typing import NamedTuple, Optional class NT(NamedTuple): x: int @@ -1997,7 +1991,6 @@ else: [out] [case testNarrowTypeAfterInDict] -# flags: --strict-optional from typing import Dict, Optional x: Dict[str, int] y: Optional[str] @@ -2014,7 +2007,6 @@ else: [out] [case testNarrowTypeAfterInNoAnyOrObject] -# flags: --strict-optional from typing import Any, List, Optional x: List[Any] z: List[object] @@ -2034,7 +2026,6 @@ else: [out] [case testNarrowTypeAfterInUserDefined] -# flags: --strict-optional from typing import Container, Optional class C(Container[int]): @@ -2056,7 +2047,6 @@ else: [out] [case testNarrowTypeAfterInSet] -# flags: --strict-optional from typing import Optional, Set s: Set[str] @@ -2073,7 +2063,6 @@ else: [out] [case testNarrowTypeAfterInTypedDict] -# flags: --strict-optional from typing import Optional from mypy_extensions import TypedDict class TD(TypedDict): @@ -2149,7 +2138,6 @@ else: [builtins fixtures/isinstance.pyi] [case testIsInstanceInitialNoneCheckSkipsImpossibleCasesNoStrictOptional] -# flags: --strict-optional from typing import Optional, Union class A: pass @@ -2196,7 +2184,6 @@ def foo2(x: Optional[str]) -> None: [builtins fixtures/isinstance.pyi] [case testNoneCheckDoesNotNarrowWhenUsingTypeVars] -# flags: --strict-optional # Note: this test (and the following one) are testing checker.conditional_type_map: # if you set the 'prohibit_none_typevar_overlap' keyword argument to False when calling @@ -2248,7 +2235,6 @@ def bar(x: Union[List[str], List[int], None]) -> None: [builtins fixtures/isinstancelist.pyi] [case testNoneAndGenericTypesOverlapStrictOptional] -# flags: --strict-optional from typing import Union, Optional, List # This test is the same as the one above, except for strict-optional. @@ -2670,10 +2656,9 @@ if type(x) == int == str: else: reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str]" -# mypy shows an error about "Unsupported left operand type for !=" if we don't include this -[builtins fixtures/typing-medium.pyi] # mypy thinks int isn't defined unless we include this [builtins fixtures/primitives.pyi] + [case testTypeNotEqualsCheck] from typing import Union @@ -2683,8 +2668,6 @@ if type(x) != int: else: reveal_type(x) # N: Revealed type is "builtins.int" -# mypy shows an error about "Unsupported left operand type for !=" if we don't include this -[builtins fixtures/typing-medium.pyi] # mypy thinks int isn't defined unless we include this [builtins fixtures/primitives.pyi] diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test index e0fe389bbbd9..4beac047e278 100644 --- a/test-data/unit/check-kwargs.test +++ b/test-data/unit/check-kwargs.test @@ -54,6 +54,7 @@ f(b=[], a=A()) [builtins fixtures/list.pyi] [case testGivingArgumentAsPositionalAndKeywordArg] +# flags: --no-strict-optional import typing class A: pass class B: pass @@ -61,11 +62,13 @@ def f(a: 'A', b: 'B' = None) -> None: pass f(A(), a=A()) # E: "f" gets multiple values for keyword argument "a" [case testGivingArgumentAsPositionalAndKeywordArg2] +# flags: --no-strict-optional import typing class A: pass class B: pass def f(a: 'A' = None, b: 'B' = None) -> None: pass f(A(), a=A()) # E: "f" gets multiple values for keyword argument "a" + [case testPositionalAndKeywordForSameArg] # This used to crash in check_argument_count(). See #1095. def f(a: int): pass @@ -134,7 +137,7 @@ f(otter=A()) # E: Missing positional argument "other" in call to "f" [case testKeywordArgumentsWithDynamicallyTypedCallable] from typing import Any -f = None # type: Any +f: Any f(x=f(), z=None()) # E: "None" not callable f(f, zz=None()) # E: "None" not callable f(x=None) @@ -143,10 +146,12 @@ f(x=None) from typing import Callable class A: pass class B: pass -f = None # type: Callable[[A, B], None] +f: Callable[[A, B], None] f(a=A(), b=B()) # E: Unexpected keyword argument "a" # E: Unexpected keyword argument "b" f(A(), b=B()) # E: Unexpected keyword argument "b" + [case testKeywordOnlyArguments] +# flags: --no-strict-optional import typing class A: pass class B: pass @@ -172,7 +177,9 @@ i(A(), b=B()) i(A(), aa=A()) # E: Missing named argument "b" for "i" i(A(), b=B(), aa=A()) i(A(), aa=A(), b=B()) + [case testKeywordOnlyArgumentsFastparse] +# flags: --no-strict-optional import typing class A: pass @@ -290,10 +297,10 @@ from typing import Dict class A: pass class B: pass def f( **kwargs: 'A') -> None: pass -d = None # type: Dict[str, A] +d: Dict[str, A] f(**d) f(x=A(), **d) -d2 = None # type: Dict[str, B] +d2: Dict[str, B] f(**d2) # E: Argument 1 to "f" has incompatible type "**Dict[str, B]"; expected "A" f(x=A(), **d2) # E: Argument 2 to "f" has incompatible type "**Dict[str, B]"; expected "A" f(**{'x': B()}) # E: Argument 1 to "f" has incompatible type "**Dict[str, B]"; expected "A" @@ -324,9 +331,9 @@ reveal_type(formatter.__call__) # N: Revealed type is "def (message: builtins.s [case testPassingMappingForKeywordVarArg] from typing import Mapping def f(**kwargs: 'A') -> None: pass -b = None # type: Mapping -d = None # type: Mapping[A, A] -m = None # type: Mapping[str, A] +b: Mapping +d: Mapping[A, A] +m: Mapping[str, A] f(**d) # E: Keywords must be strings f(**m) f(**b) @@ -337,13 +344,12 @@ class A: pass from typing import Mapping class MappingSubclass(Mapping[str, str]): pass def f(**kwargs: 'A') -> None: pass -d = None # type: MappingSubclass +d: MappingSubclass f(**d) class A: pass [builtins fixtures/dict.pyi] [case testInvalidTypeForKeywordVarArg] -# flags: --strict-optional from typing import Dict, Any, Optional class A: pass def f(**kwargs: 'A') -> None: pass @@ -357,9 +363,9 @@ f(**kwargs) # E: Argument after ** must be a mapping, not "Optional[Any]" [case testPassingKeywordVarArgsToNonVarArgsFunction] from typing import Any, Dict def f(a: 'A', b: 'B') -> None: pass -d = None # type: Dict[str, Any] +d: Dict[str, Any] f(**d) -d2 = None # type: Dict[str, A] +d2: Dict[str, A] f(**d2) # E: Argument 1 to "f" has incompatible type "**Dict[str, A]"; expected "B" class A: pass class B: pass @@ -368,8 +374,8 @@ class B: pass [case testBothKindsOfVarArgs] from typing import Any, List, Dict def f(a: 'A', b: 'A') -> None: pass -l = None # type: List[Any] -d = None # type: Dict[Any, Any] +l: List[Any] +d: Dict[Any, Any] f(*l, **d) class A: pass [builtins fixtures/dict.pyi] @@ -380,8 +386,8 @@ def f1(a: 'A', b: 'A') -> None: pass def f2(a: 'A') -> None: pass def f3(a: 'A', **kwargs: 'A') -> None: pass def f4(**kwargs: 'A') -> None: pass -d = None # type: Dict[Any, Any] -d2 = None # type: Dict[Any, Any] +d: Dict[Any, Any] +d2: Dict[Any, Any] f1(**d, **d2) f2(**d, **d2) f3(**d, **d2) @@ -392,7 +398,7 @@ class A: pass [case testPassingKeywordVarArgsToVarArgsOnlyFunction] from typing import Any, Dict def f(*args: 'A') -> None: pass -d = None # type: Dict[Any, Any] +d: Dict[Any, Any] f(**d) class A: pass [builtins fixtures/dict.pyi] @@ -499,7 +505,7 @@ g(**{}) [case testKeywordUnpackWithDifferentTypes] # https://github.com/python/mypy/issues/11144 -from typing import Dict, Generic, TypeVar, Mapping +from typing import Dict, Generic, TypeVar, Mapping, Iterable T = TypeVar("T") T2 = TypeVar("T2") @@ -516,6 +522,12 @@ class C(Generic[T, T2]): class D: ... +class E: + def keys(self) -> Iterable[str]: + ... + def __getitem__(self, key: str) -> float: + ... + def foo(**i: float) -> float: ... @@ -523,7 +535,8 @@ a: A[str, str] b: B[str, str] c: C[str, float] d: D -e = {"a": "b"} +e: E +f = {"a": "b"} foo(k=1.5) foo(**a) @@ -531,6 +544,7 @@ foo(**b) foo(**c) foo(**d) foo(**e) +foo(**f) # Correct: @@ -544,9 +558,9 @@ foo(**good1) foo(**good2) foo(**good3) [out] -main:29: error: Argument 1 to "foo" has incompatible type "**A[str, str]"; expected "float" -main:30: error: Argument 1 to "foo" has incompatible type "**B[str, str]"; expected "float" -main:31: error: Argument after ** must be a mapping, not "C[str, float]" -main:32: error: Argument after ** must be a mapping, not "D" -main:33: error: Argument 1 to "foo" has incompatible type "**Dict[str, str]"; expected "float" +main:36: error: Argument 1 to "foo" has incompatible type "**A[str, str]"; expected "float" +main:37: error: Argument 1 to "foo" has incompatible type "**B[str, str]"; expected "float" +main:38: error: Argument after ** must be a mapping, not "C[str, float]" +main:39: error: Argument after ** must be a mapping, not "D" +main:41: error: Argument 1 to "foo" has incompatible type "**Dict[str, str]"; expected "float" [builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-lists.test b/test-data/unit/check-lists.test index 899b7c5b209f..77acdafd3319 100644 --- a/test-data/unit/check-lists.test +++ b/test-data/unit/check-lists.test @@ -3,8 +3,12 @@ [case testNestedListAssignment] from typing import List -a1, b1, c1 = None, None, None # type: (A, B, C) -a2, b2, c2 = None, None, None # type: (A, B, C) +a1: A +a2: A +b1: B +b2: B +c1: C +c2: C if int(): a1, [b1, c1] = a2, [b2, c2] @@ -21,7 +25,9 @@ class C: pass [case testNestedListAssignmentToTuple] from typing import List -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C a, b = [a, b] a, b = [a] # E: Need more than 1 value to unpack (2 expected) @@ -35,7 +41,9 @@ class C: pass [case testListAssignmentFromTuple] from typing import List -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C t = a, b if int(): @@ -55,7 +63,9 @@ class C: pass [case testListAssignmentUnequalAmountToUnpack] from typing import List -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C def f() -> None: # needed because test parser tries to parse [a, b] as section header [a, b] = [a, b] @@ -79,7 +89,6 @@ reveal_type(c) # N: Revealed type is "builtins.list[builtins.int]" [builtins fixtures/list.pyi] [case testComprehensionShadowBinder] -# flags: --strict-optional def foo(x: object) -> None: if isinstance(x, str): [reveal_type(x) for x in [1, 2, 3]] # N: Revealed type is "builtins.int" diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index d523e5c08af8..423ba74eba72 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -12,14 +12,18 @@ reveal_type(g1) # N: Revealed type is "def (x: Literal['A['])" def f2(x: 'A B') -> None: pass # E: Invalid type comment or annotation def g2(x: Literal['A B']) -> None: pass +def h2(x: 'A|int') -> None: pass # E: Name "A" is not defined +def i2(x: Literal['A|B']) -> None: pass reveal_type(f2) # N: Revealed type is "def (x: Any)" reveal_type(g2) # N: Revealed type is "def (x: Literal['A B'])" +reveal_type(h2) # N: Revealed type is "def (x: Union[Any, builtins.int])" +reveal_type(i2) # N: Revealed type is "def (x: Literal['A|B'])" [builtins fixtures/tuple.pyi] [out] [case testLiteralInvalidTypeComment] from typing_extensions import Literal -def f(x): # E: syntax error in type comment "(A[) -> None" +def f(x): # E: Syntax error in type comment "(A[) -> None" # type: (A[) -> None pass @@ -64,7 +68,6 @@ reveal_type(bar) # N: Revealed type is "def (x: Tuple[Literal [out] [case testLiteralInsideOtherTypesTypeCommentsPython3] -# flags: --python-version 3.7 from typing import Tuple, Optional from typing_extensions import Literal @@ -278,7 +281,7 @@ reveal_type(c_bytes_wrapper_alias) # N: Revealed type is "__main__.Wrap[Liter [builtins fixtures/tuple.pyi] [out] -[case testLiteralUnicodeWeirdCharacters] +[case testLiteralUnicodeWeirdCharacters-skip_path_normalization] from typing import Any from typing_extensions import Literal @@ -334,7 +337,7 @@ a1 = b3 a1 = c3 # E: Incompatible types in assignment (expression has type "Literal['Ā¬b āˆ§ Ī»(p)']", variable has type "Literal['\x00Ā¬b āˆ§ Ī»(p)']") [builtins fixtures/tuple.pyi] -[out skip-path-normalization] +[out] [case testLiteralRenamingImportWorks] from typing_extensions import Literal as Foo @@ -398,29 +401,36 @@ from typing_extensions import Literal a1: Literal[4] b1: Literal[0x2a] c1: Literal[-300] +d1: Literal[+8] reveal_type(a1) # N: Revealed type is "Literal[4]" reveal_type(b1) # N: Revealed type is "Literal[42]" reveal_type(c1) # N: Revealed type is "Literal[-300]" +reveal_type(d1) # N: Revealed type is "Literal[8]" a2t = Literal[4] b2t = Literal[0x2a] c2t = Literal[-300] +d2t = Literal[+8] a2: a2t b2: b2t c2: c2t +d2: d2t reveal_type(a2) # N: Revealed type is "Literal[4]" reveal_type(b2) # N: Revealed type is "Literal[42]" reveal_type(c2) # N: Revealed type is "Literal[-300]" +reveal_type(d2) # N: Revealed type is "Literal[8]" def f1(x: Literal[4]) -> Literal[4]: pass def f2(x: Literal[0x2a]) -> Literal[0x2a]: pass def f3(x: Literal[-300]) -> Literal[-300]: pass +def f4(x: Literal[+8]) -> Literal[+8]: pass reveal_type(f1) # N: Revealed type is "def (x: Literal[4]) -> Literal[4]" reveal_type(f2) # N: Revealed type is "def (x: Literal[42]) -> Literal[42]" reveal_type(f3) # N: Revealed type is "def (x: Literal[-300]) -> Literal[-300]" +reveal_type(f4) # N: Revealed type is "def (x: Literal[8]) -> Literal[8]" [builtins fixtures/tuple.pyi] [out] @@ -478,7 +488,7 @@ reveal_type(f5) # N: Revealed type is "def (x: Literal['foo']) -> Literal['foo' [builtins fixtures/tuple.pyi] [out] -[case testLiteralBasicStrUsageSlashes] +[case testLiteralBasicStrUsageSlashes-skip_path_normalization] from typing_extensions import Literal a: Literal[r"foo\nbar"] @@ -487,7 +497,7 @@ b: Literal["foo\nbar"] reveal_type(a) reveal_type(b) [builtins fixtures/tuple.pyi] -[out skip-path-normalization] +[out] main:6: note: Revealed type is "Literal['foo\\nbar']" main:7: note: Revealed type is "Literal['foo\nbar']" @@ -591,7 +601,6 @@ from typing_extensions import Literal def dummy() -> int: return 3 a: Literal[3 + 4] # E: Invalid type: Literal[...] cannot contain arbitrary expressions b: Literal[" foo ".trim()] # E: Invalid type: Literal[...] cannot contain arbitrary expressions -c: Literal[+42] # E: Invalid type: Literal[...] cannot contain arbitrary expressions d: Literal[~12] # E: Invalid type: Literal[...] cannot contain arbitrary expressions e: Literal[dummy()] # E: Invalid type: Literal[...] cannot contain arbitrary expressions [builtins fixtures/tuple.pyi] @@ -611,8 +620,7 @@ from typing_extensions import Literal a: (1, 2, 3) # E: Syntax error in type annotation \ # N: Suggestion: Use Tuple[T1, ..., Tn] instead of (T1, ..., Tn) b: Literal[[1, 2, 3]] # E: Parameter 1 of Literal[...] is invalid -c: [1, 2, 3] # E: Bracketed expression "[...]" is not valid as a type \ - # N: Did you mean "List[...]"? +c: [1, 2, 3] # E: Bracketed expression "[...]" is not valid as a type [builtins fixtures/tuple.pyi] [out] @@ -659,7 +667,6 @@ def foo(b: Literal[T]) -> Tuple[T]: pass # E: Parameter 1 of Literal[...] is i -- [case testLiteralMultipleValues] -# flags: --strict-optional from typing_extensions import Literal a: Literal[1, 2, 3] b: Literal["a", "b", "c"] @@ -689,7 +696,6 @@ reveal_type(b) # N: Revealed type is "Union[Literal[1], Literal[2], Literal[3]] [out] [case testLiteralNestedUsage] -# flags: --strict-optional from typing_extensions import Literal a: Literal[Literal[3], 4, Literal["foo"]] @@ -753,16 +759,16 @@ Foo = Literal[5] [case testLiteralBiasTowardsAssumingForwardReferencesForTypeComments] from typing_extensions import Literal -a = None # type: Foo +a: Foo reveal_type(a) # N: Revealed type is "__main__.Foo" -b = None # type: "Foo" +b: "Foo" reveal_type(b) # N: Revealed type is "__main__.Foo" -c = None # type: Literal["Foo"] +c: Literal["Foo"] reveal_type(c) # N: Revealed type is "Literal['Foo']" -d = None # type: Literal[Foo] # E: Parameter 1 of Literal[...] is invalid +d: Literal[Foo] # E: Parameter 1 of Literal[...] is invalid class Foo: pass [builtins fixtures/tuple.pyi] @@ -818,7 +824,6 @@ foo(c) # E: Argument 1 to "foo" has incompatible type "Literal[4, 'foo']"; expe [out] [case testLiteralCheckSubtypingStrictOptional] -# flags: --strict-optional from typing import Any, NoReturn from typing_extensions import Literal @@ -1760,7 +1765,7 @@ reveal_type(func1(identity(b))) # N: Revealed type is "builtins.int" -- [case testLiteralMeets] -from typing import TypeVar, List, Callable, Union +from typing import TypeVar, List, Callable, Union, Optional from typing_extensions import Literal a: Callable[[Literal[1]], int] @@ -1794,17 +1799,19 @@ def f2(x: Literal[1], y: Literal[2]) -> None: pass def f3(x: Literal[1], y: int) -> None: pass def f4(x: Literal[1], y: object) -> None: pass def f5(x: Literal[1], y: Union[Literal[1], Literal[2]]) -> None: pass +def f6(x: Optional[Literal[1]], y: Optional[Literal[2]]) -> None: pass reveal_type(unify(f1)) # N: Revealed type is "Literal[1]" -reveal_type(unify(f2)) # N: Revealed type is "None" +if object(): + reveal_type(unify(f2)) # N: Revealed type is "Never" reveal_type(unify(f3)) # N: Revealed type is "Literal[1]" reveal_type(unify(f4)) # N: Revealed type is "Literal[1]" reveal_type(unify(f5)) # N: Revealed type is "Literal[1]" +reveal_type(unify(f6)) # N: Revealed type is "None" [builtins fixtures/list.pyi] [out] [case testLiteralMeetsWithStrictOptional] -# flags: --strict-optional from typing import TypeVar, Callable, Union from typing_extensions import Literal @@ -1821,7 +1828,7 @@ T = TypeVar('T') def unify(func: Callable[[T, T], None]) -> T: pass def func(x: Literal[1], y: Literal[2]) -> None: pass -reveal_type(unify(func)) # N: Revealed type is "" +reveal_type(unify(func)) # N: Revealed type is "Never" [builtins fixtures/list.pyi] [out] @@ -1831,7 +1838,6 @@ reveal_type(unify(func)) # N: Revealed type is "" -- [case testLiteralIntelligentIndexingTuples] -# flags: --strict-optional from typing import Tuple, NamedTuple, Optional, Final from typing_extensions import Literal @@ -1875,8 +1881,9 @@ reveal_type(tup2[idx3]) # N: Revealed type is "__main__.D" reveal_type(tup2[idx4]) # N: Revealed type is "__main__.E" reveal_type(tup2[idx_neg1]) # N: Revealed type is "__main__.E" tup2[idx5] # E: Tuple index out of range -reveal_type(tup2[idx2:idx4]) # N: Revealed type is "Tuple[__main__.C, __main__.D, fallback=__main__.Tup2Class]" -reveal_type(tup2[::idx2]) # N: Revealed type is "Tuple[__main__.A, __main__.C, __main__.E, fallback=__main__.Tup2Class]" +reveal_type(tup2[idx2:idx4]) # N: Revealed type is "Tuple[__main__.C, __main__.D]" +reveal_type(tup2[::idx2]) # N: Revealed type is "Tuple[__main__.A, __main__.C, __main__.E]" +tup3: Tup2Class = tup2[:] # E: Incompatible types in assignment (expression has type "Tuple[A, B, C, D, E]", variable has type "Tup2Class") [builtins fixtures/slice.pyi] [case testLiteralIntelligentIndexingTypedDict] @@ -1980,8 +1987,8 @@ reveal_type(tup1[0::idx1]) # N: Revealed type is "Union[Tuple[__main__.A, _ tup1[idx_bad] # E: Tuple index out of range reveal_type(tup2[idx1]) # N: Revealed type is "Union[__main__.B, __main__.C]" -reveal_type(tup2[idx1:idx2]) # N: Revealed type is "Union[Tuple[__main__.B, __main__.C, fallback=__main__.Tup2Class], Tuple[__main__.B, __main__.C, __main__.D, fallback=__main__.Tup2Class], Tuple[__main__.C, fallback=__main__.Tup2Class], Tuple[__main__.C, __main__.D, fallback=__main__.Tup2Class]]" -reveal_type(tup2[0::idx1]) # N: Revealed type is "Union[Tuple[__main__.A, __main__.B, __main__.C, __main__.D, __main__.E, fallback=__main__.Tup2Class], Tuple[__main__.A, __main__.C, __main__.E, fallback=__main__.Tup2Class]]" +reveal_type(tup2[idx1:idx2]) # N: Revealed type is "Union[Tuple[__main__.B, __main__.C], Tuple[__main__.B, __main__.C, __main__.D], Tuple[__main__.C], Tuple[__main__.C, __main__.D]]" +reveal_type(tup2[0::idx1]) # N: Revealed type is "Union[Tuple[__main__.A, __main__.B, __main__.C, __main__.D, __main__.E], Tuple[__main__.A, __main__.C, __main__.E]]" tup2[idx_bad] # E: Tuple index out of range [builtins fixtures/slice.pyi] [out] @@ -2013,7 +2020,7 @@ optional_keys: Literal["d", "e"] bad_keys: Literal["a", "bad"] reveal_type(test[good_keys]) # N: Revealed type is "Union[__main__.A, __main__.B]" -reveal_type(test.get(good_keys)) # N: Revealed type is "Union[__main__.A, __main__.B]" +reveal_type(test.get(good_keys)) # N: Revealed type is "Union[__main__.A, __main__.B, None]" reveal_type(test.get(good_keys, 3)) # N: Revealed type is "Union[__main__.A, Literal[3]?, __main__.B]" reveal_type(test.pop(optional_keys)) # N: Revealed type is "Union[__main__.D, __main__.E]" reveal_type(test.pop(optional_keys, 3)) # N: Revealed type is "Union[__main__.D, __main__.E, Literal[3]?]" @@ -2066,7 +2073,7 @@ x[bad_keys] # E: TypedDict "D1" has no key "d" \ # E: TypedDict "D2" has no key "a" reveal_type(x[good_keys]) # N: Revealed type is "Union[__main__.B, __main__.C]" -reveal_type(x.get(good_keys)) # N: Revealed type is "Union[__main__.B, __main__.C]" +reveal_type(x.get(good_keys)) # N: Revealed type is "Union[__main__.B, __main__.C, None]" reveal_type(x.get(good_keys, 3)) # N: Revealed type is "Union[__main__.B, Literal[3]?, __main__.C]" reveal_type(x.get(bad_keys)) # N: Revealed type is "builtins.object" reveal_type(x.get(bad_keys, 3)) # N: Revealed type is "builtins.object" @@ -2244,7 +2251,6 @@ force4(reveal_type(f.instancevar4)) # N: Revealed type is "None" [out] [case testLiteralFinalErasureInMutableDatastructures1] -# flags: --strict-optional from typing_extensions import Final var1: Final = [0, None] @@ -2497,7 +2503,7 @@ class Color(Enum): RED = 1 GREEN = 2 BLUE = 3 - + __ROUGE = RED def func(self) -> int: pass r: Literal[Color.RED] @@ -2506,6 +2512,8 @@ b: Literal[Color.BLUE] bad1: Literal[Color] # E: Parameter 1 of Literal[...] is invalid bad2: Literal[Color.func] # E: Parameter 1 of Literal[...] is invalid bad3: Literal[Color.func()] # E: Invalid type: Literal[...] cannot contain arbitrary expressions +# TODO: change the next line to use Color._Color__ROUGE when mypy implements name mangling +bad4: Literal[Color.__ROUGE] # E: Parameter 1 of Literal[...] is invalid def expects_color(x: Color) -> None: pass def expects_red(x: Literal[Color.RED]) -> None: pass @@ -2742,7 +2750,7 @@ def test() -> None: ... [builtins fixtures/bool.pyi] -[case testNegativeIntLiteral] +[case testUnaryOpLiteral] from typing_extensions import Literal a: Literal[-2] = -2 @@ -2750,14 +2758,24 @@ b: Literal[-1] = -1 c: Literal[0] = 0 d: Literal[1] = 1 e: Literal[2] = 2 +f: Literal[+1] = 1 +g: Literal[+2] = 2 +h: Literal[1] = +1 +i: Literal[+2] = 2 +j: Literal[+3] = +3 + +x: Literal[+True] = True # E: Invalid type: Literal[...] cannot contain arbitrary expressions +y: Literal[-True] = -1 # E: Invalid type: Literal[...] cannot contain arbitrary expressions +z: Literal[~0] = 0 # E: Invalid type: Literal[...] cannot contain arbitrary expressions [out] -[builtins fixtures/float.pyi] +[builtins fixtures/ops.pyi] [case testNegativeIntLiteralWithFinal] from typing_extensions import Literal, Final ONE: Final = 1 x: Literal[-1] = -ONE +y: Literal[+1] = +ONE TWO: Final = 2 THREE: Final = 3 @@ -2765,7 +2783,7 @@ THREE: Final = 3 err_code = -TWO if bool(): err_code = -THREE -[builtins fixtures/float.pyi] +[builtins fixtures/ops.pyi] [case testAliasForEnumTypeAsLiteral] from typing_extensions import Literal diff --git a/test-data/unit/check-lowercase.test b/test-data/unit/check-lowercase.test new file mode 100644 index 000000000000..ab6d68929f8e --- /dev/null +++ b/test-data/unit/check-lowercase.test @@ -0,0 +1,65 @@ + +[case testTupleLowercaseSettingOff] +# flags: --python-version 3.9 --force-uppercase-builtins +x = (3,) +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "Tuple[int]") +[builtins fixtures/tuple.pyi] + +[case testTupleLowercaseSettingOn] +# flags: --python-version 3.9 --no-force-uppercase-builtins +x = (3,) +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "tuple[int]") +[builtins fixtures/tuple.pyi] + +[case testListLowercaseSettingOff] +# flags: --python-version 3.9 --force-uppercase-builtins +x = [3] +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "List[int]") + +[case testListLowercaseSettingOn] +# flags: --python-version 3.9 --no-force-uppercase-builtins +x = [3] +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "list[int]") + +[case testDictLowercaseSettingOff] +# flags: --python-version 3.9 --force-uppercase-builtins +x = {"key": "value"} +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "Dict[str, str]") + +[case testDictLowercaseSettingOn] +# flags: --python-version 3.9 --no-force-uppercase-builtins +x = {"key": "value"} +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "dict[str, str]") + +[case testSetLowercaseSettingOff] +# flags: --python-version 3.9 --force-uppercase-builtins +x = {3} +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "Set[int]") +[builtins fixtures/set.pyi] + +[case testSetLowercaseSettingOn] +# flags: --python-version 3.9 --no-force-uppercase-builtins +x = {3} +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "set[int]") +[builtins fixtures/set.pyi] + +[case testTypeLowercaseSettingOff] +# flags: --python-version 3.9 --no-force-uppercase-builtins +x: type[type] +y: int + +y = x # E: Incompatible types in assignment (expression has type "type[type]", variable has type "int") + +[case testLowercaseSettingOnTypeAnnotationHint] +# flags: --python-version 3.9 --no-force-uppercase-builtins +x = [] # E: Need type annotation for "x" (hint: "x: list[] = ...") +y = {} # E: Need type annotation for "y" (hint: "y: dict[, ] = ...") +z = set() # E: Need type annotation for "z" (hint: "z: set[] = ...") +[builtins fixtures/primitives.pyi] + +[case testLowercaseSettingOnRevealTypeType] +# flags: --python-version 3.9 --no-force-uppercase-builtins +def f(t: type[int]) -> None: + reveal_type(t) # N: Revealed type is "type[builtins.int]" +reveal_type(f) # N: Revealed type is "def (t: type[builtins.int])" +[builtins fixtures/primitives.pyi] diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 4b8308310ae6..5fd48577e436 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -39,7 +39,7 @@ try: pass except m.Err: pass -except m.Bad: # E: Exception type must be derived from BaseException +except m.Bad: # E: Exception type must be derived from BaseException (or be a tuple of exception classes) pass [file m.py] class Err(BaseException): pass @@ -53,7 +53,7 @@ try: pass except Err: pass -except Bad: # E: Exception type must be derived from BaseException +except Bad: # E: Exception type must be derived from BaseException (or be a tuple of exception classes) pass [file m.py] class Err(BaseException): pass @@ -180,7 +180,8 @@ x = object() [case testChainedAssignmentAndImports] import m -i, s = None, None # type: (int, str) +i: int +s: str if int(): i = m.x if int(): @@ -404,14 +405,15 @@ _ = a _ = b _ = c _ = d -_ = e -_ = f # E: Name "f" is not defined +_ = e # E: Name "e" is not defined +_ = f _ = _g # E: Name "_g" is not defined [file m.py] __all__ = ['a'] __all__ += ('b',) __all__.append('c') -__all__.extend(('d', 'e')) +__all__.extend(('d', 'e', 'f')) +__all__.remove('e') a = b = c = d = e = f = _g = 1 [builtins fixtures/module_all.pyi] @@ -565,7 +567,6 @@ x = 1 x = 1 [case testAssignToFuncDefViaImport] -# flags: --strict-optional # Errors differ with the new analyzer. (Old analyzer gave error on the # input, which is maybe better, but no error about f, which seems @@ -584,6 +585,7 @@ x = 1+0 [case testConditionalImportAndAssign] +# flags: --no-strict-optional try: from m import x except: @@ -675,6 +677,7 @@ class B(A): ... [case testImportVariableAndAssignNone] +# flags: --no-strict-optional try: from m import x except: @@ -683,6 +686,7 @@ except: x = 1 [case testImportFunctionAndAssignNone] +# flags: --no-strict-optional try: from m import f except: @@ -708,6 +712,7 @@ except: def f(): pass [case testAssignToFuncDefViaGlobalDecl2] +# flags: --no-strict-optional import typing from m import f def g() -> None: @@ -720,6 +725,7 @@ def f(): pass [out] [case testAssignToFuncDefViaNestedModules] +# flags: --no-strict-optional import m.n m.n.f = None m.n.f = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Callable[[], Any]") @@ -729,6 +735,7 @@ def f(): pass [out] [case testAssignToFuncDefViaModule] +# flags: --no-strict-optional import m m.f = None m.f = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Callable[[], Any]") @@ -737,6 +744,7 @@ def f(): pass [out] [case testConditionalImportAndAssignNoneToModule] +# flags: --no-strict-optional if object(): import m else: @@ -757,6 +765,7 @@ else: [out] [case testImportAndAssignToModule] +# flags: --no-strict-optional import m m = None m.f(1) # E: Argument 1 to "f" has incompatible type "int"; expected "str" @@ -929,8 +938,8 @@ tmp/a/b/__init__.py:3: error: Name "a" is not defined [case testSubmoduleMixingLocalAndQualifiedNames] from a.b import MyClass -val1 = None # type: a.b.MyClass # E: Name "a" is not defined -val2 = None # type: MyClass +val1: a.b.MyClass # E: Name "a" is not defined +val2: MyClass [file a/__init__.py] [file a/b.py] @@ -1500,7 +1509,7 @@ import bar from foo import * def bar(y: AnyAlias) -> None: pass -l = None # type: ListAlias[int] +l: ListAlias[int] reveal_type(l) [file foo.py] @@ -1531,7 +1540,7 @@ Row = Dict[str, int] [case testImportStarAliasGeneric] from y import * -notes = None # type: G[X] +notes: G[X] another = G[X]() second = XT[str]() last = XT[G]() @@ -1560,7 +1569,7 @@ from typing import Any def bar(x: Any, y: AnyCallable) -> Any: return 'foo' -cb = None # type: AnyCallable +cb: AnyCallable reveal_type(cb) # N: Revealed type is "def (*Any, **Any) -> Any" [file foo.py] @@ -1809,6 +1818,8 @@ m = n # E: Cannot assign multiple modules to name "m" without explicit "types.M from stub import Iterable # E: Module "stub" does not explicitly export attribute "Iterable" from stub import D # E: Module "stub" does not explicitly export attribute "D" from stub import C +from stub import foo +from stub import bar # E: Module "stub" does not explicitly export attribute "bar" c = C() reveal_type(c.x) # N: Revealed type is "builtins.int" @@ -1819,6 +1830,8 @@ reveal_type(it) # N: Revealed type is "typing.Iterable[builtins.int]" from typing import Iterable from substub import C as C from substub import C as D +from package import foo as foo +import package.bar as bar def fun(x: Iterable[str]) -> Iterable[int]: pass @@ -1826,6 +1839,10 @@ def fun(x: Iterable[str]) -> Iterable[int]: pass class C: x: int +[file package/foo.pyi] + +[file package/bar.pyi] + [builtins fixtures/module.pyi] [case testNoReExportFromStubsMemberType] @@ -1853,7 +1870,8 @@ import stub reveal_type(stub.y) # N: Revealed type is "builtins.int" reveal_type(stub.z) # E: Module "stub" does not explicitly export attribute "z" \ - # N: Revealed type is "Any" + # N: Revealed type is "builtins.int" + [file stub.pyi] from substub import y as y @@ -2057,19 +2075,7 @@ def __getattr__(name): ... [builtins fixtures/module.pyi] -[case testModuleLevelGetattrNotStub36] -# flags: --python-version 3.6 -import has_getattr -reveal_type(has_getattr.any_attribute) # E: Module has no attribute "any_attribute" \ - # N: Revealed type is "Any" -[file has_getattr.py] -def __getattr__(name) -> str: ... - -[builtins fixtures/module.pyi] - -[case testModuleLevelGetattrNotStub37] -# flags: --python-version 3.7 - +[case testModuleLevelGetattrNotStub] import has_getattr reveal_type(has_getattr.any_attribute) # N: Revealed type is "builtins.str" @@ -2101,19 +2107,7 @@ def __getattr__(name: str) -> int: ... [builtins fixtures/module.pyi] -[case testModuleLevelGetattrImportFromNotStub36] -# flags: --python-version 3.6 -from non_stub import name # E: Module "non_stub" has no attribute "name" -reveal_type(name) # N: Revealed type is "Any" - -[file non_stub.py] -from typing import Any -def __getattr__(name: str) -> Any: ... - -[builtins fixtures/module.pyi] - -[case testModuleLevelGetattrImportFromNotStub37] -# flags: --python-version 3.7 +[case testModuleLevelGetattrImportFromNotStub] from non_stub import name reveal_type(name) # N: Revealed type is "Any" @@ -2148,7 +2142,6 @@ def __getattr__(name: str) -> int: ... [case testModuleLevelGetattrAssignedGood] -# flags: --python-version 3.7 import non_stub reveal_type(non_stub.name) # N: Revealed type is "builtins.int" @@ -2159,7 +2152,6 @@ def make_getattr_good() -> Callable[[str], int]: ... __getattr__ = make_getattr_good() # OK [case testModuleLevelGetattrAssignedBad] -# flags: --python-version 3.7 import non_stub reveal_type(non_stub.name) @@ -2171,10 +2163,9 @@ __getattr__ = make_getattr_bad() [out] tmp/non_stub.py:4: error: Invalid signature "Callable[[], int]" for "__getattr__" -main:3: note: Revealed type is "builtins.int" +main:2: note: Revealed type is "builtins.int" [case testModuleLevelGetattrImportedGood] -# flags: --python-version 3.7 import non_stub reveal_type(non_stub.name) # N: Revealed type is "builtins.int" @@ -2185,7 +2176,6 @@ from has_getattr import __getattr__ def __getattr__(name: str) -> int: ... [case testModuleLevelGetattrImportedBad] -# flags: --python-version 3.7 import non_stub reveal_type(non_stub.name) @@ -2197,7 +2187,7 @@ def __getattr__() -> int: ... [out] tmp/has_getattr.py:1: error: Invalid signature "Callable[[], int]" for "__getattr__" -main:3: note: Revealed type is "builtins.int" +main:2: note: Revealed type is "builtins.int" [builtins fixtures/module.pyi] @@ -3133,26 +3123,28 @@ import google.cloud from google.cloud import x [case testErrorFromGoogleCloud] -import google.cloud +import google.cloud # E: Cannot find implementation or library stub for module named "google.cloud" \ + # E: Cannot find implementation or library stub for module named "google" from google.cloud import x -import google.non_existent +import google.non_existent # E: Cannot find implementation or library stub for module named "google.non_existent" from google.non_existent import x -[out] -main:1: error: Library stubs not installed for "google.cloud" -main:1: note: Hint: "python3 -m pip install types-google-cloud-ndb" -main:1: note: (or run "mypy --install-types" to install all missing stub packages) -main:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports -main:1: error: Cannot find implementation or library stub for module named "google" -main:3: error: Cannot find implementation or library stub for module named "google.non_existent" + +import google.cloud.ndb # E: Library stubs not installed for "google.cloud.ndb" \ + # N: Hint: "python3 -m pip install types-google-cloud-ndb" \ + # N: (or run "mypy --install-types" to install all missing stub packages) \ + # N: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports +from google.cloud import ndb [case testMissingSubmoduleOfInstalledStubPackage] import bleach.xyz from bleach.abc import fgh [file bleach/__init__.pyi] [out] -main:1: error: Cannot find implementation or library stub for module named "bleach.xyz" +main:1: error: Library stubs not installed for "bleach.xyz" +main:1: note: Hint: "python3 -m pip install types-bleach" +main:1: note: (or run "mypy --install-types" to install all missing stub packages) main:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports -main:2: error: Cannot find implementation or library stub for module named "bleach.abc" +main:2: error: Library stubs not installed for "bleach.abc" [case testMissingSubmoduleOfInstalledStubPackageIgnored] # flags: --ignore-missing-imports @@ -3203,7 +3195,7 @@ from test1 import aaaa # E: Module "test1" has no attribute "aaaa" import b [file a.py] class Foo: - def frobnicate(self, x, *args, **kwargs): pass + def frobnicate(self, x: str, *args, **kwargs): pass [file b.py] from a import Foo class Bar(Foo): @@ -3211,22 +3203,21 @@ class Bar(Foo): [file b.py.2] from a import Foo class Bar(Foo): - def frobnicate(self, *args) -> None: pass + def frobnicate(self, *args: int) -> None: pass [file b.py.3] from a import Foo class Bar(Foo): - def frobnicate(self, *args) -> None: pass # type: ignore[override] # I know -[builtins fixtures/tuple.pyi] + def frobnicate(self, *args: int) -> None: pass # type: ignore[override] # I know [builtins fixtures/dict.pyi] [out1] tmp/b.py:3: error: Signature of "frobnicate" incompatible with supertype "Foo" tmp/b.py:3: note: Superclass: -tmp/b.py:3: note: def frobnicate(self, x: Any, *args: Any, **kwargs: Any) -> Any +tmp/b.py:3: note: def frobnicate(self, x: str, *args: Any, **kwargs: Any) -> Any tmp/b.py:3: note: Subclass: tmp/b.py:3: note: def frobnicate(self) -> None [out2] tmp/b.py:3: error: Signature of "frobnicate" incompatible with supertype "Foo" tmp/b.py:3: note: Superclass: -tmp/b.py:3: note: def frobnicate(self, x: Any, *args: Any, **kwargs: Any) -> Any +tmp/b.py:3: note: def frobnicate(self, x: str, *args: Any, **kwargs: Any) -> Any tmp/b.py:3: note: Subclass: -tmp/b.py:3: note: def frobnicate(self, *args: Any) -> None +tmp/b.py:3: note: def frobnicate(self, *args: int) -> None diff --git a/test-data/unit/check-multiple-inheritance.test b/test-data/unit/check-multiple-inheritance.test index a8d053f9504e..d03f2e35e1c4 100644 --- a/test-data/unit/check-multiple-inheritance.test +++ b/test-data/unit/check-multiple-inheritance.test @@ -668,3 +668,41 @@ class D1(B[str], C1): ... class D2(B[Union[int, str]], C2): ... class D3(C2, B[str]): ... class D4(B[str], C2): ... # E: Definition of "foo" in base class "A" is incompatible with definition in base class "C2" + + +[case testMultipleInheritanceOverridingOfFunctionsWithCallableInstances] +from typing import Any, Callable + +def dec1(f: Callable[[Any, int], None]) -> Callable[[Any, int], None]: ... + +class F: + def __call__(self, x: int) -> None: ... + +def dec2(f: Callable[[Any, int], None]) -> F: ... + +class B1: + def f(self, x: int) -> None: ... + +class B2: + @dec1 + def f(self, x: int) -> None: ... + +class B3: + @dec2 + def f(self, x: int) -> None: ... + +class B4: + f = F() + +class C12(B1, B2): ... +class C13(B1, B3): ... # E: Definition of "f" in base class "B1" is incompatible with definition in base class "B3" +class C14(B1, B4): ... # E: Definition of "f" in base class "B1" is incompatible with definition in base class "B4" +class C21(B2, B1): ... +class C23(B2, B3): ... # E: Definition of "f" in base class "B2" is incompatible with definition in base class "B3" +class C24(B2, B4): ... # E: Definition of "f" in base class "B2" is incompatible with definition in base class "B4" +class C31(B3, B1): ... +class C32(B3, B2): ... +class C34(B3, B4): ... +class C41(B4, B1): ... +class C42(B4, B2): ... +class C43(B4, B3): ... diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index 6b9f139f541c..a0d984b30279 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -2,7 +2,7 @@ from collections import namedtuple X = namedtuple('X', 'x y') -x = None # type: X +x: X a, b = x b = x[0] a = x[1] @@ -14,7 +14,7 @@ x[2] # E: Tuple index out of range from collections import namedtuple X = namedtuple('X', ('x', 'y')) -x = None # type: X +x: X a, b = x b = x[0] a = x[1] @@ -22,34 +22,26 @@ a, b, c = x # E: Need more than 2 values to unpack (3 expected) x[2] # E: Tuple index out of range [builtins fixtures/tuple.pyi] -[case testNamedTupleNoUnderscoreFields] +[case testNamedTupleInvalidFields] from collections import namedtuple -X = namedtuple('X', 'x, _y, _z') # E: "namedtuple()" field names cannot start with an underscore: _y, _z +X = namedtuple('X', 'x, _y') # E: "namedtuple()" field name "_y" starts with an underscore +Y = namedtuple('Y', ['x', '1']) # E: "namedtuple()" field name "1" is not a valid identifier +Z = namedtuple('Z', ['x', 'def']) # E: "namedtuple()" field name "def" is a keyword +A = namedtuple('A', ['x', 'x']) # E: "namedtuple()" has duplicate field name "x" [builtins fixtures/tuple.pyi] [case testNamedTupleAccessingAttributes] from collections import namedtuple X = namedtuple('X', 'x y') -x = None # type: X +x: X x.x x.y x.z # E: "X" has no attribute "z" [builtins fixtures/tuple.pyi] -[case testNamedTupleClassPython35] -# flags: --python-version 3.5 -from typing import NamedTuple - -class A(NamedTuple): - x = 3 # type: int -[builtins fixtures/tuple.pyi] -[out] -main:4: error: NamedTuple class syntax is only supported in Python 3.6 - -[case testNamedTupleClassInStubPython35] -# flags: --python-version 3.5 +[case testNamedTupleClassInStub] import foo [file foo.pyi] @@ -63,13 +55,13 @@ class A(NamedTuple): from collections import namedtuple X = namedtuple('X', 'x y') -x = None # type: X +x: X x.x = 5 # E: Property "x" defined in "X" is read-only x.y = 5 # E: Property "y" defined in "X" is read-only x.z = 5 # E: "X" has no attribute "z" class A(X): pass -a = None # type: A +a: A a.x = 5 # E: Property "x" defined in "X" is read-only a.y = 5 # E: Property "y" defined in "X" is read-only -- a.z = 5 # not supported yet @@ -136,13 +128,14 @@ E = namedtuple('E', 'a b', 0) [builtins fixtures/bool.pyi] [out] +main:4: error: Boolean literal expected as the "rename" argument to namedtuple() +main:5: error: Boolean literal expected as the "rename" argument to namedtuple() main:5: error: Argument "rename" to "namedtuple" has incompatible type "str"; expected "int" main:6: error: Unexpected keyword argument "unrecognized_arg" for "namedtuple" /test-data/unit/lib-stub/collections.pyi:3: note: "namedtuple" defined here main:7: error: Too many positional arguments for "namedtuple" [case testNamedTupleDefaults] -# flags: --python-version 3.7 from collections import namedtuple X = namedtuple('X', ['x', 'y'], defaults=(1,)) @@ -157,6 +150,23 @@ Z = namedtuple('Z', ['x', 'y'], defaults='not a tuple') # E: List or tuple lite [builtins fixtures/list.pyi] +[case testNamedTupleRename] +from collections import namedtuple + +X = namedtuple('X', ['abc', 'def'], rename=False) # E: "namedtuple()" field name "def" is a keyword +Y = namedtuple('Y', ['x', 'x', 'def', '42', '_x'], rename=True) +y = Y(x=0, _1=1, _2=2, _3=3, _4=4) +reveal_type(y.x) # N: Revealed type is "Any" +reveal_type(y._1) # N: Revealed type is "Any" +reveal_type(y._2) # N: Revealed type is "Any" +reveal_type(y._3) # N: Revealed type is "Any" +reveal_type(y._4) # N: Revealed type is "Any" +y._0 # E: "Y" has no attribute "_0" +y._5 # E: "Y" has no attribute "_5" +y._x # E: "Y" has no attribute "_x" + +[builtins fixtures/list.pyi] + [case testNamedTupleWithItemTypes] from typing import NamedTuple N = NamedTuple('N', [('a', int), @@ -292,7 +302,7 @@ A = NamedTuple('A', [('a', int), ('b', str)]) class B(A): pass a = A(1, '') b = B(1, '') -t = None # type: Tuple[int, str] +t: Tuple[int, str] if int(): b = a # E: Incompatible types in assignment (expression has type "A", variable has type "B") if int(): @@ -357,7 +367,7 @@ C(2).b from collections import namedtuple X = namedtuple('X', ['x', 'y']) -x = None # type: X +x: X reveal_type(x._asdict()) # N: Revealed type is "builtins.dict[builtins.str, Any]" [builtins fixtures/dict.pyi] @@ -366,7 +376,7 @@ reveal_type(x._asdict()) # N: Revealed type is "builtins.dict[builtins.str, Any from collections import namedtuple X = namedtuple('X', ['x', 'y']) -x = None # type: X +x: X reveal_type(x._replace()) # N: Revealed type is "Tuple[Any, Any, fallback=__main__.X]" x._replace(y=5) x._replace(x=3) @@ -391,7 +401,7 @@ X._replace(x=1, y=2) # E: Missing positional argument "_self" in call to "_repl from typing import NamedTuple X = NamedTuple('X', [('x', int), ('y', str)]) -x = None # type: X +x: X reveal_type(x._replace()) # N: Revealed type is "Tuple[builtins.int, builtins.str, fallback=__main__.X]" x._replace(x=5) x._replace(y=5) # E: Argument "y" to "_replace" of "X" has incompatible type "int"; expected "str" @@ -405,7 +415,7 @@ reveal_type(X._make([5, 'a'])) # N: Revealed type is "Tuple[builtins.int, built X._make('a b') # E: Argument 1 to "_make" of "X" has incompatible type "str"; expected "Iterable[Any]" -- # FIX: not a proper class method --- x = None # type: X +-- x: X -- reveal_type(x._make([5, 'a'])) # N: Revealed type is "Tuple[builtins.int, builtins.str, fallback=__main__.X]" -- x._make('a b') # E: Argument 1 to "_make" of "X" has incompatible type "str"; expected Iterable[Any] @@ -423,7 +433,7 @@ from typing import NamedTuple X = NamedTuple('X', [('x', int), ('y', str)]) reveal_type(X._source) # N: Revealed type is "builtins.str" -x = None # type: X +x: X reveal_type(x._source) # N: Revealed type is "builtins.str" [builtins fixtures/tuple.pyi] @@ -459,7 +469,7 @@ from typing import NamedTuple X = NamedTuple('X', [('x', int), ('y', str)]) reveal_type(X._field_types) # N: Revealed type is "builtins.dict[builtins.str, Any]" -x = None # type: X +x: X reveal_type(x._field_types) # N: Revealed type is "builtins.dict[builtins.str, Any]" [builtins fixtures/dict.pyi] @@ -472,7 +482,7 @@ def f(x: A) -> None: pass class B(NamedTuple('B', []), A): pass f(B()) -x = None # type: A +x: A if int(): x = B() @@ -482,7 +492,7 @@ def g(x: C) -> None: pass class D(NamedTuple('D', []), A): pass g(D()) # E: Argument 1 to "g" has incompatible type "D"; expected "C" -y = None # type: C +y: C if int(): y = D() # E: Incompatible types in assignment (expression has type "D", variable has type "C") [builtins fixtures/tuple.pyi] @@ -499,9 +509,9 @@ class A(NamedTuple('A', [('x', str)])): class B(A): pass -a = None # type: A +a: A a = A('').member() -b = None # type: B +b: B b = B('').member() a = B('') a = B('').member() @@ -511,14 +521,14 @@ a = B('').member() from typing import NamedTuple, TypeVar A = NamedTuple('A', [('x', str)]) reveal_type(A('hello')._replace(x='')) # N: Revealed type is "Tuple[builtins.str, fallback=__main__.A]" -a = None # type: A +a: A a = A('hello')._replace(x='') class B(A): pass reveal_type(B('hello')._replace(x='')) # N: Revealed type is "Tuple[builtins.str, fallback=__main__.B]" -b = None # type: B +b: B b = B('hello')._replace(x='') [builtins fixtures/tuple.pyi] @@ -618,16 +628,18 @@ tmp/b.py:4: note: Revealed type is "Tuple[Any, fallback=a.N]" tmp/b.py:7: note: Revealed type is "Tuple[Any, fallback=a.N]" [case testSimpleSelfReferentialNamedTuple] -# flags: --disable-recursive-aliases from typing import NamedTuple -class MyNamedTuple(NamedTuple): - parent: 'MyNamedTuple' # E: Cannot resolve name "MyNamedTuple" (possible cyclic definition) -def bar(nt: MyNamedTuple) -> MyNamedTuple: - return nt +def test() -> None: + class MyNamedTuple(NamedTuple): + parent: 'MyNamedTuple' # E: Cannot resolve name "MyNamedTuple" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope + + def bar(nt: MyNamedTuple) -> MyNamedTuple: + return nt -x: MyNamedTuple -reveal_type(x.parent) # N: Revealed type is "Any" + x: MyNamedTuple + reveal_type(x.parent) # N: Revealed type is "Any" [builtins fixtures/tuple.pyi] -- Some crazy self-referential named tuples and types dicts @@ -656,106 +668,111 @@ class B: [out] [case testSelfRefNT1] -# flags: --disable-recursive-aliases from typing import Tuple, NamedTuple -Node = NamedTuple('Node', [ - ('name', str), - ('children', Tuple['Node', ...]), # E: Cannot resolve name "Node" (possible cyclic definition) - ]) -n: Node -reveal_type(n) # N: Revealed type is "Tuple[builtins.str, builtins.tuple[Any, ...], fallback=__main__.Node]" +def test() -> None: + Node = NamedTuple('Node', [ + ('name', str), + ('children', Tuple['Node', ...]), # E: Cannot resolve name "Node" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope + ]) + n: Node + reveal_type(n) # N: Revealed type is "Tuple[builtins.str, builtins.tuple[Any, ...], fallback=__main__.Node@4]" [builtins fixtures/tuple.pyi] [case testSelfRefNT2] -# flags: --disable-recursive-aliases from typing import Tuple, NamedTuple -A = NamedTuple('A', [ - ('x', str), - ('y', Tuple['B', ...]), # E: Cannot resolve name "B" (possible cyclic definition) - ]) -class B(NamedTuple): - x: A - y: int +def test() -> None: + A = NamedTuple('A', [ + ('x', str), + ('y', Tuple['B', ...]), # E: Cannot resolve name "B" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope + ]) + class B(NamedTuple): + x: A + y: int -n: A -reveal_type(n) # N: Revealed type is "Tuple[builtins.str, builtins.tuple[Any, ...], fallback=__main__.A]" + n: A + reveal_type(n) # N: Revealed type is "Tuple[builtins.str, builtins.tuple[Any, ...], fallback=__main__.A@4]" [builtins fixtures/tuple.pyi] [case testSelfRefNT3] -# flags: --disable-recursive-aliases from typing import NamedTuple, Tuple -class B(NamedTuple): - x: Tuple[A, int] # E: Cannot resolve name "A" (possible cyclic definition) - y: int +def test() -> None: + class B(NamedTuple): + x: Tuple[A, int] # E: Cannot resolve name "A" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope + y: int -A = NamedTuple('A', [ - ('x', str), - ('y', 'B'), - ]) -n: B -m: A -reveal_type(n.x) # N: Revealed type is "Tuple[Any, builtins.int]" -reveal_type(m[0]) # N: Revealed type is "builtins.str" -lst = [m, n] -reveal_type(lst[0]) # N: Revealed type is "Tuple[builtins.object, builtins.object]" + A = NamedTuple('A', [ + ('x', str), + ('y', 'B'), + ]) + n: B + m: A + reveal_type(n.x) # N: Revealed type is "Tuple[Any, builtins.int]" + reveal_type(m[0]) # N: Revealed type is "builtins.str" + lst = [m, n] + reveal_type(lst[0]) # N: Revealed type is "Tuple[builtins.object, builtins.object]" [builtins fixtures/tuple.pyi] [case testSelfRefNT4] -# flags: --disable-recursive-aliases from typing import NamedTuple -class B(NamedTuple): - x: A # E: Cannot resolve name "A" (possible cyclic definition) - y: int +def test() -> None: + class B(NamedTuple): + x: A # E: Cannot resolve name "A" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope + y: int -class A(NamedTuple): - x: str - y: B + class A(NamedTuple): + x: str + y: B -n: A -reveal_type(n.y[0]) # N: Revealed type is "Any" + n: A + reveal_type(n.y[0]) # N: Revealed type is "Any" [builtins fixtures/tuple.pyi] [case testSelfRefNT5] -# flags: --disable-recursive-aliases from typing import NamedTuple -B = NamedTuple('B', [ - ('x', A), # E: Cannot resolve name "A" (possible cyclic definition) # E: Name "A" is used before definition - ('y', int), - ]) -A = NamedTuple('A', [ - ('x', str), - ('y', 'B'), - ]) -n: A -def f(m: B) -> None: pass -reveal_type(n) # N: Revealed type is "Tuple[builtins.str, Tuple[Any, builtins.int, fallback=__main__.B], fallback=__main__.A]" -reveal_type(f) # N: Revealed type is "def (m: Tuple[Any, builtins.int, fallback=__main__.B])" +def test() -> None: + B = NamedTuple('B', [ + ('x', A), # E: Cannot resolve name "A" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope \ + # E: Name "A" is used before definition + ('y', int), + ]) + A = NamedTuple('A', [ + ('x', str), + ('y', 'B'), + ]) + n: A + def f(m: B) -> None: pass + reveal_type(n) # N: Revealed type is "Tuple[builtins.str, Tuple[Any, builtins.int, fallback=__main__.B@4], fallback=__main__.A@8]" + reveal_type(f) # N: Revealed type is "def (m: Tuple[Any, builtins.int, fallback=__main__.B@4])" [builtins fixtures/tuple.pyi] [case testRecursiveNamedTupleInBases] -# flags: --disable-recursive-aliases from typing import List, NamedTuple, Union -Exp = Union['A', 'B'] # E: Cannot resolve name "Exp" (possible cyclic definition) \ - # E: Cannot resolve name "A" (possible cyclic definition) -class A(NamedTuple('A', [('attr', List[Exp])])): pass -class B(NamedTuple('B', [('val', object)])): pass +def test() -> None: + Exp = Union['A', 'B'] # E: Cannot resolve name "Exp" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope \ + # E: Cannot resolve name "A" (possible cyclic definition) + class A(NamedTuple('A', [('attr', List[Exp])])): pass + class B(NamedTuple('B', [('val', object)])): pass -def my_eval(exp: Exp) -> int: - reveal_type(exp) # N: Revealed type is "Union[Any, Tuple[builtins.object, fallback=__main__.B]]" + exp: Exp + reveal_type(exp) # N: Revealed type is "Union[Any, Tuple[builtins.object, fallback=__main__.B@6]]" if isinstance(exp, A): - my_eval(exp[0][0]) - return my_eval(exp.attr[0]) + reveal_type(exp[0][0]) # N: Revealed type is "Union[Any, Tuple[builtins.object, fallback=__main__.B@6]]" + reveal_type(exp.attr[0]) # N: Revealed type is "Union[Any, Tuple[builtins.object, fallback=__main__.B@6]]" if isinstance(exp, B): - return exp.val # E: Incompatible return value type (got "object", expected "int") - return 0 - -my_eval(A([B(1), B(2)])) # OK + reveal_type(exp.val) # N: Revealed type is "builtins.object" + reveal_type(A([B(1), B(2)])) # N: Revealed type is "Tuple[builtins.list[Union[Any, Tuple[builtins.object, fallback=__main__.B@6]]], fallback=__main__.A@5]" [builtins fixtures/isinstancelist.pyi] [out] @@ -782,17 +799,18 @@ tp = NamedTuple('tp', [('x', int)]) [out] [case testSubclassOfRecursiveNamedTuple] -# flags: --disable-recursive-aliases from typing import List, NamedTuple -class Command(NamedTuple): - subcommands: List['Command'] # E: Cannot resolve name "Command" (possible cyclic definition) +def test() -> None: + class Command(NamedTuple): + subcommands: List['Command'] # E: Cannot resolve name "Command" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope -class HelpCommand(Command): - pass + class HelpCommand(Command): + pass -hc = HelpCommand(subcommands=[]) -reveal_type(hc) # N: Revealed type is "Tuple[builtins.list[Any], fallback=__main__.HelpCommand]" + hc = HelpCommand(subcommands=[]) + reveal_type(hc) # N: Revealed type is "Tuple[builtins.list[Any], fallback=__main__.HelpCommand@7]" [builtins fixtures/list.pyi] [out] @@ -806,14 +824,20 @@ class Fraction(Real): [builtins fixtures/tuple.pyi] [case testForwardReferenceInNamedTuple] -from typing import NamedTuple +from typing import List, NamedTuple class A(NamedTuple): b: 'B' x: int + y: List['B'] class B: pass + +def f(a: A): + reveal_type(a.b) # N: Revealed type is "__main__.B" + reveal_type(a.x) # N: Revealed type is "builtins.int" + reveal_type(a.y) # N: Revealed type is "builtins.list[__main__.B]" [builtins fixtures/tuple.pyi] [case testTypeNamedTupleClassmethod] @@ -889,7 +913,7 @@ reveal_type(Child.class_method()) # N: Revealed type is "Tuple[builtins.str, fa [builtins fixtures/classmethod.pyi] [case testNamedTupleAsConditionalStrictOptionalDisabled] -# flags: --no-strict-optional +# flags: --no-strict-optional --warn-unreachable from typing import NamedTuple class C(NamedTuple): @@ -938,7 +962,18 @@ class A: def __init__(self) -> None: self.b = NamedTuple('x', [('s', str), ('n', int)]) # E: NamedTuple type as an attribute is not supported -reveal_type(A().b) # N: Revealed type is "Any" +reveal_type(A().b) # N: Revealed type is "typing.NamedTuple" +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-namedtuple.pyi] + + +[case testEmptyNamedTupleTypeRepr] +from typing import NamedTuple + +N = NamedTuple('N', []) +n: N +reveal_type(N) # N: Revealed type is "def () -> Tuple[(), fallback=__main__.N]" +reveal_type(n) # N: Revealed type is "Tuple[(), fallback=__main__.N]" [builtins fixtures/tuple.pyi] [case testNamedTupleWrongfile] @@ -983,7 +1018,7 @@ class Both2(Other, Bar): ... class Both3(Biz, Other): ... def print_namedtuple(obj: NamedTuple) -> None: - reveal_type(obj.name) # N: Revealed type is "builtins.str" + reveal_type(obj._fields) # N: Revealed type is "builtins.tuple[builtins.str, ...]" b1: Bar b2: Baz @@ -1046,7 +1081,7 @@ def good6() -> NamedTuple: def bad1() -> NamedTuple: return 1 # E: Incompatible return value type (got "int", expected "NamedTuple") def bad2() -> NamedTuple: - return () # E: Incompatible return value type (got "Tuple[]", expected "NamedTuple") + return () # E: Incompatible return value type (got "Tuple[()]", expected "NamedTuple") def bad3() -> NamedTuple: return (1, 2) # E: Incompatible return value type (got "Tuple[int, int]", expected "NamedTuple") @@ -1337,3 +1372,43 @@ class SNT(NT[int]): ... reveal_type(SNT("test", 42).meth()) # N: Revealed type is "Tuple[builtins.str, builtins.int, fallback=__main__.SNT]" [builtins fixtures/tuple.pyi] [typing fixtures/typing-namedtuple.pyi] + +[case testNoCrashUnsupportedNamedTuple] +from typing import NamedTuple +class Test: + def __init__(self, field) -> None: + self.Item = NamedTuple("x", [(field, str)]) # E: NamedTuple type as an attribute is not supported + self.item: self.Item # E: Name "self.Item" is not defined +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-namedtuple.pyi] + +[case testNoClassKeywordsForNamedTuple] +from typing import NamedTuple +class Test1(NamedTuple, x=1, y=2): # E: Unexpected keyword argument "x" for "__init_subclass__" of "NamedTuple" \ + # E: Unexpected keyword argument "y" for "__init_subclass__" of "NamedTuple" + ... + +class Meta(type): ... + +class Test2(NamedTuple, metaclass=Meta): # E: Unexpected keyword argument "metaclass" for "__init_subclass__" of "NamedTuple" + ... + +# Technically this would work, but it is just easier for the implementation: +class Test3(NamedTuple, metaclass=type): # E: Unexpected keyword argument "metaclass" for "__init_subclass__" of "NamedTuple" + ... +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-namedtuple.pyi] + + +[case testNamedTupleDunderReplace] +# flags: --python-version 3.13 +from typing import NamedTuple + +class A(NamedTuple): + x: int + +A(x=0).__replace__(x=1) +A(x=0).__replace__(x="asdf") # E: Argument "x" to "__replace__" of "A" has incompatible type "str"; expected "int" +A(x=0).__replace__(y=1) # E: Unexpected keyword argument "y" for "__replace__" of "A" +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-namedtuple.pyi] diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index f05e2aaf5c19..8612df9bc663 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -1,5 +1,4 @@ [case testNarrowingParentWithStrsBasic] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import NamedTuple, Tuple, Union from typing_extensions import Literal, TypedDict @@ -83,7 +82,6 @@ else: [builtins fixtures/primitives.pyi] [case testNarrowingParentWithEnumsBasic] -# flags: --python-version 3.7 from enum import Enum from dataclasses import dataclass from typing import NamedTuple, Tuple, Union @@ -173,7 +171,6 @@ else: [builtins fixtures/narrowing.pyi] [case testNarrowingParentWithIsInstanceBasic] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import NamedTuple, Tuple, Union from typing_extensions import TypedDict @@ -703,51 +700,50 @@ class FlipFlopStr: def mutate(self) -> None: self.state = "state-2" if self.state == "state-1" else "state-1" -def test1(switch: FlipFlopEnum) -> None: + +def test1(switch: FlipFlopStr) -> None: # Naively, we might assume the 'assert' here would narrow the type to - # Literal[State.A]. However, doing this ends up breaking a fair number of real-world + # Literal["state-1"]. However, doing this ends up breaking a fair number of real-world # code (usually test cases) that looks similar to this function: e.g. checks # to make sure a field was mutated to some particular value. # # And since mypy can't really reason about state mutation, we take a conservative # approach and avoid narrowing anything here. - assert switch.state == State.A - reveal_type(switch.state) # N: Revealed type is "__main__.State" + assert switch.state == "state-1" + reveal_type(switch.state) # N: Revealed type is "builtins.str" switch.mutate() - assert switch.state == State.B - reveal_type(switch.state) # N: Revealed type is "__main__.State" + assert switch.state == "state-2" + reveal_type(switch.state) # N: Revealed type is "builtins.str" def test2(switch: FlipFlopEnum) -> None: - # So strictly speaking, we ought to do the same thing with 'is' comparisons - # for the same reasons as above. But in practice, not too many people seem to - # know that doing 'some_enum is MyEnum.Value' is idiomatic. So in practice, - # this is probably good enough for now. + # This is the same thing as 'test1', except we use enums, which we allow to be narrowed + # to literals. - assert switch.state is State.A + assert switch.state == State.A reveal_type(switch.state) # N: Revealed type is "Literal[__main__.State.A]" switch.mutate() - assert switch.state is State.B # E: Non-overlapping identity check (left operand type: "Literal[State.A]", right operand type: "Literal[State.B]") + assert switch.state == State.B # E: Non-overlapping equality check (left operand type: "Literal[State.A]", right operand type: "Literal[State.B]") reveal_type(switch.state) # E: Statement is unreachable -def test3(switch: FlipFlopStr) -> None: - # This is the same thing as 'test1', except we try using str literals. +def test3(switch: FlipFlopEnum) -> None: + # Same thing, but using 'is' comparisons. Previously mypy's behaviour differed + # here, narrowing when using 'is', but not when using '=='. - assert switch.state == "state-1" - reveal_type(switch.state) # N: Revealed type is "builtins.str" + assert switch.state is State.A + reveal_type(switch.state) # N: Revealed type is "Literal[__main__.State.A]" switch.mutate() - assert switch.state == "state-2" - reveal_type(switch.state) # N: Revealed type is "builtins.str" + assert switch.state is State.B # E: Non-overlapping identity check (left operand type: "Literal[State.A]", right operand type: "Literal[State.B]") + reveal_type(switch.state) # E: Statement is unreachable [builtins fixtures/primitives.pyi] [case testNarrowingEqualityRequiresExplicitStrLiteral] -# flags: --strict-optional from typing_extensions import Literal, Final A_final: Final = "A" @@ -794,7 +790,7 @@ reveal_type(x_union) # N: Revealed type is "Union[Literal['A'], Literal['B' [builtins fixtures/primitives.pyi] [case testNarrowingEqualityRequiresExplicitEnumLiteral] -# flags: --strict-optional +from typing import Union from typing_extensions import Literal, Final from enum import Enum @@ -805,19 +801,19 @@ class Foo(Enum): A_final: Final = Foo.A A_literal: Literal[Foo.A] -# See comments in testNarrowingEqualityRequiresExplicitStrLiteral and -# testNarrowingEqualityFlipFlop for more on why we can't narrow here. +# Note this is unlike testNarrowingEqualityRequiresExplicitStrLiteral +# See also testNarrowingEqualityFlipFlop x1: Foo if x1 == Foo.A: - reveal_type(x1) # N: Revealed type is "__main__.Foo" + reveal_type(x1) # N: Revealed type is "Literal[__main__.Foo.A]" else: - reveal_type(x1) # N: Revealed type is "__main__.Foo" + reveal_type(x1) # N: Revealed type is "Literal[__main__.Foo.B]" x2: Foo if x2 == A_final: - reveal_type(x2) # N: Revealed type is "__main__.Foo" + reveal_type(x2) # N: Revealed type is "Literal[__main__.Foo.A]" else: - reveal_type(x2) # N: Revealed type is "__main__.Foo" + reveal_type(x2) # N: Revealed type is "Literal[__main__.Foo.B]" # But we let this narrow since there's an explicit literal in the RHS. x3: Foo @@ -825,6 +821,14 @@ if x3 == A_literal: reveal_type(x3) # N: Revealed type is "Literal[__main__.Foo.A]" else: reveal_type(x3) # N: Revealed type is "Literal[__main__.Foo.B]" + + +class SingletonFoo(Enum): + A = "A" + +def bar(x: Union[SingletonFoo, Foo], y: SingletonFoo) -> None: + if x == y: + reveal_type(x) # N: Revealed type is "Literal[__main__.SingletonFoo.A]" [builtins fixtures/primitives.pyi] [case testNarrowingEqualityDisabledForCustomEquality] @@ -870,7 +874,7 @@ else: [builtins fixtures/primitives.pyi] [case testNarrowingEqualityDisabledForCustomEqualityChain] -# flags: --strict-optional --strict-equality --warn-unreachable +# flags: --strict-equality --warn-unreachable from typing import Union from typing_extensions import Literal @@ -907,7 +911,7 @@ else: [builtins fixtures/primitives.pyi] [case testNarrowingUnreachableCases] -# flags: --strict-optional --strict-equality --warn-unreachable +# flags: --strict-equality --warn-unreachable from typing import Union from typing_extensions import Literal @@ -955,7 +959,7 @@ else: [builtins fixtures/primitives.pyi] [case testNarrowingUnreachableCases2] -# flags: --strict-optional --strict-equality --warn-unreachable +# flags: --strict-equality --warn-unreachable from typing import Union from typing_extensions import Literal @@ -1013,6 +1017,105 @@ else: reveal_type(true_or_false) # N: Revealed type is "Literal[False]" [builtins fixtures/primitives.pyi] + +[case testNarrowingIsInstanceFinalSubclass] +# flags: --warn-unreachable + +from typing import final + +class N: ... +@final +class F1: ... +@final +class F2: ... + +n: N +f1: F1 + +if isinstance(f1, F1): + reveal_type(f1) # N: Revealed type is "__main__.F1" +else: + reveal_type(f1) # E: Statement is unreachable + +if isinstance(n, F1): # E: Subclass of "N" and "F1" cannot exist: "F1" is final + reveal_type(n) # E: Statement is unreachable +else: + reveal_type(n) # N: Revealed type is "__main__.N" + +if isinstance(f1, N): # E: Subclass of "F1" and "N" cannot exist: "F1" is final + reveal_type(f1) # E: Statement is unreachable +else: + reveal_type(f1) # N: Revealed type is "__main__.F1" + +if isinstance(f1, F2): # E: Subclass of "F1" and "F2" cannot exist: "F1" is final \ + # E: Subclass of "F1" and "F2" cannot exist: "F2" is final + reveal_type(f1) # E: Statement is unreachable +else: + reveal_type(f1) # N: Revealed type is "__main__.F1" +[builtins fixtures/isinstance.pyi] + + +[case testNarrowingIsInstanceFinalSubclassWithUnions] +# flags: --warn-unreachable + +from typing import final, Union + +class N: ... +@final +class F1: ... +@final +class F2: ... + +n_f1: Union[N, F1] +n_f2: Union[N, F2] +f1_f2: Union[F1, F2] + +if isinstance(n_f1, F1): + reveal_type(n_f1) # N: Revealed type is "__main__.F1" +else: + reveal_type(n_f1) # N: Revealed type is "__main__.N" + +if isinstance(n_f2, F1): # E: Subclass of "N" and "F1" cannot exist: "F1" is final \ + # E: Subclass of "F2" and "F1" cannot exist: "F2" is final \ + # E: Subclass of "F2" and "F1" cannot exist: "F1" is final + reveal_type(n_f2) # E: Statement is unreachable +else: + reveal_type(n_f2) # N: Revealed type is "Union[__main__.N, __main__.F2]" + +if isinstance(f1_f2, F1): + reveal_type(f1_f2) # N: Revealed type is "__main__.F1" +else: + reveal_type(f1_f2) # N: Revealed type is "__main__.F2" +[builtins fixtures/isinstance.pyi] + + +[case testNarrowingIsSubclassFinalSubclassWithTypeVar] +# flags: --warn-unreachable + +from typing import final, Type, TypeVar + +@final +class A: ... +@final +class B: ... + +T = TypeVar("T", A, B) + +def f(cls: Type[T]) -> T: + if issubclass(cls, A): + reveal_type(cls) # N: Revealed type is "Type[__main__.A]" + x: bool + if x: + return A() + else: + return B() # E: Incompatible return value type (got "B", expected "A") + assert False + +reveal_type(f(A)) # N: Revealed type is "__main__.A" +reveal_type(f(B)) # N: Revealed type is "__main__.B" +[builtins fixtures/isinstance.pyi] + + [case testNarrowingLiteralIdentityCheck] from typing import Union from typing_extensions import Literal @@ -1055,7 +1158,6 @@ else: [builtins fixtures/primitives.pyi] [case testNarrowingBooleanIdentityCheck] -# flags: --strict-optional from typing import Optional from typing_extensions import Literal @@ -1078,7 +1180,6 @@ else: [builtins fixtures/primitives.pyi] [case testNarrowingBooleanTruthiness] -# flags: --strict-optional from typing import Optional from typing_extensions import Literal @@ -1100,7 +1201,6 @@ reveal_type(opt_bool_val) # N: Revealed type is "Union[builtins.bool, None]" [builtins fixtures/primitives.pyi] [case testNarrowingBooleanBoolOp] -# flags: --strict-optional from typing import Optional from typing_extensions import Literal @@ -1129,7 +1229,6 @@ reveal_type(x) # N: Revealed type is "builtins.bool" [builtins fixtures/primitives.pyi] [case testNarrowingTypedDictUsingEnumLiteral] -# flags: --python-version 3.6 from typing import Union from typing_extensions import TypedDict, Literal from enum import Enum @@ -1153,7 +1252,6 @@ def f(d: Union[Foo, Bar]) -> None: [builtins fixtures/dict.pyi] [case testNarrowingUsingMetaclass] -# flags: --strict-optional from typing import Type class M(type): @@ -1173,7 +1271,6 @@ def f(t: Type[C]) -> None: reveal_type(t) # N: Revealed type is "Type[__main__.C]" [case testNarrowingUsingTypeVar] -# flags: --strict-optional from typing import Type, TypeVar class A: pass @@ -1188,7 +1285,7 @@ def f(t: Type[T], a: A, b: B) -> None: reveal_type(a) # N: Revealed type is "__main__.A" if type(b) is t: - reveal_type(b) # N: Revealed type is "" + reveal_type(b) # N: Revealed type is "Never" else: reveal_type(b) # N: Revealed type is "__main__.B" @@ -1260,3 +1357,760 @@ def g() -> None: def foo(): ... foo() [builtins fixtures/dict.pyi] + + +[case testNarrowingOptionalEqualsNone] +from typing import Optional + +class A: ... + +val: Optional[A] + +if val == None: + reveal_type(val) # N: Revealed type is "Union[__main__.A, None]" +else: + reveal_type(val) # N: Revealed type is "Union[__main__.A, None]" +if val != None: + reveal_type(val) # N: Revealed type is "Union[__main__.A, None]" +else: + reveal_type(val) # N: Revealed type is "Union[__main__.A, None]" + +if val in (None,): + reveal_type(val) # N: Revealed type is "Union[__main__.A, None]" +else: + reveal_type(val) # N: Revealed type is "Union[__main__.A, None]" +if val not in (None,): + reveal_type(val) # N: Revealed type is "Union[__main__.A, None]" +else: + reveal_type(val) # N: Revealed type is "Union[__main__.A, None]" +[builtins fixtures/primitives.pyi] + +[case testNarrowingWithTupleOfTypes] +from typing import Tuple, Type + +class Base: ... + +class Impl1(Base): ... +class Impl2(Base): ... + +impls: Tuple[Type[Base], ...] = (Impl1, Impl2) +some: object + +if isinstance(some, impls): + reveal_type(some) # N: Revealed type is "__main__.Base" +else: + reveal_type(some) # N: Revealed type is "builtins.object" + +raw: Tuple[type, ...] +if isinstance(some, raw): + reveal_type(some) # N: Revealed type is "builtins.object" +else: + reveal_type(some) # N: Revealed type is "builtins.object" +[builtins fixtures/dict.pyi] + + +[case testNarrowingWithTupleOfTypesPy310Plus] +# flags: --python-version 3.10 +class Base: ... + +class Impl1(Base): ... +class Impl2(Base): ... + +some: int | Base + +impls: tuple[type[Base], ...] = (Impl1, Impl2) +if isinstance(some, impls): + reveal_type(some) # N: Revealed type is "__main__.Base" +else: + reveal_type(some) # N: Revealed type is "Union[builtins.int, __main__.Base]" + +raw: tuple[type, ...] +if isinstance(some, raw): + reveal_type(some) # N: Revealed type is "Union[builtins.int, __main__.Base]" +else: + reveal_type(some) # N: Revealed type is "Union[builtins.int, __main__.Base]" +[builtins fixtures/dict.pyi] + +[case testNarrowingWithAnyOps] +from typing import Any + +class C: ... +class D(C): ... +tp: Any + +c: C +if isinstance(c, tp) or isinstance(c, D): + reveal_type(c) # N: Revealed type is "Union[Any, __main__.D]" +else: + reveal_type(c) # N: Revealed type is "__main__.C" +reveal_type(c) # N: Revealed type is "__main__.C" + +c1: C +if isinstance(c1, tp) and isinstance(c1, D): + reveal_type(c1) # N: Revealed type is "Any" +else: + reveal_type(c1) # N: Revealed type is "__main__.C" +reveal_type(c1) # N: Revealed type is "__main__.C" + +c2: C +if isinstance(c2, D) or isinstance(c2, tp): + reveal_type(c2) # N: Revealed type is "Union[__main__.D, Any]" +else: + reveal_type(c2) # N: Revealed type is "__main__.C" +reveal_type(c2) # N: Revealed type is "__main__.C" + +c3: C +if isinstance(c3, D) and isinstance(c3, tp): + reveal_type(c3) # N: Revealed type is "Any" +else: + reveal_type(c3) # N: Revealed type is "__main__.C" +reveal_type(c3) # N: Revealed type is "__main__.C" + +t: Any +if isinstance(t, (list, tuple)) and isinstance(t, tuple): + reveal_type(t) # N: Revealed type is "builtins.tuple[Any, ...]" +else: + reveal_type(t) # N: Revealed type is "Any" +reveal_type(t) # N: Revealed type is "Any" +[builtins fixtures/isinstancelist.pyi] + +[case testNarrowingLenItemAndLenCompare] +from typing import Any + +x: Any +if len(x) == x: + reveal_type(x) # N: Revealed type is "Any" +[builtins fixtures/len.pyi] + +[case testNarrowingLenTuple] +from typing import Tuple, Union + +VarTuple = Union[Tuple[int, int], Tuple[int, int, int]] + +x: VarTuple +a = b = c = 0 +if len(x) == 3: + a, b, c = x +else: + a, b = x + +if len(x) != 3: + a, b = x +else: + a, b, c = x +[builtins fixtures/len.pyi] + +[case testNarrowingLenHomogeneousTuple] +from typing import Tuple + +x: Tuple[int, ...] +if len(x) == 3: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.int, builtins.int]" +else: + reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.int, ...]" + +if len(x) != 3: + reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.int, ...]" +else: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.int, builtins.int]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenTypeUnaffected] +from typing import Union, List + +x: Union[str, List[int]] +if len(x) == 3: + reveal_type(x) # N: Revealed type is "Union[builtins.str, builtins.list[builtins.int]]" +else: + reveal_type(x) # N: Revealed type is "Union[builtins.str, builtins.list[builtins.int]]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenAnyListElseNotAffected] +from typing import Any + +def f(self, value: Any) -> Any: + if isinstance(value, list) and len(value) == 0: + reveal_type(value) # N: Revealed type is "builtins.list[Any]" + return value + reveal_type(value) # N: Revealed type is "Any" + return None +[builtins fixtures/len.pyi] + +[case testNarrowingLenMultiple] +from typing import Tuple, Union + +VarTuple = Union[Tuple[int, int], Tuple[int, int, int]] + +x: VarTuple +y: VarTuple +if len(x) == len(y) == 3: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.int, builtins.int]" + reveal_type(y) # N: Revealed type is "Tuple[builtins.int, builtins.int, builtins.int]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenFinal] +from typing import Tuple, Union +from typing_extensions import Final + +VarTuple = Union[Tuple[int, int], Tuple[int, int, int]] + +x: VarTuple +fin: Final = 3 +if len(x) == fin: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.int, builtins.int]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenGreaterThan] +from typing import Tuple, Union + +VarTuple = Union[Tuple[int], Tuple[int, int], Tuple[int, int, int]] + +x: VarTuple +if len(x) > 1: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" +else: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int]" + +if len(x) < 2: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int]" +else: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" + +if len(x) >= 2: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" +else: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int]" + +if len(x) <= 2: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int], Tuple[builtins.int, builtins.int]]" +else: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.int, builtins.int]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenBothSidesUnionTuples] +from typing import Tuple, Union + +VarTuple = Union[ + Tuple[int], + Tuple[int, int], + Tuple[int, int, int], + Tuple[int, int, int, int], +] + +x: VarTuple +if 2 <= len(x) <= 3: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" +else: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int], Tuple[builtins.int, builtins.int, builtins.int, builtins.int]]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenGreaterThanHomogeneousTupleShort] +# flags: --enable-incomplete-feature=PreciseTupleTypes +from typing import Tuple + +VarTuple = Tuple[int, ...] + +x: VarTuple +if len(x) < 3: + reveal_type(x) # N: Revealed type is "Union[Tuple[()], Tuple[builtins.int], Tuple[builtins.int, builtins.int]]" +else: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.int, builtins.int, Unpack[builtins.tuple[builtins.int, ...]]]" +reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.int, ...]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenBiggerThanHomogeneousTupleLong] +# flags: --enable-incomplete-feature=PreciseTupleTypes +from typing import Tuple + +VarTuple = Tuple[int, ...] + +x: VarTuple +if len(x) < 30: + reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.int, ...]" +else: + reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.int, ...]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenBothSidesHomogeneousTuple] +# flags: --enable-incomplete-feature=PreciseTupleTypes +from typing import Tuple + +x: Tuple[int, ...] +if 1 < len(x) < 4: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" +else: + reveal_type(x) # N: Revealed type is "Union[Tuple[()], Tuple[builtins.int], Tuple[builtins.int, builtins.int, builtins.int, builtins.int, Unpack[builtins.tuple[builtins.int, ...]]]]" +reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.int, ...]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenUnionTupleUnreachable] +# flags: --warn-unreachable +from typing import Tuple, Union + +x: Union[Tuple[int, int], Tuple[int, int, int]] +if len(x) >= 4: + reveal_type(x) # E: Statement is unreachable +else: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" + +if len(x) < 2: + reveal_type(x) # E: Statement is unreachable +else: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenMixedTypes] +from typing import Tuple, List, Union + +x: Union[Tuple[int, int], Tuple[int, int, int], List[int]] +a = b = c = 0 +if len(x) == 3: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int, builtins.int], builtins.list[builtins.int]]" + a, b, c = x +else: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], builtins.list[builtins.int]]" + a, b = x + +if len(x) != 3: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], builtins.list[builtins.int]]" + a, b = x +else: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int, builtins.int], builtins.list[builtins.int]]" + a, b, c = x +[builtins fixtures/len.pyi] + +[case testNarrowingLenTypeVarTupleEquals] +from typing import Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +def foo(x: Tuple[int, Unpack[Ts], str]) -> None: + if len(x) == 5: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" + else: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" + + if len(x) != 5: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" + else: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenTypeVarTupleGreaterThan] +from typing import Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +def foo(x: Tuple[int, Unpack[Ts], str]) -> None: + if len(x) > 5: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" + reveal_type(x[5]) # N: Revealed type is "builtins.object" + reveal_type(x[-6]) # N: Revealed type is "builtins.object" + reveal_type(x[-1]) # N: Revealed type is "builtins.str" + else: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" + + if len(x) < 5: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" + else: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" + x[5] # E: Tuple index out of range \ + # N: Variadic tuple can have length 5 + x[-6] # E: Tuple index out of range \ + # N: Variadic tuple can have length 5 + x[2] # E: Tuple index out of range \ + # N: Variadic tuple can have length 2 + x[-3] # E: Tuple index out of range \ + # N: Variadic tuple can have length 2 +[builtins fixtures/len.pyi] + +[case testNarrowingLenTypeVarTupleUnreachable] +# flags: --warn-unreachable +from typing import Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +def foo(x: Tuple[int, Unpack[Ts], str]) -> None: + if len(x) == 1: + reveal_type(x) # E: Statement is unreachable + else: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" + + if len(x) != 1: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" + else: + reveal_type(x) # E: Statement is unreachable + +def bar(x: Tuple[int, Unpack[Ts], str]) -> None: + if len(x) >= 2: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" + else: + reveal_type(x) # E: Statement is unreachable + + if len(x) < 2: + reveal_type(x) # E: Statement is unreachable + else: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenVariadicTupleEquals] +from typing import Tuple +from typing_extensions import Unpack + +def foo(x: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None: + if len(x) == 4: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.float, builtins.float, builtins.str]" + else: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" + + if len(x) != 4: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" + else: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.float, builtins.float, builtins.str]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenVariadicTupleGreaterThan] +from typing import Tuple +from typing_extensions import Unpack + +def foo(x: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None: + if len(x) > 3: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.float, builtins.float, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" + else: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.str], Tuple[builtins.int, builtins.float, builtins.str]]" + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" + + if len(x) < 3: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.str]" + else: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.float, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenVariadicTupleUnreachable] +# flags: --warn-unreachable +from typing import Tuple +from typing_extensions import Unpack + +def foo(x: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None: + if len(x) == 1: + reveal_type(x) # E: Statement is unreachable + else: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" + + if len(x) != 1: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" + else: + reveal_type(x) # E: Statement is unreachable + +def bar(x: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None: + if len(x) >= 2: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" + else: + reveal_type(x) # E: Statement is unreachable + + if len(x) < 2: + reveal_type(x) # E: Statement is unreachable + else: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenBareExpressionPrecise] +# flags: --enable-incomplete-feature=PreciseTupleTypes +from typing import Tuple + +x: Tuple[int, ...] +assert x +reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.int, ...]]]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenBareExpressionTypeVarTuple] +from typing import Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +def test(*xs: Unpack[Ts]) -> None: + assert xs + xs[0] # OK +[builtins fixtures/len.pyi] + +[case testNarrowingLenBareExpressionWithNonePrecise] +# flags: --enable-incomplete-feature=PreciseTupleTypes +from typing import Tuple, Optional + +x: Optional[Tuple[int, ...]] +if x: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.int, ...]]]" +else: + reveal_type(x) # N: Revealed type is "Union[Tuple[()], None]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenBareExpressionWithNoneImprecise] +from typing import Tuple, Optional + +x: Optional[Tuple[int, ...]] +if x: + reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.int, ...]" +else: + reveal_type(x) # N: Revealed type is "Union[builtins.tuple[builtins.int, ...], None]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenMixWithAnyPrecise] +# flags: --enable-incomplete-feature=PreciseTupleTypes +from typing import Any + +x: Any +if isinstance(x, (list, tuple)) and len(x) == 0: + reveal_type(x) # N: Revealed type is "Union[Tuple[()], builtins.list[Any]]" +else: + reveal_type(x) # N: Revealed type is "Any" +reveal_type(x) # N: Revealed type is "Any" + +x1: Any +if isinstance(x1, (list, tuple)) and len(x1) > 1: + reveal_type(x1) # N: Revealed type is "Union[Tuple[Any, Any, Unpack[builtins.tuple[Any, ...]]], builtins.list[Any]]" +else: + reveal_type(x1) # N: Revealed type is "Any" +reveal_type(x1) # N: Revealed type is "Any" +[builtins fixtures/len.pyi] + +[case testNarrowingLenMixWithAnyImprecise] +from typing import Any + +x: Any +if isinstance(x, (list, tuple)) and len(x) == 0: + reveal_type(x) # N: Revealed type is "Union[Tuple[()], builtins.list[Any]]" +else: + reveal_type(x) # N: Revealed type is "Any" +reveal_type(x) # N: Revealed type is "Any" + +x1: Any +if isinstance(x1, (list, tuple)) and len(x1) > 1: + reveal_type(x1) # N: Revealed type is "Union[builtins.tuple[Any, ...], builtins.list[Any]]" +else: + reveal_type(x1) # N: Revealed type is "Any" +reveal_type(x1) # N: Revealed type is "Any" +[builtins fixtures/len.pyi] + +[case testNarrowingLenExplicitLiteralTypes] +from typing import Tuple, Union +from typing_extensions import Literal + +VarTuple = Union[ + Tuple[int], + Tuple[int, int], + Tuple[int, int, int], +] +x: VarTuple + +supported: Literal[2] +if len(x) == supported: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.int]" +else: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" + +not_supported_yet: Literal[2, 3] +if len(x) == not_supported_yet: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int], Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" +else: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int], Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenUnionOfVariadicTuples] +from typing import Tuple, Union + +x: Union[Tuple[int, ...], Tuple[str, ...]] +if len(x) == 2: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.str, builtins.str]]" +else: + reveal_type(x) # N: Revealed type is "Union[builtins.tuple[builtins.int, ...], builtins.tuple[builtins.str, ...]]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenUnionOfNamedTuples] +from typing import NamedTuple, Union + +class Point2D(NamedTuple): + x: int + y: int +class Point3D(NamedTuple): + x: int + y: int + z: int + +x: Union[Point2D, Point3D] +if len(x) == 2: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.int, fallback=__main__.Point2D]" +else: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.int, builtins.int, fallback=__main__.Point3D]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenTupleSubclass] +from typing import Tuple + +class Ints(Tuple[int, ...]): + size: int + +x: Ints +if len(x) == 2: + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.int, fallback=__main__.Ints]" + reveal_type(x.size) # N: Revealed type is "builtins.int" +else: + reveal_type(x) # N: Revealed type is "__main__.Ints" + reveal_type(x.size) # N: Revealed type is "builtins.int" + +reveal_type(x) # N: Revealed type is "__main__.Ints" +[builtins fixtures/len.pyi] + +[case testNarrowingLenTupleSubclassCustomNotAllowed] +from typing import Tuple + +class Ints(Tuple[int, ...]): + def __len__(self) -> int: + return 0 + +x: Ints +if len(x) > 2: + reveal_type(x) # N: Revealed type is "__main__.Ints" +else: + reveal_type(x) # N: Revealed type is "__main__.Ints" +[builtins fixtures/len.pyi] + +[case testNarrowingLenTupleSubclassPreciseNotAllowed] +# flags: --enable-incomplete-feature=PreciseTupleTypes +from typing import Tuple + +class Ints(Tuple[int, ...]): + size: int + +x: Ints +if len(x) > 2: + reveal_type(x) # N: Revealed type is "__main__.Ints" +else: + reveal_type(x) # N: Revealed type is "__main__.Ints" +[builtins fixtures/len.pyi] + +[case testNarrowingLenUnknownLen] +from typing import Any, Tuple, Union + +x: Union[Tuple[int, int], Tuple[int, int, int]] + +n: int +if len(x) == n: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" +else: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" + +a: Any +if len(x) == a: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" +else: + reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]" +[builtins fixtures/len.pyi] + +[case testNarrowingLenUnionWithUnreachable] +from typing import Union, Sequence + +def f(x: Union[int, Sequence[int]]) -> None: + if ( + isinstance(x, tuple) + and len(x) == 2 + and isinstance(x[0], int) + and isinstance(x[1], int) + ): + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.int]" +[builtins fixtures/len.pyi] + +[case testNarrowingIsSubclassNoneType1] +from typing import Type, Union + +def f(cls: Type[Union[None, int]]) -> None: + if issubclass(cls, int): + reveal_type(cls) # N: Revealed type is "Type[builtins.int]" + else: + reveal_type(cls) # N: Revealed type is "Type[None]" +[builtins fixtures/isinstance.pyi] + +[case testNarrowingIsSubclassNoneType2] +from typing import Type, Union + +def f(cls: Type[Union[None, int]]) -> None: + if issubclass(cls, type(None)): + reveal_type(cls) # N: Revealed type is "Type[None]" + else: + reveal_type(cls) # N: Revealed type is "Type[builtins.int]" +[builtins fixtures/isinstance.pyi] + +[case testNarrowingIsSubclassNoneType3] +from typing import Type, Union + +NoneType_ = type(None) + +def f(cls: Type[Union[None, int]]) -> None: + if issubclass(cls, NoneType_): + reveal_type(cls) # N: Revealed type is "Type[None]" + else: + reveal_type(cls) # N: Revealed type is "Type[builtins.int]" +[builtins fixtures/isinstance.pyi] + +[case testNarrowingIsSubclassNoneType4] +# flags: --python-version 3.10 + +from types import NoneType +from typing import Type, Union + +def f(cls: Type[Union[None, int]]) -> None: + if issubclass(cls, NoneType): + reveal_type(cls) # N: Revealed type is "Type[None]" + else: + reveal_type(cls) # N: Revealed type is "Type[builtins.int]" +[builtins fixtures/isinstance.pyi] + +[case testNarrowingIsInstanceNoIntersectionWithFinalTypeAndNoneType] +# flags: --warn-unreachable --python-version 3.10 + +from types import NoneType +from typing import final + +class X: ... +class Y: ... +@final +class Z: ... + +x: X + +if isinstance(x, (Y, Z)): + reveal_type(x) # N: Revealed type is "__main__." +if isinstance(x, (Y, NoneType)): + reveal_type(x) # N: Revealed type is "__main__.1" +if isinstance(x, (Y, Z, NoneType)): + reveal_type(x) # N: Revealed type is "__main__.2" +if isinstance(x, (Z, NoneType)): # E: Subclass of "X" and "Z" cannot exist: "Z" is final \ + # E: Subclass of "X" and "NoneType" cannot exist: "NoneType" is final + reveal_type(x) # E: Statement is unreachable + +[builtins fixtures/isinstance.pyi] + +[case testTypeNarrowingReachableNegative] +# flags: --warn-unreachable +from typing import Literal + +x: Literal[-1] + +if x == -1: + assert True + +[typing fixtures/typing-medium.pyi] +[builtins fixtures/ops.pyi] + +[case testTypeNarrowingReachableNegativeUnion] +from typing import Literal + +x: Literal[-1, 1] + +if x == -1: + reveal_type(x) # N: Revealed type is "Literal[-1]" +else: + reveal_type(x) # N: Revealed type is "Literal[1]" + +[typing fixtures/typing-medium.pyi] +[builtins fixtures/ops.pyi] diff --git a/test-data/unit/check-native-int.test b/test-data/unit/check-native-int.test index 1e945d0af27d..2f852ca522c5 100644 --- a/test-data/unit/check-native-int.test +++ b/test-data/unit/check-native-int.test @@ -69,7 +69,6 @@ reveal_type(join(a, n64)) # N: Revealed type is "Any" [builtins fixtures/dict.pyi] [case testNativeIntMeets] -# flags: --strict-optional from typing import TypeVar, Callable, Any from mypy_extensions import i32, i64 @@ -87,8 +86,10 @@ reveal_type(meet(f32, f)) # N: Revealed type is "mypy_extensions.i32" reveal_type(meet(f, f32)) # N: Revealed type is "mypy_extensions.i32" reveal_type(meet(f64, f)) # N: Revealed type is "mypy_extensions.i64" reveal_type(meet(f, f64)) # N: Revealed type is "mypy_extensions.i64" -reveal_type(meet(f32, f64)) # N: Revealed type is "" -reveal_type(meet(f64, f32)) # N: Revealed type is "" +if object(): + reveal_type(meet(f32, f64)) # N: Revealed type is "Never" +if object(): + reveal_type(meet(f64, f32)) # N: Revealed type is "Never" reveal_type(meet(f, fa)) # N: Revealed type is "builtins.int" reveal_type(meet(f32, fa)) # N: Revealed type is "mypy_extensions.i32" @@ -128,7 +129,6 @@ reveal_type(y) # N: Revealed type is "builtins.int" [builtins fixtures/dict.pyi] [case testNativeIntFloatConversion] -# flags: --strict-optional from typing import TypeVar, Callable from mypy_extensions import i32 @@ -148,8 +148,10 @@ def meet(c1: Callable[[T], None], c2: Callable[[T], None]) -> T: def ff(x: float) -> None: pass def fi32(x: i32) -> None: pass -reveal_type(meet(ff, fi32)) # N: Revealed type is "" -reveal_type(meet(fi32, ff)) # N: Revealed type is "" +if object(): + reveal_type(meet(ff, fi32)) # N: Revealed type is "Never" +if object(): + reveal_type(meet(fi32, ff)) # N: Revealed type is "Never" [builtins fixtures/dict.pyi] [case testNativeIntForLoopRange] diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 99f4141a4d64..7cbed5637c3a 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -434,13 +434,14 @@ def main() -> None: x # E: Name "x" is not defined [case testNewAnalyzerCyclicDefinitions] -# flags: --disable-recursive-aliases --disable-error-code used-before-def +# flags: --disable-error-code used-before-def gx = gy # E: Cannot resolve name "gy" (possible cyclic definition) gy = gx def main() -> None: class C: def meth(self) -> None: - lx = ly # E: Cannot resolve name "ly" (possible cyclic definition) + lx = ly # E: Cannot resolve name "ly" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope ly = lx [case testNewAnalyzerCyclicDefinitionCrossModule] @@ -992,7 +993,7 @@ class SubO(Out): pass o: SubO -reveal_type(SubO._make) # N: Revealed type is "def (iterable: typing.Iterable[Any], *, new: Any =, len: Any =) -> Tuple[Tuple[builtins.str, __main__.Other, fallback=__main__.In], __main__.Other, fallback=__main__.SubO]" +reveal_type(SubO._make) # N: Revealed type is "def (iterable: typing.Iterable[Any]) -> Tuple[Tuple[builtins.str, __main__.Other, fallback=__main__.In], __main__.Other, fallback=__main__.SubO]" reveal_type(o._replace(y=Other())) # N: Revealed type is "Tuple[Tuple[builtins.str, __main__.Other, fallback=__main__.In], __main__.Other, fallback=__main__.SubO]" [builtins fixtures/tuple.pyi] @@ -1009,7 +1010,7 @@ o: Out reveal_type(o) # N: Revealed type is "Tuple[Tuple[builtins.str, __main__.Other, fallback=__main__.In], __main__.Other, fallback=__main__.Out]" reveal_type(o.x) # N: Revealed type is "Tuple[builtins.str, __main__.Other, fallback=__main__.In]" reveal_type(o.x.t) # N: Revealed type is "__main__.Other" -reveal_type(Out._make) # N: Revealed type is "def (iterable: typing.Iterable[Any], *, new: Any =, len: Any =) -> Tuple[Tuple[builtins.str, __main__.Other, fallback=__main__.In], __main__.Other, fallback=__main__.Out]" +reveal_type(Out._make) # N: Revealed type is "def (iterable: typing.Iterable[Any]) -> Tuple[Tuple[builtins.str, __main__.Other, fallback=__main__.In], __main__.Other, fallback=__main__.Out]" [builtins fixtures/tuple.pyi] [case testNewAnalyzerIncompleteRefShadowsBuiltin1] @@ -1495,22 +1496,25 @@ reveal_type(x[0][0]) # N: Revealed type is "__main__.C" [builtins fixtures/list.pyi] [case testNewAnalyzerAliasToNotReadyDirectBase] -# flags: --disable-recursive-aliases --disable-error-code used-before-def +# flags: --disable-error-code used-before-def from typing import List -x: B -B = List[C] -class C(B): pass +def test() -> None: + x: B + B = List[C] + class C(B): pass -reveal_type(x) -reveal_type(x[0][0]) + reveal_type(x) + reveal_type(x[0][0]) [builtins fixtures/list.pyi] [out] -main:4: error: Cannot resolve name "B" (possible cyclic definition) main:5: error: Cannot resolve name "B" (possible cyclic definition) -main:5: error: Cannot resolve name "C" (possible cyclic definition) -main:8: note: Revealed type is "Any" +main:5: note: Recursive types are not allowed at function scope +main:6: error: Cannot resolve name "B" (possible cyclic definition) +main:6: note: Recursive types are not allowed at function scope +main:6: error: Cannot resolve name "C" (possible cyclic definition) main:9: note: Revealed type is "Any" +main:10: note: Revealed type is "Any" [case testNewAnalyzerAliasToNotReadyTwoDeferralsFunction] # flags: --disable-error-code used-before-def @@ -1530,25 +1534,21 @@ reveal_type(f) # N: Revealed type is "def (x: builtins.list[a.C]) -> builtins.l [builtins fixtures/list.pyi] [case testNewAnalyzerAliasToNotReadyDirectBaseFunction] -# flags: --disable-recursive-aliases --disable-error-code used-before-def +# flags: --disable-error-code used-before-def import a [file a.py] from typing import List from b import D def f(x: B) -> List[B]: ... -B = List[C] # E +B = List[C] class C(B): pass [file b.py] from a import f class D: ... -reveal_type(f) # N +reveal_type(f) # N: Revealed type is "def (x: builtins.list[a.C]) -> builtins.list[builtins.list[a.C]]" [builtins fixtures/list.pyi] -[out] -tmp/b.py:3: note: Revealed type is "def (x: builtins.list[Any]) -> builtins.list[builtins.list[Any]]" -tmp/a.py:5: error: Cannot resolve name "B" (possible cyclic definition) -tmp/a.py:5: error: Cannot resolve name "C" (possible cyclic definition) [case testNewAnalyzerAliasToNotReadyMixed] from typing import List, Union @@ -2118,25 +2118,29 @@ class B(List[C]): [builtins fixtures/list.pyi] [case testNewAnalyzerNewTypeForwardClassAliasDirect] -# flags: --disable-recursive-aliases --disable-error-code used-before-def +# flags: --disable-error-code used-before-def from typing import NewType, List -x: D -reveal_type(x[0][0]) +def test() -> None: + x: D + reveal_type(x[0][0]) -D = List[C] -C = NewType('C', 'B') + D = List[C] + C = NewType('C', 'B') -class B(D): - pass + class B(D): + pass [builtins fixtures/list.pyi] [out] -main:4: error: Cannot resolve name "D" (possible cyclic definition) -main:5: note: Revealed type is "Any" -main:7: error: Cannot resolve name "D" (possible cyclic definition) -main:7: error: Cannot resolve name "C" (possible cyclic definition) -main:8: error: Argument 2 to NewType(...) must be a valid type -main:8: error: Cannot resolve name "B" (possible cyclic definition) +main:5: error: Cannot resolve name "D" (possible cyclic definition) +main:5: note: Recursive types are not allowed at function scope +main:6: note: Revealed type is "Any" +main:8: error: Cannot resolve name "D" (possible cyclic definition) +main:8: note: Recursive types are not allowed at function scope +main:8: error: Cannot resolve name "C" (possible cyclic definition) +main:9: error: Argument 2 to NewType(...) must be a valid type +main:9: error: Cannot resolve name "B" (possible cyclic definition) +main:9: note: Recursive types are not allowed at function scope -- Copied from check-classes.test (tricky corner cases). [case testNewAnalyzerNoCrashForwardRefToBrokenDoubleNewTypeClass] @@ -2149,26 +2153,29 @@ x: C class C: def frob(self, foos: Dict[Any, Foos]) -> None: foo = foos.get(1) + assert foo dict(foo) [builtins fixtures/dict.pyi] [case testNewAnalyzerForwardTypeAliasInBase] -# flags: --disable-recursive-aliases from typing import List, Generic, TypeVar, NamedTuple T = TypeVar('T') -class C(A, B): # E: Cannot resolve name "A" (possible cyclic definition) - pass -class G(Generic[T]): pass -A = G[C] # E: Cannot resolve name "A" (possible cyclic definition) -class B(NamedTuple): - x: int +def test() -> None: + class C(A, B): # E: Cannot resolve name "A" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope + pass + class G(Generic[T]): pass + A = G[C] # E: Cannot resolve name "A" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope + class B(NamedTuple): + x: int -y: C -reveal_type(y.x) # N: Revealed type is "builtins.int" -reveal_type(y[0]) # N: Revealed type is "builtins.int" -x: A -reveal_type(x) # N: Revealed type is "__main__.G[Tuple[builtins.int, fallback=__main__.C]]" + y: C + reveal_type(y.x) # N: Revealed type is "builtins.int" + reveal_type(y[0]) # N: Revealed type is "builtins.int" + x: A + reveal_type(x) # N: Revealed type is "__main__.G@7[Tuple[builtins.int, fallback=__main__.C@5]]" [builtins fixtures/list.pyi] [case testNewAnalyzerDuplicateTypeVar] @@ -2570,20 +2577,7 @@ import n [file n.pyi] class C: pass -[case testNewAnalyzerModuleGetAttrInPython36] -# flags: --python-version 3.6 -import m -import n - -x: m.n.C # E: Name "m.n.C" is not defined -y: n.D # E: Name "n.D" is not defined -[file m.py] -import n -[file n.py] -def __getattr__(x): pass - -[case testNewAnalyzerModuleGetAttrInPython37] -# flags: --python-version 3.7 +[case testNewAnalyzerModuleGetAttr] import m import n @@ -2595,9 +2589,9 @@ import n def __getattr__(x): pass [case testNewAnalyzerReportLoopInMRO2] -# flags: --disable-recursive-aliases def f() -> None: - class A(A): ... # E: Cannot resolve name "A" (possible cyclic definition) + class A(A): ... # E: Cannot resolve name "A" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope [case testNewAnalyzerUnsupportedBaseClassInsideFunction] class C: @@ -3218,8 +3212,7 @@ class User: self.first_name = value def __init__(self, name: str) -> None: - self.name = name # E: Cannot assign to a method \ - # E: Incompatible types in assignment (expression has type "str", variable has type "Callable[..., Any]") + self.name = name # E: Cannot assign to a method [case testNewAnalyzerMemberNameMatchesTypedDict] from typing import Union, Any diff --git a/test-data/unit/check-newsyntax.test b/test-data/unit/check-newsyntax.test index 63284c34bd8b..3ed4c6d3d8e2 100644 --- a/test-data/unit/check-newsyntax.test +++ b/test-data/unit/check-newsyntax.test @@ -1,15 +1,8 @@ -[case testNewSyntaxRequire36] -# flags: --python-version 3.5 -x: int = 5 # E: Variable annotation syntax is only supported in Python 3.6 and greater -[out] - [case testNewSyntaxSyntaxError] -# flags: --python-version 3.6 x: int: int # E: invalid syntax [out] [case testNewSyntaxBasics] -# flags: --python-version 3.6 x: int x = 5 y: int = 5 @@ -19,11 +12,10 @@ a = 5 # E: Incompatible types in assignment (expression has type "int", variabl b: str = 5 # E: Incompatible types in assignment (expression has type "int", variable has type "str") zzz: int -zzz: str # E: Name "zzz" already defined on line 10 +zzz: str # E: Name "zzz" already defined on line 9 [out] [case testNewSyntaxWithDict] -# flags: --python-version 3.6 from typing import Dict, Any d: Dict[int, str] = {} @@ -34,7 +26,6 @@ d['ab'] = 'ab' # E: Invalid index type "str" for "Dict[int, str]"; expected typ [out] [case testNewSyntaxWithRevealType] -# flags: --python-version 3.6 from typing import Dict def tst_local(dct: Dict[int, T]) -> Dict[T, int]: @@ -46,7 +37,6 @@ reveal_type(tst_local({1: 'a'})) # N: Revealed type is "builtins.dict[builtins. [out] [case testNewSyntaxWithInstanceVars] -# flags: --python-version 3.6 class TstInstance: a: str def __init__(self) -> None: @@ -59,20 +49,17 @@ TstInstance().a = 'ab' [out] [case testNewSyntaxWithClassVars] -# flags: --strict-optional --python-version 3.6 class CCC: a: str = None # E: Incompatible types in assignment (expression has type "None", variable has type "str") [out] [case testNewSyntaxWithStrictOptional] -# flags: --strict-optional --python-version 3.6 strict: int strict = None # E: Incompatible types in assignment (expression has type "None", variable has type "int") strict2: int = None # E: Incompatible types in assignment (expression has type "None", variable has type "int") [out] [case testNewSyntaxWithStrictOptionalFunctions] -# flags: --strict-optional --python-version 3.6 def f() -> None: x: int if int(): @@ -80,7 +67,6 @@ def f() -> None: [out] [case testNewSyntaxWithStrictOptionalClasses] -# flags: --strict-optional --python-version 3.6 class C: def meth(self) -> None: x: int = None # E: Incompatible types in assignment (expression has type "None", variable has type "int") @@ -88,30 +74,18 @@ class C: [out] [case testNewSyntaxSpecialAssign] -# flags: --python-version 3.6 class X: x: str x[0]: int x.x: int [out] -main:4: error: Unexpected type declaration -main:4: error: Unsupported target for indexed assignment ("str") -main:5: error: Type cannot be declared in assignment to non-self attribute -main:5: error: "str" has no attribute "x" - -[case testNewSyntaxAsyncComprehensionError] -# flags: --python-version 3.5 -async def f(): - results = [i async for i in aiter() if i % 2] # E: Async comprehensions are only supported in Python 3.6 and greater - - -[case testNewSyntaxFstringError] -# flags: --python-version 3.5 -f'' # E: Format strings are only supported in Python 3.6 and greater +main:3: error: Unexpected type declaration +main:3: error: Unsupported target for indexed assignment ("str") +main:4: error: Type cannot be declared in assignment to non-self attribute +main:4: error: "str" has no attribute "x" [case testNewSyntaxFStringBasics] -# flags: --python-version 3.6 f'foobar' f'{"foobar"}' f'foo{"bar"}' @@ -123,22 +97,19 @@ a = f'{"foobar"}' [builtins fixtures/f_string.pyi] [case testNewSyntaxFStringExpressionsOk] -# flags: --python-version 3.6 f'.{1 + 1}.' f'.{1 + 1}.{"foo" + "bar"}' [builtins fixtures/f_string.pyi] [case testNewSyntaxFStringExpressionsErrors] -# flags: --python-version 3.6 f'{1 + ""}' f'.{1 + ""}' [builtins fixtures/f_string.pyi] [out] +main:1: error: Unsupported operand types for + ("int" and "str") main:2: error: Unsupported operand types for + ("int" and "str") -main:3: error: Unsupported operand types for + ("int" and "str") [case testNewSyntaxFStringParseFormatOptions] -# flags: --python-version 3.6 value = 10.5142 width = 10 precision = 4 @@ -146,7 +117,6 @@ f'result: {value:{width}.{precision}}' [builtins fixtures/f_string.pyi] [case testNewSyntaxFStringSingleField] -# flags: --python-version 3.6 v = 1 reveal_type(f'{v}') # N: Revealed type is "builtins.str" reveal_type(f'{1}') # N: Revealed type is "builtins.str" diff --git a/test-data/unit/check-newtype.test b/test-data/unit/check-newtype.test index 0ff6b8396fa7..a0a30079f062 100644 --- a/test-data/unit/check-newtype.test +++ b/test-data/unit/check-newtype.test @@ -379,3 +379,9 @@ N = NewType('N', XXX) # E: Argument 2 to NewType(...) must be subclassable (got # E: Name "XXX" is not defined x: List[Union[N, int]] [builtins fixtures/list.pyi] + +[case testTypingExtensionsNewType] +from typing_extensions import NewType +N = NewType("N", int) +x: N +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 754c6b52ff19..70f3c4486e14 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -361,9 +361,9 @@ def f() -> None: def g(x: Optional[int]) -> int: pass -x = f() # E: "f" does not return a value -f() + 1 # E: "f" does not return a value -g(f()) # E: "f" does not return a value +x = f() # E: "f" does not return a value (it only ever returns None) +f() + 1 # E: "f" does not return a value (it only ever returns None) +g(f()) # E: "f" does not return a value (it only ever returns None) [case testEmptyReturn] def f() -> None: @@ -1040,3 +1040,306 @@ x: Optional[List[int]] if 3 in x: pass +[case testNarrowedVariableInNestedFunctionBasic] +from typing import Optional + +def can_narrow(x: Optional[str]) -> None: + if x is None: + x = "a" + def nested() -> str: + return reveal_type(x) # N: Revealed type is "builtins.str" + nested() + +def foo(a): pass + +class C: + def can_narrow_in_method(self, x: Optional[str]) -> None: + if x is None: + x = "a" + def nested() -> str: + return reveal_type(x) # N: Revealed type is "builtins.str" + # Reading the variable is fine + y = x + with foo(x): + foo(x) + for a in foo(x): + foo(x) + nested() + +def can_narrow_lambda(x: Optional[str]) -> None: + if x is None: + x = "a" + nested = lambda: x + reveal_type(nested()) # N: Revealed type is "builtins.str" + +def cannot_narrow_if_reassigned(x: Optional[str]) -> None: + if x is None: + x = "a" + def nested() -> str: + return x # E: Incompatible return value type (got "Optional[str]", expected "str") + if int(): + x = None + nested() + +x: Optional[str] = "x" + +def narrow_global_in_func() -> None: + global x + if x is None: + x = "a" + def nested() -> str: + # This should perhaps not be narrowed, since the nested function could outlive + # the outer function, and since other functions could also assign to x, but + # this seems like a minor issue. + return x + nested() + +x = "y" + +def narrowing_global_at_top_level_not_propagated() -> str: + def nested() -> str: + return x # E: Incompatible return value type (got "Optional[str]", expected "str") + return x # E: Incompatible return value type (got "Optional[str]", expected "str") + +[case testNarrowedVariableInNestedFunctionMore1] +from typing import Optional, overload + +class C: + a: Optional[str] + +def attribute_narrowing(c: C) -> None: + # This case is not supported, since we can't keep track of assignments to attributes. + c.a = "x" + def nested() -> str: + return c.a # E: Incompatible return value type (got "Optional[str]", expected "str") + nested() + +def assignment_in_for(x: Optional[str]) -> None: + if x is None: + x = "e" + def nested() -> str: + return x # E: Incompatible return value type (got "Optional[str]", expected "str") + for x in ["x"]: + pass + +def foo(): pass + +def assignment_in_with(x: Optional[str]) -> None: + if x is None: + x = "e" + def nested() -> str: + return x # E: Incompatible return value type (got "Optional[str]", expected "str") + with foo() as x: + pass + +g: Optional[str] + +def assign_to_global() -> None: + global g + g = "x" + # This is unsafe, but we don't generate an error, for convenience. Besides, + # this is probably a very rare case. + def nested() -> str: + return g + +def assign_to_nonlocal(x: Optional[str]) -> None: + def nested() -> str: + nonlocal x + + if x is None: + x = "a" + + def nested2() -> str: + return x # E: Incompatible return value type (got "Optional[str]", expected "str") + + return nested2() + nested() + x = None + +def dec(f): + return f + +@dec +def decorated_outer(x: Optional[str]) -> None: + if x is None: + x = "a" + def nested() -> str: + return x + nested() + +@dec +def decorated_outer_bad(x: Optional[str]) -> None: + if x is None: + x = "a" + def nested() -> str: + return x # E: Incompatible return value type (got "Optional[str]", expected "str") + x = None + nested() + +def decorated_inner(x: Optional[str]) -> None: + if x is None: + x = "a" + @dec + def nested() -> str: + return x + nested() + +def decorated_inner_bad(x: Optional[str]) -> None: + if x is None: + x = "a" + @dec + def nested() -> str: + return x # E: Incompatible return value type (got "Optional[str]", expected "str") + x = None + nested() + +@overload +def overloaded_outer(x: None) -> None: ... +@overload +def overloaded_outer(x: str) -> None: ... +def overloaded_outer(x: Optional[str]) -> None: + if x is None: + x = "a" + def nested() -> str: + return x + nested() + +@overload +def overloaded_outer_bad(x: None) -> None: ... +@overload +def overloaded_outer_bad(x: str) -> None: ... +def overloaded_outer_bad(x: Optional[str]) -> None: + if x is None: + x = "a" + def nested() -> str: + return x # E: Incompatible return value type (got "Optional[str]", expected "str") + x = None + nested() + +[case testNarrowedVariableInNestedFunctionMore2] +from typing import Optional + +def narrow_multiple(x: Optional[str], y: Optional[int]) -> None: + z: Optional[str] = x + if x is None: + x = "" + if y is None: + y = 1 + if int(): + if z is None: + z = "" + def nested() -> None: + a: str = x + b: int = y + c: str = z + nested() + +def narrow_multiple_partial(x: Optional[str], y: Optional[int]) -> None: + z: Optional[str] = x + if x is None: + x = "" + if isinstance(y, int): + if z is None: + z = "" + def nested() -> None: + a: str = x + b: int = y + c: str = z # E: Incompatible types in assignment (expression has type "Optional[str]", variable has type "str") + z = None + nested() + +def multiple_nested_functions(x: Optional[str], y: Optional[str]) -> None: + if x is None: + x = "" + def nested1() -> str: + return x + if y is None: + y = "" + def nested2() -> str: + a: str = y + return x + +class C: + a: str + def __setitem__(self, key, value): pass + +def narrowed_variable_used_in_lvalue_but_not_assigned(c: Optional[C]) -> None: + if c is None: + c = C() + def nested() -> C: + return c + c.a = "x" + c[1] = 2 + cc = C() + cc[c] = 3 + nested() + +def narrow_with_multi_lvalues_1(x: Optional[str]) -> None: + if x is None: + x = "" + + def nested() -> str: + return x + + y = z = None + +def narrow_with_multi_lvalue_2(x: Optional[str]) -> None: + if x is None: + x = "" + + def nested() -> str: + return x # E: Incompatible return value type (got "Optional[str]", expected "str") + + x = y = None + +def narrow_with_multi_lvalue_3(x: Optional[str]) -> None: + if x is None: + x = "" + + def nested() -> str: + return x # E: Incompatible return value type (got "Optional[str]", expected "str") + + y = x = None + +def narrow_with_multi_assign_1(x: Optional[str]) -> None: + if x is None: + x = "" + + def nested() -> str: + return x + + y, z = None, None + +def narrow_with_multi_assign_2(x: Optional[str]) -> None: + if x is None: + x = "" + + def nested() -> str: + return x # E: Incompatible return value type (got "Optional[str]", expected "str") + + x, y = None, None + +def narrow_with_multi_assign_3(x: Optional[str]) -> None: + if x is None: + x = "" + + def nested() -> str: + return x # E: Incompatible return value type (got "Optional[str]", expected "str") + + y, x = None, None + +[builtins fixtures/isinstance.pyi] + +[case testNestedFunctionSpecialCase] +class C: + def __enter__(self, *args): ... + def __exit__(self, *args) -> bool: ... + +def f(x: object) -> None: + if x is not None: + pass + + def nested() -> None: + with C(): + pass +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 4209f4ec9164..7bca5cc7b508 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -450,7 +450,8 @@ class C: pass from foo import * [file foo.pyi] from typing import overload -a, b = None, None # type: (A, B) +a: A +b: B if int(): b = f(a) # E: Incompatible types in assignment (expression has type "A", variable has type "B") if int(): @@ -492,7 +493,8 @@ class C: pass from foo import * [file foo.pyi] from typing import overload -a, b = None, None # type: (A, B) +a: A +b: B if int(): b = a.f(a) # E: Incompatible types in assignment (expression has type "A", variable has type "B") if int(): @@ -514,7 +516,8 @@ class B: pass from foo import * [file foo.pyi] from typing import overload -a, b = None, None # type: (A, B) +a: A +b: B if int(): a = f(a) if int(): @@ -549,7 +552,10 @@ from foo import * [file foo.pyi] from typing import overload, TypeVar, Generic t = TypeVar('t') -ab, ac, b, c = None, None, None, None # type: (A[B], A[C], B, C) +ab: A[B] +ac: A[C] +b: B +c: C if int(): b = f(ab) c = f(ac) @@ -569,7 +575,8 @@ class C: pass from foo import * [file foo.pyi] from typing import overload -a, b = None, None # type: (A, B) +a: A +b: B a = A(a) a = A(b) a = A(object()) # E: No overload variant of "A" matches argument type "object" \ @@ -589,8 +596,8 @@ class B: pass from foo import * [file foo.pyi] from typing import overload, Callable -o = None # type: object -a = None # type: A +o: object +a: A if int(): a = f # E: Incompatible types in assignment (expression has type overloaded function, variable has type "A") @@ -607,7 +614,8 @@ class A: pass from foo import * [file foo.pyi] from typing import overload -t, a = None, None # type: (type, A) +t: type +a: A if int(): a = A # E: Incompatible types in assignment (expression has type "Type[A]", variable has type "A") @@ -625,7 +633,8 @@ class B: pass from foo import * [file foo.pyi] from typing import overload -a, b = None, None # type: int, str +a: int +b: str if int(): a = A()[a] if int(): @@ -647,7 +656,9 @@ from foo import * [file foo.pyi] from typing import TypeVar, Generic, overload t = TypeVar('t') -a, b, c = None, None, None # type: (A, B, C[A]) +a: A +b: B +c: C[A] if int(): a = c[a] b = c[a] # E: Incompatible types in assignment (expression has type "A", variable has type "B") @@ -761,7 +772,8 @@ from typing import overload def f(t: type) -> 'A': pass @overload def f(t: 'A') -> 'B': pass -a, b = None, None # type: (A, B) +a: A +b: B if int(): a = f(A) if int(): @@ -1133,7 +1145,7 @@ def f(x: str) -> None: pass f(1.1) f('') f(1) -f(()) # E: No overload variant of "f" matches argument type "Tuple[]" \ +f(()) # E: No overload variant of "f" matches argument type "Tuple[()]" \ # N: Possible overload variants: \ # N: def f(x: float) -> None \ # N: def f(x: str) -> None @@ -1215,6 +1227,7 @@ f(*(1, '', 1))() # E: No overload variant of "f" matches argument type "Tuple[in [builtins fixtures/tuple.pyi] [case testPreferExactSignatureMatchInOverload] +# flags: --no-strict-optional from foo import * [file foo.pyi] from typing import overload, List @@ -2172,36 +2185,63 @@ def bar2(*x: int) -> int: ... [builtins fixtures/tuple.pyi] [case testOverloadDetectsPossibleMatchesWithGenerics] -from typing import overload, TypeVar, Generic +# flags: --strict-optional +from typing import overload, TypeVar, Generic, Optional, List T = TypeVar('T') +# The examples below are unsafe, but it is a quite common pattern +# so we ignore the possibility of type variables taking value `None` +# for the purpose of overload overlap checks. @overload -def foo(x: None, y: None) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +def foo(x: None, y: None) -> str: ... @overload def foo(x: T, y: T) -> int: ... def foo(x): ... +oi: Optional[int] +reveal_type(foo(None, None)) # N: Revealed type is "builtins.str" +reveal_type(foo(None, 42)) # N: Revealed type is "builtins.int" +reveal_type(foo(42, 42)) # N: Revealed type is "builtins.int" +reveal_type(foo(oi, None)) # N: Revealed type is "Union[builtins.int, builtins.str]" +reveal_type(foo(oi, 42)) # N: Revealed type is "builtins.int" +reveal_type(foo(oi, oi)) # N: Revealed type is "Union[builtins.int, builtins.str]" + +@overload +def foo_list(x: None) -> None: ... +@overload +def foo_list(x: T) -> List[T]: ... +def foo_list(x): ... + +reveal_type(foo_list(oi)) # N: Revealed type is "Union[builtins.list[builtins.int], None]" + # What if 'T' is 'object'? @overload -def bar(x: None, y: int) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +def bar(x: None, y: int) -> str: ... @overload def bar(x: T, y: T) -> int: ... def bar(x, y): ... class Wrapper(Generic[T]): @overload - def foo(self, x: None, y: None) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types + def foo(self, x: None, y: None) -> str: ... @overload def foo(self, x: T, y: None) -> int: ... def foo(self, x): ... @overload - def bar(self, x: None, y: int) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types + def bar(self, x: None, y: int) -> str: ... @overload def bar(self, x: T, y: T) -> int: ... def bar(self, x, y): ... +@overload +def baz(x: str, y: str) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def baz(x: T, y: T) -> int: ... +def baz(x): ... +[builtins fixtures/tuple.pyi] + [case testOverloadFlagsPossibleMatches] from wrapper import * [file wrapper.pyi] @@ -3251,7 +3291,6 @@ f(x, B()) # E: Argument 1 to "f" has incompatible type "Union[A, B]"; expected [builtins fixtures/tuple.pyi] [case testOverloadInferUnionWithMixOfPositionalAndOptionalArgs] -# flags: --strict-optional from typing import overload, Union, Optional class A: ... @@ -3358,11 +3397,11 @@ def wrapper() -> None: # Note: These should be fine, but mypy has an unrelated bug # that makes them error out? - a2_overload: A = SomeType().foo(obj1) # E: Argument 1 to "foo" of "SomeType" has incompatible type "Union[W1[A], W2[A]]"; expected "W1[]" - a2_union: A = SomeType().bar(obj1) # E: Argument 1 to "bar" of "SomeType" has incompatible type "Union[W1[A], W2[A]]"; expected "Union[W1[], W2[]]" + a2_overload: A = SomeType().foo(obj1) # E: Argument 1 to "foo" of "SomeType" has incompatible type "Union[W1[A], W2[A]]"; expected "W1[Never]" + a2_union: A = SomeType().bar(obj1) # E: Argument 1 to "bar" of "SomeType" has incompatible type "Union[W1[A], W2[A]]"; expected "Union[W1[Never], W2[Never]]" - SomeType().foo(obj1) # E: Argument 1 to "foo" of "SomeType" has incompatible type "Union[W1[A], W2[A]]"; expected "W1[]" - SomeType().bar(obj1) # E: Argument 1 to "bar" of "SomeType" has incompatible type "Union[W1[A], W2[A]]"; expected "Union[W1[], W2[]]" + SomeType().foo(obj1) # E: Argument 1 to "foo" of "SomeType" has incompatible type "Union[W1[A], W2[A]]"; expected "W1[Never]" + SomeType().bar(obj1) # E: Argument 1 to "bar" of "SomeType" has incompatible type "Union[W1[A], W2[A]]"; expected "Union[W1[Never], W2[Never]]" [case testOverloadingInferUnionReturnWithBadObjectTypevarReturn] from typing import overload, Union, TypeVar, Generic @@ -3386,8 +3425,8 @@ class SomeType(Generic[T]): def wrapper(mysterious: T) -> T: obj1: Union[W1[A], W2[B]] - SomeType().foo(obj1) # E: Argument 1 to "foo" of "SomeType" has incompatible type "Union[W1[A], W2[B]]"; expected "W1[]" - SomeType().bar(obj1) # E: Argument 1 to "bar" of "SomeType" has incompatible type "Union[W1[A], W2[B]]"; expected "Union[W1[], W2[]]" + SomeType().foo(obj1) # E: Argument 1 to "foo" of "SomeType" has incompatible type "Union[W1[A], W2[B]]"; expected "W1[Never]" + SomeType().bar(obj1) # E: Argument 1 to "bar" of "SomeType" has incompatible type "Union[W1[A], W2[B]]"; expected "Union[W1[Never], W2[Never]]" SomeType[A]().foo(obj1) # E: Argument 1 to "foo" of "SomeType" has incompatible type "Union[W1[A], W2[B]]"; expected "W1[A]" SomeType[A]().bar(obj1) # E: Argument 1 to "bar" of "SomeType" has incompatible type "Union[W1[A], W2[B]]"; expected "Union[W1[A], W2[A]]" @@ -3590,7 +3629,6 @@ reveal_type(g(b)) # N: Revealed type is "builtins.str" reveal_type(g(c)) # N: Revealed type is "builtins.str" [case testOverloadsAndNoneWithStrictOptional] -# flags: --strict-optional from typing import overload, Optional @overload @@ -3638,7 +3676,6 @@ reveal_type(mymap(f3, seq)) # N: Revealed type is "typing.Iterable[builtins.str [typing fixtures/typing-medium.pyi] [case testOverloadsNoneAndTypeVarsWithStrictOptional] -# flags: --strict-optional from typing import Callable, Iterable, TypeVar, overload, Optional T = TypeVar('T') @@ -3695,7 +3732,6 @@ def test_narrow_int() -> None: [typing fixtures/typing-medium.pyi] [case testOverloadsAndNoReturnNarrowTypeWithStrictOptional1] -# flags: --strict-optional from typing import overload, Union, NoReturn @overload @@ -3759,7 +3795,6 @@ def test_narrow_none() -> None: [typing fixtures/typing-medium.pyi] [case testOverloadsAndNoReturnNarrowTypeWithStrictOptional2] -# flags: --strict-optional from typing import overload, Union, TypeVar, NoReturn, Optional T = TypeVar('T') @@ -3823,7 +3858,6 @@ def test_narrow_none_v2() -> None: [typing fixtures/typing-medium.pyi] [case testOverloadsAndNoReturnNarrowTypeWithStrictOptional3] -# flags: --strict-optional from typing import overload, TypeVar, NoReturn, Optional @overload @@ -3989,7 +4023,7 @@ T = TypeVar('T') class FakeAttribute(Generic[T]): @overload - def dummy(self, instance: None, owner: Type[T]) -> 'FakeAttribute[T]': ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types + def dummy(self, instance: None, owner: Type[T]) -> 'FakeAttribute[T]': ... @overload def dummy(self, instance: T, owner: Type[T]) -> int: ... def dummy(self, instance: Optional[T], owner: Type[T]) -> Union['FakeAttribute[T]', int]: ... @@ -4635,7 +4669,6 @@ def none_second(x: int) -> int: return x [case testOverloadsWithNoneComingSecondIsOkInStrictOptional] -# flags: --strict-optional from typing import overload, Optional @overload @@ -4659,8 +4692,8 @@ def none_loose_impl(x: int) -> int: ... def none_loose_impl(x: int) -> int: return x [out] -main:22: error: Overloaded function implementation does not accept all possible arguments of signature 1 -main:22: error: Overloaded function implementation cannot produce return type of signature 1 +main:21: error: Overloaded function implementation does not accept all possible arguments of signature 1 +main:21: error: Overloaded function implementation cannot produce return type of signature 1 [case testTooManyUnionsException] from typing import overload, Union @@ -4745,7 +4778,7 @@ main:12: note: @overload main:12: note: def __add__(self, Other, /) -> B main:12: note: @overload main:12: note: def __add__(self, A, /) -> A -main:12: note: Overloaded operator methods cannot have wider argument types in overrides +main:12: note: Overloaded operator methods can't have wider argument types in overrides main:32: note: Revealed type is "__main__.Other" [case testOverloadErrorMessageManyMatches] @@ -5405,26 +5438,26 @@ if False: def f2(g): ... reveal_type(f2(A())) # N: Revealed type is "__main__.A" reveal_type(f2(C())) # E: No overload variant of "f2" matches argument type "C" \ - # N: Possible overload variants: \ - # N: def f2(g: A) -> A \ - # N: def f2(g: B) -> B \ - # N: Revealed type is "Any" + # N: Possible overload variants: \ + # N: def f2(g: A) -> A \ + # N: def f2(g: B) -> B \ + # N: Revealed type is "Any" @overload def f3(g: A) -> A: ... @overload def f3(g: B) -> B: ... -if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \ +if maybe_true: # E: Condition can't be inferred, unable to merge overloads \ # E: Name "maybe_true" is not defined @overload def f3(g: C) -> C: ... def f3(g): ... reveal_type(f3(A())) # N: Revealed type is "__main__.A" reveal_type(f3(C())) # E: No overload variant of "f3" matches argument type "C" \ - # N: Possible overload variants: \ - # N: def f3(g: A) -> A \ - # N: def f3(g: B) -> B \ - # N: Revealed type is "Any" + # N: Possible overload variants: \ + # N: def f3(g: A) -> A \ + # N: def f3(g: B) -> B \ + # N: Revealed type is "Any" if True: @overload @@ -5731,10 +5764,10 @@ def f1(x): ... reveal_type(f1(A())) # N: Revealed type is "__main__.A" reveal_type(f1(B())) # N: Revealed type is "__main__.B" reveal_type(f1(D())) # E: No overload variant of "f1" matches argument type "D" \ - # N: Possible overload variants: \ - # N: def f1(x: A) -> A \ - # N: def f1(x: B) -> B \ - # N: Revealed type is "Any" + # N: Possible overload variants: \ + # N: def f1(x: A) -> A \ + # N: def f1(x: B) -> B \ + # N: Revealed type is "Any" @overload def f2(x: A) -> A: ... @@ -5751,14 +5784,14 @@ def f2(x): ... reveal_type(f2(A())) # N: Revealed type is "__main__.A" reveal_type(f2(B())) # N: Revealed type is "__main__.B" reveal_type(f2(C())) # E: No overload variant of "f2" matches argument type "C" \ - # N: Possible overload variants: \ - # N: def f2(x: A) -> A \ - # N: def f2(x: B) -> B \ - # N: Revealed type is "Any" + # N: Possible overload variants: \ + # N: def f2(x: A) -> A \ + # N: def f2(x: B) -> B \ + # N: Revealed type is "Any" @overload # E: Single overload definition, multiple required def f3(x: A) -> A: ... -if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \ +if maybe_true: # E: Condition can't be inferred, unable to merge overloads \ # E: Name "maybe_true" is not defined @overload def f3(x: B) -> B: ... @@ -5771,13 +5804,13 @@ else: def f3(x): ... reveal_type(f3(A())) # N: Revealed type is "__main__.A" reveal_type(f3(B())) # E: No overload variant of "f3" matches argument type "B" \ - # N: Possible overload variant: \ - # N: def f3(x: A) -> A \ - # N: Revealed type is "Any" + # N: Possible overload variant: \ + # N: def f3(x: A) -> A \ + # N: Revealed type is "Any" @overload # E: Single overload definition, multiple required def f4(x: A) -> A: ... -if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \ +if maybe_true: # E: Condition can't be inferred, unable to merge overloads \ # E: Name "maybe_true" is not defined @overload def f4(x: B) -> B: ... @@ -5787,9 +5820,10 @@ else: def f4(x): ... reveal_type(f4(A())) # N: Revealed type is "__main__.A" reveal_type(f4(B())) # E: No overload variant of "f4" matches argument type "B" \ - # N: Possible overload variant: \ - # N: def f4(x: A) -> A \ - # N: Revealed type is "Any" + # N: Possible overload variant: \ + # N: def f4(x: A) -> A \ + # N: Revealed type is "Any" + [case testOverloadIfElse3] # flags: --always-false False @@ -5817,10 +5851,10 @@ else: def f1(x): ... reveal_type(f1(A())) # N: Revealed type is "__main__.A" reveal_type(f1(B())) # E: No overload variant of "f1" matches argument type "B" \ - # N: Possible overload variants: \ - # N: def f1(x: A) -> A \ - # N: def f1(x: D) -> D \ - # N: Revealed type is "Any" + # N: Possible overload variants: \ + # N: def f1(x: A) -> A \ + # N: def f1(x: D) -> D \ + # N: Revealed type is "Any" reveal_type(f1(D())) # N: Revealed type is "__main__.D" @overload # E: Single overload definition, multiple required @@ -5828,7 +5862,7 @@ def f2(x: A) -> A: ... if False: @overload def f2(x: B) -> B: ... -elif maybe_true: # E: Condition cannot be inferred, unable to merge overloads \ +elif maybe_true: # E: Condition can't be inferred, unable to merge overloads \ # E: Name "maybe_true" is not defined @overload def f2(x: C) -> C: ... @@ -5838,13 +5872,13 @@ else: def f2(x): ... reveal_type(f2(A())) # N: Revealed type is "__main__.A" reveal_type(f2(C())) # E: No overload variant of "f2" matches argument type "C" \ - # N: Possible overload variant: \ - # N: def f2(x: A) -> A \ - # N: Revealed type is "Any" + # N: Possible overload variant: \ + # N: def f2(x: A) -> A \ + # N: Revealed type is "Any" @overload # E: Single overload definition, multiple required def f3(x: A) -> A: ... -if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \ +if maybe_true: # E: Condition can't be inferred, unable to merge overloads \ # E: Name "maybe_true" is not defined @overload def f3(x: B) -> B: ... @@ -5857,14 +5891,14 @@ else: def f3(x): ... reveal_type(f3(A())) # N: Revealed type is "__main__.A" reveal_type(f3(B())) # E: No overload variant of "f3" matches argument type "B" \ - # N: Possible overload variant: \ - # N: def f3(x: A) -> A \ - # N: Revealed type is "Any" + # N: Possible overload variant: \ + # N: def f3(x: A) -> A \ + # N: Revealed type is "Any" def g(bool_var: bool) -> None: @overload def f4(x: A) -> A: ... - if bool_var: # E: Condition cannot be inferred, unable to merge overloads + if bool_var: # E: Condition can't be inferred, unable to merge overloads @overload def f4(x: B) -> B: ... elif maybe_true: # E: Name "maybe_true" is not defined @@ -5880,10 +5914,11 @@ def g(bool_var: bool) -> None: def f4(x): ... reveal_type(f4(E())) # N: Revealed type is "__main__.E" reveal_type(f4(B())) # E: No overload variant of "f4" matches argument type "B" \ - # N: Possible overload variants: \ - # N: def f4(x: A) -> A \ - # N: def f4(x: E) -> E \ - # N: Revealed type is "Any" + # N: Possible overload variants: \ + # N: def f4(x: A) -> A \ + # N: def f4(x: E) -> E \ + # N: Revealed type is "Any" + [case testOverloadIfSkipUnknownExecution] # flags: --always-true True @@ -5901,14 +5936,14 @@ class D: ... @overload # E: Single overload definition, multiple required def f1(x: A) -> A: ... -if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \ +if maybe_true: # E: Condition can't be inferred, unable to merge overloads \ # E: Name "maybe_true" is not defined @overload def f1(x: B) -> B: ... def f1(x): ... reveal_type(f1(A())) # N: Revealed type is "__main__.A" -if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \ +if maybe_true: # E: Condition can't be inferred, unable to merge overloads \ # E: Name "maybe_true" is not defined @overload def f2(x: A) -> A: ... @@ -5918,15 +5953,15 @@ def f2(x: B) -> B: ... def f2(x: C) -> C: ... def f2(x): ... reveal_type(f2(A())) # E: No overload variant of "f2" matches argument type "A" \ - # N: Possible overload variants: \ - # N: def f2(x: B) -> B \ - # N: def f2(x: C) -> C \ - # N: Revealed type is "Any" + # N: Possible overload variants: \ + # N: def f2(x: B) -> B \ + # N: def f2(x: C) -> C \ + # N: Revealed type is "Any" if True: @overload # E: Single overload definition, multiple required def f3(x: A) -> A: ... - if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \ + if maybe_true: # E: Condition can't be inferred, unable to merge overloads \ # E: Name "maybe_true" is not defined @overload def f3(x: B) -> B: ... @@ -5934,7 +5969,7 @@ if True: reveal_type(f3(A())) # N: Revealed type is "__main__.A" if True: - if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \ + if maybe_true: # E: Condition can't be inferred, unable to merge overloads \ # E: Name "maybe_true" is not defined @overload def f4(x: A) -> A: ... @@ -5944,10 +5979,10 @@ if True: def f4(x: C) -> C: ... def f4(x): ... reveal_type(f4(A())) # E: No overload variant of "f4" matches argument type "A" \ - # N: Possible overload variants: \ - # N: def f4(x: B) -> B \ - # N: def f4(x: C) -> C \ - # N: Revealed type is "Any" + # N: Possible overload variants: \ + # N: def f4(x: B) -> B \ + # N: def f4(x: C) -> C \ + # N: Revealed type is "Any" [case testOverloadIfDontSkipUnrelatedOverload] # flags: --always-true True @@ -6187,16 +6222,16 @@ if False: def f8(x): ... reveal_type(f8(A())) # N: Revealed type is "__main__.A" reveal_type(f8(C())) # E: No overload variant of "f8" matches argument type "C" \ - # N: Possible overload variants: \ - # N: def f8(x: A) -> A \ - # N: def f8(x: B) -> B \ - # N: Revealed type is "Any" + # N: Possible overload variants: \ + # N: def f8(x: A) -> A \ + # N: def f8(x: B) -> B \ + # N: Revealed type is "Any" -if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \ +if maybe_true: # E: Condition can't be inferred, unable to merge overloads \ # E: Name "maybe_true" is not defined @overload def f9(x: A) -> A: ... -if another_maybe_true: # E: Condition cannot be inferred, unable to merge overloads \ +if another_maybe_true: # E: Condition can't be inferred, unable to merge overloads \ # E: Name "another_maybe_true" is not defined @overload def f9(x: B) -> B: ... @@ -6206,18 +6241,18 @@ def f9(x: C) -> C: ... def f9(x: D) -> D: ... def f9(x): ... reveal_type(f9(A())) # E: No overload variant of "f9" matches argument type "A" \ - # N: Possible overload variants: \ - # N: def f9(x: C) -> C \ - # N: def f9(x: D) -> D \ - # N: Revealed type is "Any" + # N: Possible overload variants: \ + # N: def f9(x: C) -> C \ + # N: def f9(x: D) -> D \ + # N: Revealed type is "Any" reveal_type(f9(C())) # N: Revealed type is "__main__.C" if True: - if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \ + if maybe_true: # E: Condition can't be inferred, unable to merge overloads \ # E: Name "maybe_true" is not defined @overload def f10(x: A) -> A: ... - if another_maybe_true: # E: Condition cannot be inferred, unable to merge overloads \ + if another_maybe_true: # E: Condition can't be inferred, unable to merge overloads \ # E: Name "another_maybe_true" is not defined @overload def f10(x: B) -> B: ... @@ -6227,10 +6262,10 @@ if True: def f10(x: D) -> D: ... def f10(x): ... reveal_type(f10(A())) # E: No overload variant of "f10" matches argument type "A" \ - # N: Possible overload variants: \ - # N: def f10(x: C) -> C \ - # N: def f10(x: D) -> D \ - # N: Revealed type is "Any" + # N: Possible overload variants: \ + # N: def f10(x: C) -> C \ + # N: def f10(x: D) -> D \ + # N: Revealed type is "Any" reveal_type(f10(C())) # N: Revealed type is "__main__.C" if some_var: # E: Name "some_var" is not defined @@ -6251,6 +6286,7 @@ if True: def f12(x: B) -> B: ... def f12(x): ... reveal_type(f12(A())) # N: Revealed type is "__main__.A" + [typing fixtures/typing-medium.pyi] [case testOverloadIfUnconditionalFuncDef] @@ -6406,7 +6442,7 @@ def f1(g: A) -> A: ... if True: @overload # E: Single overload definition, multiple required def f1(g: B) -> B: ... - if maybe_true: # E: Condition cannot be inferred, unable to merge overloads \ + if maybe_true: # E: Condition can't be inferred, unable to merge overloads \ # E: Name "maybe_true" is not defined @overload def f1(g: C) -> C: ... @@ -6432,7 +6468,7 @@ if True: def f3(g: B) -> B: ... if True: pass # Some other node - @overload # E: Name "f3" already defined on line 32 \ + @overload # E: Name "f3" already defined on line 32 \ # E: An overloaded function outside a stub file must have an implementation def f3(g: C) -> C: ... @overload @@ -6465,8 +6501,7 @@ eggs = lambda: 'eggs' reveal_type(func(eggs)) # N: Revealed type is "def (builtins.str) -> builtins.str" spam: Callable[..., str] = lambda x, y: 'baz' -reveal_type(func(spam)) # N: Revealed type is "def (*Any, **Any) -> builtins.str" - +reveal_type(func(spam)) # N: Revealed type is "def (*Any, **Any) -> Any" [builtins fixtures/paramspec.pyi] [case testGenericOverloadOverlapWithType] @@ -6545,3 +6580,116 @@ class Snafu(object): reveal_type(Snafu().snafu('123')) # N: Revealed type is "builtins.str" reveal_type(Snafu.snafu('123')) # N: Revealed type is "builtins.str" [builtins fixtures/staticmethod.pyi] + +[case testOverloadedWithInternalTypeVars] +# flags: --new-type-inference +import m + +[file m.pyi] +from typing import Callable, TypeVar, overload + +T = TypeVar("T") +S = TypeVar("S", bound=str) + +@overload +def foo(x: int = ...) -> Callable[[T], T]: ... +@overload +def foo(x: S = ...) -> Callable[[T], T]: ... + +[case testOverloadGenericStarArgOverlap] +from typing import Any, Callable, TypeVar, overload, Union, Tuple, List + +F = TypeVar("F", bound=Callable[..., Any]) +S = TypeVar("S", bound=int) + +def id(f: F) -> F: ... + +@overload +def struct(*cols: S) -> int: ... +@overload +def struct(__cols: Union[List[S], Tuple[S, ...]]) -> int: ... +@id +def struct(*cols: Union[S, Union[List[S], Tuple[S, ...]]]) -> int: + pass +[builtins fixtures/tuple.pyi] + +[case testRegularGenericDecoratorOverload] +from typing import Callable, overload, TypeVar, List + +S = TypeVar("S") +T = TypeVar("T") +def transform(func: Callable[[S], List[T]]) -> Callable[[S], T]: ... + +@overload +def foo(x: int) -> List[float]: ... +@overload +def foo(x: str) -> List[str]: ... +def foo(x): ... + +reveal_type(transform(foo)) # N: Revealed type is "Overload(def (builtins.int) -> builtins.float, def (builtins.str) -> builtins.str)" + +@transform +@overload +def bar(x: int) -> List[float]: ... +@transform +@overload +def bar(x: str) -> List[str]: ... +@transform +def bar(x): ... + +reveal_type(bar) # N: Revealed type is "Overload(def (builtins.int) -> builtins.float, def (builtins.str) -> builtins.str)" +[builtins fixtures/paramspec.pyi] + +[case testOverloadOverlapWithNameOnlyArgs] +from typing import overload + +@overload +def d(x: int) -> int: ... +@overload +def d(f: int, *, x: int) -> str: ... +def d(*args, **kwargs): ... +[builtins fixtures/tuple.pyi] + +[case testOverloadCallableGenericSelf] +from typing import Any, TypeVar, Generic, overload, reveal_type + +T = TypeVar("T") + +class MyCallable(Generic[T]): + def __init__(self, t: T): + self.t = t + + @overload + def __call__(self: "MyCallable[int]") -> str: ... + @overload + def __call__(self: "MyCallable[str]") -> int: ... + def __call__(self): ... + +c = MyCallable(5) +reveal_type(c) # N: Revealed type is "__main__.MyCallable[builtins.int]" +reveal_type(c()) # N: Revealed type is "builtins.str" + +c2 = MyCallable("test") +reveal_type(c2) # N: Revealed type is "__main__.MyCallable[builtins.str]" +reveal_type(c2()) # should be int # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] + +[case testOverloadWithStarAnyFallback] +from typing import overload, Any + +class A: + @overload + def f(self, e: str) -> str: ... + @overload + def f(self, *args: Any, **kwargs: Any) -> Any: ... + def f(self, *args, **kwargs): + pass + +class B: + @overload + def f(self, e: str, **kwargs: Any) -> str: ... + @overload + def f(self, *args: Any, **kwargs: Any) -> Any: ... + def f(self, *args, **kwargs): + pass +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index 56fc3b6faa14..cab7d2bf6819 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -6,11 +6,11 @@ P = ParamSpec('P') [case testInvalidParamSpecDefinitions] from typing import ParamSpec -P1 = ParamSpec("P1", covariant=True) # E: Only the first argument to ParamSpec has defined semantics -P2 = ParamSpec("P2", contravariant=True) # E: Only the first argument to ParamSpec has defined semantics -P3 = ParamSpec("P3", bound=int) # E: Only the first argument to ParamSpec has defined semantics -P4 = ParamSpec("P4", int, str) # E: Only the first argument to ParamSpec has defined semantics -P5 = ParamSpec("P5", covariant=True, bound=int) # E: Only the first argument to ParamSpec has defined semantics +P1 = ParamSpec("P1", covariant=True) # E: The variance and bound arguments to ParamSpec do not have defined semantics yet +P2 = ParamSpec("P2", contravariant=True) # E: The variance and bound arguments to ParamSpec do not have defined semantics yet +P3 = ParamSpec("P3", bound=int) # E: The variance and bound arguments to ParamSpec do not have defined semantics yet +P4 = ParamSpec("P4", int, str) # E: Too many positional arguments for "ParamSpec" +P5 = ParamSpec("P5", covariant=True, bound=int) # E: The variance and bound arguments to ParamSpec do not have defined semantics yet [builtins fixtures/paramspec.pyi] [case testParamSpecLocations] @@ -38,6 +38,74 @@ def foo6(x: Callable[[P], int]) -> None: ... # E: Invalid location for ParamSpe # N: You can use ParamSpec as the first argument to Callable, e.g., 'Callable[P, int]' [builtins fixtures/paramspec.pyi] +[case testParamSpecImports] +import lib +from lib import Base + +class C(Base[[int]]): + def test(self, x: int): ... + +class D(lib.Base[[int]]): + def test(self, x: int): ... + +class E(lib.Base[...]): ... +reveal_type(E().test) # N: Revealed type is "def (*Any, **Any)" + +[file lib.py] +from typing import Generic +from typing_extensions import ParamSpec + +P = ParamSpec("P") +class Base(Generic[P]): + def test(self, *args: P.args, **kwargs: P.kwargs) -> None: + ... +[builtins fixtures/paramspec.pyi] + +[case testParamSpecEllipsisInAliases] +from typing import Any, Callable, Generic, TypeVar +from typing_extensions import ParamSpec + +P = ParamSpec('P') +R = TypeVar('R') +Alias = Callable[P, R] + +class B(Generic[P]): ... +Other = B[P] + +T = TypeVar('T', bound=Alias[..., Any]) +Alias[..., Any] # E: Type application is only supported for generic classes +B[...] +Other[...] +[builtins fixtures/paramspec.pyi] + +[case testParamSpecEllipsisInConcatenate] +from typing import Any, Callable, Generic, TypeVar +from typing_extensions import ParamSpec, Concatenate + +P = ParamSpec('P') +R = TypeVar('R') +Alias = Callable[P, R] + +IntFun = Callable[Concatenate[int, ...], None] +f: IntFun +reveal_type(f) # N: Revealed type is "def (builtins.int, *Any, **Any)" + +g: Callable[Concatenate[int, ...], None] +reveal_type(g) # N: Revealed type is "def (builtins.int, *Any, **Any)" + +class B(Generic[P]): + def test(self, *args: P.args, **kwargs: P.kwargs) -> None: + ... + +x: B[Concatenate[int, ...]] +reveal_type(x.test) # N: Revealed type is "def (builtins.int, *Any, **Any)" + +Bad = Callable[Concatenate[int, [int, str]], None] # E: The last parameter to Concatenate needs to be a ParamSpec \ + # E: Bracketed expression "[...]" is not valid as a type +def bad(fn: Callable[Concatenate[P, int], None]): # E: The last parameter to Concatenate needs to be a ParamSpec + ... +[builtins fixtures/paramspec.pyi] + [case testParamSpecContextManagerLike] from typing import Callable, List, Iterator, TypeVar from typing_extensions import ParamSpec @@ -171,7 +239,6 @@ reveal_type(f(g, 1, y='x')) # N: Revealed type is "None" f(g, 'x', y='x') # E: Argument 2 to "f" has incompatible type "str"; expected "int" f(g, 1, y=1) # E: Argument "y" to "f" has incompatible type "int"; expected "str" f(g) # E: Missing positional arguments "x", "y" in call to "f" - [builtins fixtures/dict.pyi] [case testParamSpecSpecialCase] @@ -347,14 +414,19 @@ P = ParamSpec('P') T = TypeVar('T') # Similar to atexit.register -def register(f: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> Callable[P, T]: ... # N: "register" defined here +def register(f: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> Callable[P, T]: ... def f(x: int) -> None: pass +def g(x: int, y: str) -> None: pass reveal_type(register(lambda: f(1))) # N: Revealed type is "def ()" -reveal_type(register(lambda x: f(x), x=1)) # N: Revealed type is "def (x: Any)" -register(lambda x: f(x)) # E: Missing positional argument "x" in call to "register" -register(lambda x: f(x), y=1) # E: Unexpected keyword argument "y" for "register" +reveal_type(register(lambda x: f(x), x=1)) # N: Revealed type is "def (x: Literal[1]?)" +register(lambda x: f(x)) # E: Cannot infer type of lambda \ + # E: Argument 1 to "register" has incompatible type "Callable[[Any], None]"; expected "Callable[[], None]" +register(lambda x: f(x), y=1) # E: Argument 1 to "register" has incompatible type "Callable[[Arg(int, 'x')], None]"; expected "Callable[[Arg(int, 'y')], None]" +reveal_type(register(lambda x: f(x), 1)) # N: Revealed type is "def (Literal[1]?)" +reveal_type(register(lambda x, y: g(x, y), 1, "a")) # N: Revealed type is "def (Literal[1]?, Literal['a']?)" +reveal_type(register(lambda x, y: g(x, y), 1, y="a")) # N: Revealed type is "def (Literal[1]?, y: Literal['a']?)" [builtins fixtures/dict.pyi] [case testParamSpecInvalidCalls] @@ -539,7 +611,6 @@ def three(**kwargs: int) -> int: ... @expects_int_first # Accepted def four(*args: int) -> int: ... -[builtins fixtures/tuple.pyi] [builtins fixtures/dict.pyi] [case testParamSpecTwiceSolving] @@ -571,7 +642,7 @@ reveal_type(f(n)) # N: Revealed type is "def (builtins.int, builtins.bytes) -> [builtins fixtures/paramspec.pyi] [case testParamSpecConcatenateNamedArgs] -# flags: --python-version 3.8 --strict-concatenate +# flags: --extra-checks # this is one noticeable deviation from PEP but I believe it is for the better from typing_extensions import ParamSpec, Concatenate from typing import Callable, TypeVar @@ -593,14 +664,10 @@ def f2(c: Callable[P, R]) -> Callable[Concatenate[int, P], R]: f2(lambda x: 42)(42, x=42) [builtins fixtures/paramspec.pyi] [out] -main:10: error: invalid syntax; you likely need to run mypy using Python 3.8 or newer -[out version>=3.8] main:17: error: Incompatible return value type (got "Callable[[Arg(int, 'x'), **P], R]", expected "Callable[[int, **P], R]") main:17: note: This is likely because "result" has named arguments: "x". Consider marking them positional-only [case testNonStrictParamSpecConcatenateNamedArgs] -# flags: --python-version 3.8 - # this is one noticeable deviation from PEP but I believe it is for the better from typing_extensions import ParamSpec, Concatenate from typing import Callable, TypeVar @@ -621,9 +688,6 @@ def f2(c: Callable[P, R]) -> Callable[Concatenate[int, P], R]: # reason for rejection: f2(lambda x: 42)(42, x=42) [builtins fixtures/paramspec.pyi] -[out] -main:11: error: invalid syntax; you likely need to run mypy using Python 3.8 or newer -[out version>=3.8] [case testParamSpecConcatenateWithTypeVar] from typing_extensions import ParamSpec, Concatenate @@ -644,8 +708,6 @@ reveal_type(n(42)) # N: Revealed type is "None" [builtins fixtures/paramspec.pyi] [case testCallablesAsParameters] -# flags: --python-version 3.8 - # credits to https://github.com/microsoft/pyright/issues/2705 from typing_extensions import ParamSpec, Concatenate from typing import Generic, Callable, Any @@ -663,9 +725,7 @@ reveal_type(abc) bar(abc) [builtins fixtures/paramspec.pyi] [out] -main:13: error: invalid syntax; you likely need to run mypy using Python 3.8 or newer -[out version>=3.8] -main:16: note: Revealed type is "__main__.Foo[[builtins.int, b: builtins.str]]" +main:14: note: Revealed type is "__main__.Foo[[builtins.int, b: builtins.str]]" [case testSolveParamSpecWithSelfType] from typing_extensions import ParamSpec, Concatenate @@ -841,16 +901,15 @@ class A: def func(self, action: Callable[_P, _R], *args: _P.args, **kwargs: _P.kwargs) -> _R: ... -reveal_type(A.func) # N: Revealed type is "def [_P, _R] (self: __main__.A, action: def (*_P.args, **_P.kwargs) -> _R`-2, *_P.args, **_P.kwargs) -> _R`-2" -reveal_type(A().func) # N: Revealed type is "def [_P, _R] (action: def (*_P.args, **_P.kwargs) -> _R`5, *_P.args, **_P.kwargs) -> _R`5" +reveal_type(A.func) # N: Revealed type is "def [_P, _R] (self: __main__.A, action: def (*_P.args, **_P.kwargs) -> _R`4, *_P.args, **_P.kwargs) -> _R`4" +reveal_type(A().func) # N: Revealed type is "def [_P, _R] (action: def (*_P.args, **_P.kwargs) -> _R`8, *_P.args, **_P.kwargs) -> _R`8" def f(x: int) -> int: ... reveal_type(A().func(f, 42)) # N: Revealed type is "builtins.int" -# TODO: this should reveal `int` -reveal_type(A().func(lambda x: x + x, 42)) # N: Revealed type is "Any" +reveal_type(A().func(lambda x: x + x, 42)) # N: Revealed type is "builtins.int" [builtins fixtures/paramspec.pyi] [case testParamSpecConstraintOnOtherParamSpec] @@ -874,8 +933,8 @@ class A: def func(self, action: Job[_P, None]) -> Job[_P, None]: ... -reveal_type(A.func) # N: Revealed type is "def [_P] (self: __main__.A, action: __main__.Job[_P`-1, None]) -> __main__.Job[_P`-1, None]" -reveal_type(A().func) # N: Revealed type is "def [_P] (action: __main__.Job[_P`3, None]) -> __main__.Job[_P`3, None]" +reveal_type(A.func) # N: Revealed type is "def [_P] (self: __main__.A, action: __main__.Job[_P`3, None]) -> __main__.Job[_P`3, None]" +reveal_type(A().func) # N: Revealed type is "def [_P] (action: __main__.Job[_P`5, None]) -> __main__.Job[_P`5, None]" reveal_type(A().func(Job(lambda x: x))) # N: Revealed type is "__main__.Job[[x: Any], None]" def f(x: int, y: int) -> None: ... @@ -1037,10 +1096,32 @@ j = Job(generic_f) reveal_type(j) # N: Revealed type is "__main__.Job[[x: _T`-1]]" jf = j.into_callable() -reveal_type(jf) # N: Revealed type is "def [_T] (x: _T`-1)" +reveal_type(jf) # N: Revealed type is "def [_T] (x: _T`3)" reveal_type(jf(1)) # N: Revealed type is "None" [builtins fixtures/paramspec.pyi] +[case testGenericsInInferredParamspecReturn] +# flags: --new-type-inference +from typing import Callable, TypeVar, Generic +from typing_extensions import ParamSpec + +_P = ParamSpec("_P") +_T = TypeVar("_T") + +class Job(Generic[_P, _T]): + def __init__(self, target: Callable[_P, _T]) -> None: ... + def into_callable(self) -> Callable[_P, _T]: ... + +def generic_f(x: _T) -> _T: ... + +j = Job(generic_f) +reveal_type(j) # N: Revealed type is "__main__.Job[[x: _T`3], _T`3]" + +jf = j.into_callable() +reveal_type(jf) # N: Revealed type is "def [_T] (x: _T`4) -> _T`4" +reveal_type(jf(1)) # N: Revealed type is "builtins.int" +[builtins fixtures/paramspec.pyi] + [case testStackedConcatenateIsIllegal] from typing_extensions import Concatenate, ParamSpec from typing import Callable @@ -1112,7 +1193,28 @@ def func(callback: Callable[P, str]) -> Callable[P, str]: return inner [builtins fixtures/paramspec.pyi] -[case testParamSpecArgsAndKwargsMissmatch] +[case testParamSpecArgsAndKwargsStringified] +from typing import Callable +from typing_extensions import ParamSpec + +P1 = ParamSpec("P1") + +def func(callback: Callable[P1, str]) -> Callable[P1, str]: + def inner(*args: "P1.args", **kwargs: "P1.kwargs") -> str: + return "foo" + return inner + +@func +def outer(a: int) -> str: + return "" + +outer(1) # OK +outer("x") # E: Argument 1 to "outer" has incompatible type "str"; expected "int" +outer(a=1) # OK +outer(b=1) # E: Unexpected keyword argument "b" for "outer" +[builtins fixtures/paramspec.pyi] + +[case testParamSpecArgsAndKwargsMismatch] from typing import Callable from typing_extensions import ParamSpec @@ -1273,7 +1375,6 @@ P = ParamSpec('P') class Some(Generic[P]): def call(self, *args: P.args, **kwargs: P.kwargs): ... -# TODO: this probably should be reported. def call(*args: P.args, **kwargs: P.kwargs): ... [builtins fixtures/paramspec.pyi] @@ -1293,8 +1394,7 @@ reveal_type(bar(C(fn=foo, x=1))) # N: Revealed type is "__main__.C[[x: builtins [builtins fixtures/paramspec.pyi] [case testParamSpecClassConstructor] -# flags: --strict-optional -from typing import ParamSpec, Callable +from typing import ParamSpec, Callable, TypeVar P = ParamSpec("P") @@ -1302,7 +1402,10 @@ class SomeClass: def __init__(self, a: str) -> None: pass -def func(t: Callable[P, SomeClass], val: Callable[P, SomeClass]) -> None: +def func(t: Callable[P, SomeClass], val: Callable[P, SomeClass]) -> Callable[P, SomeClass]: + pass + +def func_regular(t: Callable[[T], SomeClass], val: Callable[[T], SomeClass]) -> Callable[[T], SomeClass]: pass def constructor(a: str) -> SomeClass: @@ -1311,9 +1414,13 @@ def constructor(a: str) -> SomeClass: def wrong_constructor(a: bool) -> SomeClass: return SomeClass("a") +def wrong_name_constructor(b: bool) -> SomeClass: + return SomeClass("a") + func(SomeClass, constructor) -func(SomeClass, wrong_constructor) # E: Argument 1 to "func" has incompatible type "Type[SomeClass]"; expected "Callable[[VarArg(), KwArg()], SomeClass]" \ - # E: Argument 2 to "func" has incompatible type "Callable[[bool], SomeClass]"; expected "Callable[[VarArg(), KwArg()], SomeClass]" +reveal_type(func(SomeClass, wrong_constructor)) # N: Revealed type is "def (a: Never) -> __main__.SomeClass" +reveal_type(func_regular(SomeClass, wrong_constructor)) # N: Revealed type is "def (Never) -> __main__.SomeClass" +reveal_type(func(SomeClass, wrong_name_constructor)) # N: Revealed type is "def (Never) -> __main__.SomeClass" [builtins fixtures/paramspec.pyi] [case testParamSpecInTypeAliasBasic] @@ -1411,15 +1518,15 @@ from typing import ParamSpec, Generic, List, TypeVar, Callable P = ParamSpec("P") T = TypeVar("T") A = List[T] -def f(x: A[[int, str]]) -> None: ... # E: Bracketed expression "[...]" is not valid as a type \ - # N: Did you mean "List[...]"? +def f(x: A[[int, str]]) -> None: ... # E: Bracketed expression "[...]" is not valid as a type def g(x: A[P]) -> None: ... # E: Invalid location for ParamSpec "P" \ # N: You can use ParamSpec as the first argument to Callable, e.g., 'Callable[P, int]' C = Callable[P, T] -x: C[int] # E: Bad number of arguments for type alias, expected: 2, given: 1 +x: C[int] # E: Bad number of arguments for type alias, expected 2, given 1 y: C[int, str] # E: Can only replace ParamSpec with a parameter types list or another ParamSpec, got "int" -z: C[int, str, bytes] # E: Bad number of arguments for type alias, expected: 2, given: 3 +z: C[int, str, bytes] # E: Bad number of arguments for type alias, expected 2, given 3 + [builtins fixtures/paramspec.pyi] [case testTrivialParametersHandledCorrectly] @@ -1453,8 +1560,7 @@ reveal_type(gs) # N: Revealed type is "builtins.list[def (builtins.int, builtin T = TypeVar("T") class C(Generic[T]): ... -C[Callable[P, int]]() # E: The first argument to Callable must be a list of types, parameter specification, or "..." \ - # N: See https://mypy.readthedocs.io/en/stable/kinds_of_types.html#callable-types-and-lambdas +C[Callable[P, int]]() [builtins fixtures/paramspec.pyi] [case testConcatDeferralNoCrash] @@ -1471,3 +1577,630 @@ def test(f: Concat[T, ...]) -> None: ... class Defer: ... [builtins fixtures/paramspec.pyi] + +[case testNoParamSpecDoubling] +# https://github.com/python/mypy/issues/12734 +from typing import Callable, ParamSpec +from typing_extensions import Concatenate + +P = ParamSpec("P") +Q = ParamSpec("Q") + +def foo(f: Callable[P, int]) -> Callable[P, int]: + return f + +def bar(f: Callable[Concatenate[str, Q], int]) -> Callable[Concatenate[str, Q], int]: + return foo(f) +[builtins fixtures/paramspec.pyi] + +[case testAlreadyExpandedCallableWithParamSpecReplacement] +from typing import Callable, Any, overload +from typing_extensions import Concatenate, ParamSpec + +P = ParamSpec("P") + +@overload +def command() -> Callable[[Callable[Concatenate[object, object, P], object]], None]: # E: Overloaded function signatures 1 and 2 overlap with incompatible return types + ... + +@overload +def command( + cls: int = ..., +) -> Callable[[Callable[Concatenate[object, P], object]], None]: + ... + +def command( + cls: int = 42, +) -> Any: + ... +[builtins fixtures/paramspec.pyi] + +[case testCopiedParamSpecComparison] +# minimized from https://github.com/python/mypy/issues/12909 +from typing import Callable +from typing_extensions import ParamSpec + +P = ParamSpec("P") + +def identity(func: Callable[P, None]) -> Callable[P, None]: ... + +@identity +def f(f: Callable[P, None], *args: P.args, **kwargs: P.kwargs) -> None: ... +[builtins fixtures/paramspec.pyi] + +[case testParamSpecDecoratorAppliedToGeneric] +# flags: --new-type-inference +from typing import Callable, List, TypeVar +from typing_extensions import ParamSpec + +P = ParamSpec("P") +T = TypeVar("T") +U = TypeVar("U") + +def dec(f: Callable[P, T]) -> Callable[P, List[T]]: ... +def test(x: U) -> U: ... +reveal_type(dec) # N: Revealed type is "def [P, T] (f: def (*P.args, **P.kwargs) -> T`-2) -> def (*P.args, **P.kwargs) -> builtins.list[T`-2]" +reveal_type(dec(test)) # N: Revealed type is "def [T] (x: T`3) -> builtins.list[T`3]" + +class A: ... +TA = TypeVar("TA", bound=A) + +def test_with_bound(x: TA) -> TA: ... +reveal_type(dec(test_with_bound)) # N: Revealed type is "def [T <: __main__.A] (x: T`5) -> builtins.list[T`5]" +dec(test_with_bound)(0) # E: Value of type variable "T" of function cannot be "int" +dec(test_with_bound)(A()) # OK +[builtins fixtures/paramspec.pyi] + +[case testParamSpecArgumentParamInferenceRegular] +from typing import TypeVar, Generic +from typing_extensions import ParamSpec + +P = ParamSpec("P") +class Foo(Generic[P]): + def call(self, *args: P.args, **kwargs: P.kwargs) -> None: ... +def test(*args: P.args, **kwargs: P.kwargs) -> Foo[P]: ... + +reveal_type(test(1, 2)) # N: Revealed type is "__main__.Foo[[Literal[1]?, Literal[2]?]]" +reveal_type(test(x=1, y=2)) # N: Revealed type is "__main__.Foo[[x: Literal[1]?, y: Literal[2]?]]" +ints = [1, 2, 3] +reveal_type(test(*ints)) # N: Revealed type is "__main__.Foo[[*builtins.int]]" +[builtins fixtures/paramspec.pyi] + +[case testParamSpecArgumentParamInferenceGeneric] +# flags: --new-type-inference +from typing import Callable, TypeVar +from typing_extensions import ParamSpec + +P = ParamSpec("P") +R = TypeVar("R") +def call(f: Callable[P, R], *args: P.args, **kwargs: P.kwargs) -> R: + return f(*args, **kwargs) + +T = TypeVar("T") +def identity(x: T) -> T: + return x + +reveal_type(call(identity, 2)) # N: Revealed type is "builtins.int" +y: int = call(identity, 2) +[builtins fixtures/paramspec.pyi] + +[case testParamSpecNestedApplyNoCrash] +# flags: --new-type-inference +from typing import Callable, TypeVar +from typing_extensions import ParamSpec + +P = ParamSpec("P") +T = TypeVar("T") + +def apply(fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T: ... +def test() -> int: ... +reveal_type(apply(apply, test)) # N: Revealed type is "builtins.int" +[builtins fixtures/paramspec.pyi] + +[case testParamSpecNestedApplyPosVsNamed] +from typing import Callable, TypeVar +from typing_extensions import ParamSpec + +P = ParamSpec("P") +T = TypeVar("T") + +def apply(fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> None: ... + +def test(x: int) -> int: ... +apply(apply, test, x=42) # OK +apply(apply, test, 42) # Also OK (but requires some special casing) +apply(apply, test, "bad") # E: Argument 1 to "apply" has incompatible type "Callable[[Callable[P, T], **P], None]"; expected "Callable[[Callable[[int], int], str], None]" + +def test2(x: int, y: str) -> None: ... +apply(apply, test2, 42, "yes") +apply(apply, test2, "no", 42) # E: Argument 1 to "apply" has incompatible type "Callable[[Callable[P, T], **P], None]"; expected "Callable[[Callable[[int, str], None], str, int], None]" +apply(apply, test2, x=42, y="yes") +apply(apply, test2, y="yes", x=42) +apply(apply, test2, y=42, x="no") # E: Argument 1 to "apply" has incompatible type "Callable[[Callable[P, T], **P], None]"; expected "Callable[[Callable[[int, str], None], int, str], None]" +[builtins fixtures/paramspec.pyi] + +[case testParamSpecApplyPosVsNamedOptional] +from typing import Callable, TypeVar +from typing_extensions import ParamSpec + +P = ParamSpec("P") +T = TypeVar("T") + +def apply(fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> None: ... +def test(x: str = ..., y: int = ...) -> int: ... +apply(test, y=42) # OK +[builtins fixtures/paramspec.pyi] + +[case testParamSpecPrefixSubtypingGenericInvalid] +from typing import Generic +from typing_extensions import ParamSpec, Concatenate + +P = ParamSpec("P") + +class A(Generic[P]): + def foo(self, *args: P.args, **kwargs: P.kwargs): + ... + +def bar(b: A[P]) -> A[Concatenate[int, P]]: + return b # E: Incompatible return value type (got "A[P]", expected "A[[int, **P]]") +[builtins fixtures/paramspec.pyi] + +[case testParamSpecPrefixSubtypingProtocolInvalid] +from typing import Protocol +from typing_extensions import ParamSpec, Concatenate + +P = ParamSpec("P") + +class A(Protocol[P]): + def foo(self, *args: P.args, **kwargs: P.kwargs): + ... + +def bar(b: A[P]) -> A[Concatenate[int, P]]: + return b # E: Incompatible return value type (got "A[P]", expected "A[[int, **P]]") \ + # N: Following member(s) of "A[P]" have conflicts: \ + # N: Expected: \ + # N: def foo(self, int, /, *args: P.args, **kwargs: P.kwargs) -> Any \ + # N: Got: \ + # N: def foo(self, *args: P.args, **kwargs: P.kwargs) -> Any +[builtins fixtures/paramspec.pyi] + +[case testParamSpecPrefixSubtypingValidNonStrict] +from typing import Protocol +from typing_extensions import ParamSpec, Concatenate + +P = ParamSpec("P") + +class A(Protocol[P]): + def foo(self, a: int, *args: P.args, **kwargs: P.kwargs): + ... + +class B(Protocol[P]): + def foo(self, a: int, b: int, *args: P.args, **kwargs: P.kwargs): + ... + +def bar(b: B[P]) -> A[Concatenate[int, P]]: + return b +[builtins fixtures/paramspec.pyi] + +[case testParamSpecPrefixSubtypingInvalidStrict] +# flags: --extra-checks +from typing import Protocol +from typing_extensions import ParamSpec, Concatenate + +P = ParamSpec("P") + +class A(Protocol[P]): + def foo(self, a: int, *args: P.args, **kwargs: P.kwargs): + ... + +class B(Protocol[P]): + def foo(self, a: int, b: int, *args: P.args, **kwargs: P.kwargs): + ... + +def bar(b: B[P]) -> A[Concatenate[int, P]]: + return b # E: Incompatible return value type (got "B[P]", expected "A[[int, **P]]") \ + # N: Following member(s) of "B[P]" have conflicts: \ + # N: Expected: \ + # N: def foo(self, a: int, int, /, *args: P.args, **kwargs: P.kwargs) -> Any \ + # N: Got: \ + # N: def foo(self, a: int, b: int, *args: P.args, **kwargs: P.kwargs) -> Any +[builtins fixtures/paramspec.pyi] + +[case testParamSpecDecoratorOverload] +from typing import Callable, overload, TypeVar, List +from typing_extensions import ParamSpec + +P = ParamSpec("P") +T = TypeVar("T") +def transform(func: Callable[P, List[T]]) -> Callable[P, T]: ... + +@overload +def foo(x: int) -> List[float]: ... +@overload +def foo(x: str) -> List[str]: ... +def foo(x): ... + +reveal_type(transform(foo)) # N: Revealed type is "Overload(def (x: builtins.int) -> builtins.float, def (x: builtins.str) -> builtins.str)" + +@transform +@overload +def bar(x: int) -> List[float]: ... +@transform +@overload +def bar(x: str) -> List[str]: ... +@transform +def bar(x): ... + +reveal_type(bar) # N: Revealed type is "Overload(def (x: builtins.int) -> builtins.float, def (x: builtins.str) -> builtins.str)" +[builtins fixtures/paramspec.pyi] + +[case testParamSpecDecoratorOverloadNoCrashOnInvalidTypeVar] +from typing import Any, Callable, List +from typing_extensions import ParamSpec + +P = ParamSpec("P") +T = 1 + +Alias = Callable[P, List[T]] # type: ignore +def dec(fn: Callable[P, T]) -> Alias[P, T]: ... # type: ignore +f: Any +dec(f) # No crash +[builtins fixtures/paramspec.pyi] + +[case testParamSpecErrorNestedParams] +from typing import Generic +from typing_extensions import ParamSpec + +P = ParamSpec("P") +class C(Generic[P]): ... +c: C[int, [int, str], str] # E: Nested parameter specifications are not allowed +reveal_type(c) # N: Revealed type is "__main__.C[Any]" +[builtins fixtures/paramspec.pyi] + +[case testParamSpecConcatenateSelfType] +from typing import Callable +from typing_extensions import ParamSpec, Concatenate + +P = ParamSpec("P") +class A: + def __init__(self, a_param_1: str) -> None: ... + + @classmethod + def add_params(cls: Callable[P, A]) -> Callable[Concatenate[float, P], A]: + def new_constructor(i: float, *args: P.args, **kwargs: P.kwargs) -> A: + return cls(*args, **kwargs) + return new_constructor + + @classmethod + def remove_params(cls: Callable[Concatenate[str, P], A]) -> Callable[P, A]: + def new_constructor(*args: P.args, **kwargs: P.kwargs) -> A: + return cls("my_special_str", *args, **kwargs) + return new_constructor + +reveal_type(A.add_params()) # N: Revealed type is "def (builtins.float, a_param_1: builtins.str) -> __main__.A" +reveal_type(A.remove_params()) # N: Revealed type is "def () -> __main__.A" +[builtins fixtures/paramspec.pyi] + +[case testParamSpecConcatenateCallbackProtocol] +from typing import Protocol, TypeVar +from typing_extensions import ParamSpec, Concatenate + +P = ParamSpec("P") +R = TypeVar("R", covariant=True) + +class Path: ... + +class Function(Protocol[P, R]): + def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: ... + +def file_cache(fn: Function[Concatenate[Path, P], R]) -> Function[P, R]: + def wrapper(*args: P.args, **kw: P.kwargs) -> R: + return fn(Path(), *args, **kw) + return wrapper + +@file_cache +def get_thing(path: Path, *, some_arg: int) -> int: ... +reveal_type(get_thing) # N: Revealed type is "__main__.Function[[*, some_arg: builtins.int], builtins.int]" +get_thing(some_arg=1) # OK +[builtins fixtures/paramspec.pyi] + +[case testParamSpecConcatenateKeywordOnly] +from typing import Callable, TypeVar +from typing_extensions import ParamSpec, Concatenate + +P = ParamSpec("P") +R = TypeVar("R") + +class Path: ... + +def file_cache(fn: Callable[Concatenate[Path, P], R]) -> Callable[P, R]: + def wrapper(*args: P.args, **kw: P.kwargs) -> R: + return fn(Path(), *args, **kw) + return wrapper + +@file_cache +def get_thing(path: Path, *, some_arg: int) -> int: ... +reveal_type(get_thing) # N: Revealed type is "def (*, some_arg: builtins.int) -> builtins.int" +get_thing(some_arg=1) # OK +[builtins fixtures/paramspec.pyi] + +[case testParamSpecConcatenateCallbackApply] +from typing import Callable, Protocol +from typing_extensions import ParamSpec, Concatenate + +P = ParamSpec("P") + +class FuncType(Protocol[P]): + def __call__(self, x: int, s: str, *args: P.args, **kw_args: P.kwargs) -> str: ... + +def forwarder1(fp: FuncType[P], *args: P.args, **kw_args: P.kwargs) -> str: + return fp(0, '', *args, **kw_args) + +def forwarder2(fp: Callable[Concatenate[int, str, P], str], *args: P.args, **kw_args: P.kwargs) -> str: + return fp(0, '', *args, **kw_args) + +def my_f(x: int, s: str, d: bool) -> str: ... +forwarder1(my_f, True) # OK +forwarder2(my_f, True) # OK +forwarder1(my_f, 1.0) # E: Argument 2 to "forwarder1" has incompatible type "float"; expected "bool" +forwarder2(my_f, 1.0) # E: Argument 2 to "forwarder2" has incompatible type "float"; expected "bool" +[builtins fixtures/paramspec.pyi] + +[case testParamSpecCallbackProtocolSelf] +from typing import Callable, Protocol, TypeVar +from typing_extensions import ParamSpec, Concatenate + +Params = ParamSpec("Params") +Result = TypeVar("Result", covariant=True) + +class FancyMethod(Protocol): + def __call__(self, arg1: int, arg2: str) -> bool: ... + def return_me(self: Callable[Params, Result]) -> Callable[Params, Result]: ... + def return_part(self: Callable[Concatenate[int, Params], Result]) -> Callable[Params, Result]: ... + +m: FancyMethod +reveal_type(m.return_me()) # N: Revealed type is "def (arg1: builtins.int, arg2: builtins.str) -> builtins.bool" +reveal_type(m.return_part()) # N: Revealed type is "def (arg2: builtins.str) -> builtins.bool" +[builtins fixtures/paramspec.pyi] + +[case testParamSpecInferenceCallableAgainstAny] +from typing import Callable, TypeVar, Any +from typing_extensions import ParamSpec, Concatenate + +_P = ParamSpec("_P") +_R = TypeVar("_R") + +class A: ... +a = A() + +def a_func( + func: Callable[Concatenate[A, _P], _R], +) -> Callable[Concatenate[Any, _P], _R]: + def wrapper(__a: Any, *args: _P.args, **kwargs: _P.kwargs) -> _R: + return func(a, *args, **kwargs) + return wrapper + +def test(a, *args): ... +x: Any +y: object + +a_func(test) +x = a_func(test) +y = a_func(test) +[builtins fixtures/paramspec.pyi] + +[case testParamSpecInferenceWithCallbackProtocol] +from typing import Protocol, Callable, ParamSpec + +class CB(Protocol): + def __call__(self, x: str, y: int) -> None: ... + +P = ParamSpec('P') +def g(fn: Callable[P, None], *args: P.args, **kwargs: P.kwargs) -> None: ... + +cb: CB +g(cb, y=0, x='a') # OK +g(cb, y='a', x=0) # E: Argument "y" to "g" has incompatible type "str"; expected "int" \ + # E: Argument "x" to "g" has incompatible type "int"; expected "str" +[builtins fixtures/paramspec.pyi] + +[case testParamSpecBadRuntimeTypeApplication] +from typing import ParamSpec, TypeVar, Generic, Callable + +R = TypeVar("R") +P = ParamSpec("P") +class C(Generic[P, R]): + x: Callable[P, R] + +bad = C[int, str]() # E: Can only replace ParamSpec with a parameter types list or another ParamSpec, got "int" +reveal_type(bad) # N: Revealed type is "__main__.C[Any, Any]" +reveal_type(bad.x) # N: Revealed type is "def (*Any, **Any) -> Any" +[builtins fixtures/paramspec.pyi] + +[case testParamSpecNoCrashOnUnificationAlias] +import mod +[file mod.pyi] +from typing import Callable, Protocol, TypeVar, overload +from typing_extensions import ParamSpec + +P = ParamSpec("P") +R_co = TypeVar("R_co", covariant=True) +Handler = Callable[P, R_co] + +class HandlerDecorator(Protocol): + def __call__(self, handler: Handler[P, R_co]) -> Handler[P, R_co]: ... + +@overload +def event(event_handler: Handler[P, R_co]) -> Handler[P, R_co]: ... +@overload +def event(namespace: str, *args, **kwargs) -> HandlerDecorator: ... +[builtins fixtures/paramspec.pyi] + +[case testParamSpecNoCrashOnUnificationCallable] +import mod +[file mod.pyi] +from typing import Callable, Protocol, TypeVar, overload +from typing_extensions import ParamSpec + +P = ParamSpec("P") +R_co = TypeVar("R_co", covariant=True) + +class HandlerDecorator(Protocol): + def __call__(self, handler: Callable[P, R_co]) -> Callable[P, R_co]: ... + +@overload +def event(event_handler: Callable[P, R_co]) -> Callable[P, R_co]: ... +@overload +def event(namespace: str, *args, **kwargs) -> HandlerDecorator: ... +[builtins fixtures/paramspec.pyi] + +[case testParamSpecNoCrashOnUnificationPrefix] +from typing import Any, Callable, TypeVar, overload +from typing_extensions import ParamSpec, Concatenate + +T = TypeVar("T") +U = TypeVar("U") +V = TypeVar("V") +W = TypeVar("W") +P = ParamSpec("P") + +@overload +def call( + func: Callable[Concatenate[T, P], U], + x: T, + *args: Any, + **kwargs: Any, +) -> U: ... +@overload +def call( + func: Callable[Concatenate[T, U, P], V], + x: T, + y: U, + *args: Any, + **kwargs: Any, +) -> V: ... +def call(*args: Any, **kwargs: Any) -> Any: ... + +def test1(x: int) -> str: ... +def test2(x: int, y: int) -> str: ... +reveal_type(call(test1, 1)) # N: Revealed type is "builtins.str" +reveal_type(call(test2, 1, 2)) # N: Revealed type is "builtins.str" +[builtins fixtures/paramspec.pyi] + +[case testParamSpecCorrectParameterNameInference] +from typing import Callable, Protocol +from typing_extensions import ParamSpec, Concatenate + +def a(i: int) -> None: ... +def b(__i: int) -> None: ... + +class WithName(Protocol): + def __call__(self, i: int) -> None: ... +NoName = Callable[[int], None] + +def f1(__fn: WithName, i: int) -> None: ... +def f2(__fn: NoName, i: int) -> None: ... + +P = ParamSpec("P") +def d(f: Callable[P, None], fn: Callable[Concatenate[Callable[P, None], P], None]) -> Callable[P, None]: + def inner(*args: P.args, **kwargs: P.kwargs) -> None: + fn(f, *args, **kwargs) + return inner + +reveal_type(d(a, f1)) # N: Revealed type is "def (i: builtins.int)" +reveal_type(d(a, f2)) # N: Revealed type is "def (i: builtins.int)" +reveal_type(d(b, f1)) # E: Cannot infer type argument 1 of "d" \ + # N: Revealed type is "def (*Any, **Any)" +reveal_type(d(b, f2)) # N: Revealed type is "def (builtins.int)" +[builtins fixtures/paramspec.pyi] + +[case testParamSpecGenericWithNamedArg1] +from typing import Callable, TypeVar +from typing_extensions import ParamSpec + +R = TypeVar("R") +P = ParamSpec("P") + +def run(func: Callable[[], R], *args: object, backend: str = "asyncio") -> R: ... +class Result: ... +def run_portal() -> Result: ... +def submit(func: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs) -> R: ... + +reveal_type(submit( # N: Revealed type is "__main__.Result" + run, + run_portal, + backend="asyncio", +)) +submit( + run, # E: Argument 1 to "submit" has incompatible type "Callable[[Callable[[], R], VarArg(object), DefaultNamedArg(str, 'backend')], R]"; expected "Callable[[Callable[[], Result], int], Result]" + run_portal, + backend=int(), +) +[builtins fixtures/paramspec.pyi] + +[case testParamSpecGenericWithNamedArg2] +from typing import Callable, TypeVar, Type +from typing_extensions import ParamSpec + +P= ParamSpec("P") +T = TypeVar("T") + +def smoke_testable(*args: P.args, **kwargs: P.kwargs) -> Callable[[Callable[P, T]], Type[T]]: + ... + +@smoke_testable(name="bob", size=512, flt=0.5) +class SomeClass: + def __init__(self, size: int, name: str, flt: float) -> None: + pass + +# Error message is confusing, but this is a known issue, see #4530. +@smoke_testable(name=42, size="bad", flt=0.5) # E: Argument 1 has incompatible type "Type[OtherClass]"; expected "Callable[[int, str, float], OtherClass]" +class OtherClass: + def __init__(self, size: int, name: str, flt: float) -> None: + pass +[builtins fixtures/paramspec.pyi] + +[case testInferenceAgainstGenericCallableUnionParamSpec] +from typing import Callable, TypeVar, List, Union +from typing_extensions import ParamSpec + +T = TypeVar("T") +P = ParamSpec("P") + +def dec(f: Callable[P, T]) -> Callable[P, List[T]]: ... +@dec +def func(arg: T) -> Union[T, str]: + ... +reveal_type(func) # N: Revealed type is "def [T] (arg: T`-1) -> builtins.list[Union[T`-1, builtins.str]]" +reveal_type(func(42)) # N: Revealed type is "builtins.list[Union[builtins.int, builtins.str]]" + +def dec2(f: Callable[P, List[T]]) -> Callable[P, T]: ... +@dec2 +def func2(arg: T) -> List[Union[T, str]]: + ... +reveal_type(func2) # N: Revealed type is "def [T] (arg: T`-1) -> Union[T`-1, builtins.str]" +reveal_type(func2(42)) # N: Revealed type is "Union[builtins.int, builtins.str]" +[builtins fixtures/paramspec.pyi] + +[case testParamSpecPreciseKindsUsedIfPossible] +from typing import Callable, Generic +from typing_extensions import ParamSpec + +P = ParamSpec('P') + +class Case(Generic[P]): + def __init__(self, *args: P.args, **kwargs: P.kwargs) -> None: + pass + +def _test(a: int, b: int = 0) -> None: ... + +def parametrize( + func: Callable[P, None], *cases: Case[P], **named_cases: Case[P] +) -> Callable[[], None]: + ... + +parametrize(_test, Case(1, 2), Case(3, 4)) +parametrize(_test, Case(1, b=2), Case(3, b=4)) +parametrize(_test, Case(1, 2), Case(3)) +parametrize(_test, Case(1, 2), Case(3, b=4)) +[builtins fixtures/paramspec.pyi] diff --git a/test-data/unit/check-attr.test b/test-data/unit/check-plugin-attrs.test similarity index 73% rename from test-data/unit/check-attr.test rename to test-data/unit/check-plugin-attrs.test index f6ef289e792e..39b266dba50e 100644 --- a/test-data/unit/check-attr.test +++ b/test-data/unit/check-plugin-attrs.test @@ -185,10 +185,10 @@ from attr import attrib, attrs class A: a: int reveal_type(A) # N: Revealed type is "def (a: builtins.int) -> __main__.A" -reveal_type(A.__lt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" -reveal_type(A.__le__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" -reveal_type(A.__gt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" -reveal_type(A.__ge__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" +reveal_type(A.__lt__) # N: Revealed type is "def [_AT] (self: _AT`3, other: _AT`3) -> builtins.bool" +reveal_type(A.__le__) # N: Revealed type is "def [_AT] (self: _AT`4, other: _AT`4) -> builtins.bool" +reveal_type(A.__gt__) # N: Revealed type is "def [_AT] (self: _AT`5, other: _AT`5) -> builtins.bool" +reveal_type(A.__ge__) # N: Revealed type is "def [_AT] (self: _AT`6, other: _AT`6) -> builtins.bool" A(1) < A(2) A(1) <= A(2) @@ -210,7 +210,7 @@ A(1) != 1 1 >= A(1) # E: Unsupported operand types for <= ("A" and "int") 1 == A(1) 1 != A(1) -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsEqFalse] from attr import attrib, attrs @@ -241,7 +241,7 @@ A(1) != 1 1 >= A(1) # E: Unsupported left operand type for >= ("int") 1 == A(1) 1 != A(1) -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsOrderFalse] from attr import attrib, attrs @@ -270,7 +270,7 @@ A(1) != 1 1 >= A(1) # E: Unsupported left operand type for >= ("int") 1 == A(1) 1 != A(1) -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsCmpEqOrderValues] from attr import attrib, attrs @@ -289,7 +289,7 @@ class Mixed: @attrs(order=True, eq=False) # E: eq must be True if order is True class Confused: ... -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsInheritance] @@ -360,7 +360,8 @@ class A: a = A(5) a.a = 16 # E: Property "a" defined in "A" is read-only -[builtins fixtures/bool.pyi] +[builtins fixtures/plugin_attrs.pyi] + [case testAttrsNextGenFrozen] from attr import frozen, field @@ -370,7 +371,7 @@ class A: a = A(5) a.a = 16 # E: Property "a" defined in "A" is read-only -[builtins fixtures/bool.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsNextGenDetect] from attr import define, field @@ -400,22 +401,27 @@ reveal_type(D) # N: Revealed type is "def (b: Any) -> __main__.D" [builtins fixtures/bool.pyi] -[case testAttrsNewPackage] -import attrs -@attrs.define +[case testAttrsOldPackage] +import attr +@attr.s(auto_attribs=True) class A: - a: int = attrs.field() + a: int = attr.ib() b: bool -@attrs.frozen +@attr.s(auto_attribs=True, frozen=True) class B: a: bool b: int +@attr.s +class C: + a = attr.ib(type=int) + reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.bool) -> __main__.A" reveal_type(B) # N: Revealed type is "def (a: builtins.bool, b: builtins.int) -> __main__.B" +reveal_type(C) # N: Revealed type is "def (a: builtins.int) -> __main__.C" -[builtins fixtures/bool.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsDataClass] import attr @@ -469,6 +475,56 @@ A([1], '2') # E: Cannot infer type argument 1 of "A" [builtins fixtures/list.pyi] +[case testAttrsGenericWithConverter] +from typing import TypeVar, Generic, List, Iterable, Iterator, Callable +import attr +T = TypeVar('T') + +def int_gen() -> Iterator[int]: + yield 1 + +def list_converter(x: Iterable[T]) -> List[T]: + return list(x) + +@attr.s(auto_attribs=True) +class A(Generic[T]): + x: List[T] = attr.ib(converter=list_converter) + y: T = attr.ib() + def foo(self) -> List[T]: + return [self.y] + def bar(self) -> T: + return self.x[0] + def problem(self) -> T: + return self.x # E: Incompatible return value type (got "List[T]", expected "T") +reveal_type(A) # N: Revealed type is "def [T] (x: typing.Iterable[T`1], y: T`1) -> __main__.A[T`1]" +a1 = A([1], 2) +reveal_type(a1) # N: Revealed type is "__main__.A[builtins.int]" +reveal_type(a1.x) # N: Revealed type is "builtins.list[builtins.int]" +reveal_type(a1.y) # N: Revealed type is "builtins.int" + +a2 = A(int_gen(), 2) +reveal_type(a2) # N: Revealed type is "__main__.A[builtins.int]" +reveal_type(a2.x) # N: Revealed type is "builtins.list[builtins.int]" +reveal_type(a2.y) # N: Revealed type is "builtins.int" + + +def get_int() -> int: + return 1 + +class Other(Generic[T]): + def __init__(self, x: T) -> None: + pass + +@attr.s(auto_attribs=True) +class B(Generic[T]): + x: Other[Callable[..., T]] = attr.ib(converter=Other[Callable[..., T]]) + +b1 = B(get_int) +reveal_type(b1) # N: Revealed type is "__main__.B[builtins.int]" +reveal_type(b1.x) # N: Revealed type is "__main__.Other[def (*Any, **Any) -> builtins.int]" + +[builtins fixtures/list.pyi] + [case testAttrsUntypedGenericInheritance] from typing import Generic, TypeVar @@ -617,6 +673,7 @@ class A(Generic[T]): [builtins fixtures/classmethod.pyi] [case testAttrsForwardReference] +# flags: --no-strict-optional import attr @attr.s(auto_attribs=True) class A: @@ -632,6 +689,7 @@ A(B(None)) [builtins fixtures/list.pyi] [case testAttrsForwardReferenceInClass] +# flags: --no-strict-optional import attr @attr.s(auto_attribs=True) class A: @@ -919,7 +977,7 @@ class C: o = C("1", "2", "3") o = C(1, 2, "3") -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsCmpWithSubclasses] import attr @@ -932,10 +990,10 @@ class C(A, B): pass @attr.s class D(A): pass -reveal_type(A.__lt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" -reveal_type(B.__lt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" -reveal_type(C.__lt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" -reveal_type(D.__lt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" +reveal_type(A.__lt__) # N: Revealed type is "def [_AT] (self: _AT`5, other: _AT`5) -> builtins.bool" +reveal_type(B.__lt__) # N: Revealed type is "def [_AT] (self: _AT`6, other: _AT`6) -> builtins.bool" +reveal_type(C.__lt__) # N: Revealed type is "def [_AT] (self: _AT`7, other: _AT`7) -> builtins.bool" +reveal_type(D.__lt__) # N: Revealed type is "def [_AT] (self: _AT`8, other: _AT`8) -> builtins.bool" A() < A() B() < B() @@ -998,6 +1056,31 @@ C() C(_x=42) # E: Unexpected keyword argument "_x" for "C" [builtins fixtures/list.pyi] +[case testAttrsAliasForInit] +from attrs import define, field + +@define +class C1: + _x: int = field(alias="x1") + +c1 = C1(x1=42) +reveal_type(c1._x) # N: Revealed type is "builtins.int" +c1.x1 # E: "C1" has no attribute "x1" +C1(_x=42) # E: Unexpected keyword argument "_x" for "C1" + +alias = "x2" +@define +class C2: + _x: int = field(alias=alias) # E: "alias" argument to attrs field must be a string literal + +@define +class C3: + _x: int = field(alias="_x") + +c3 = C3(_x=1) +reveal_type(c3._x) # N: Revealed type is "builtins.int" +[builtins fixtures/plugin_attrs.pyi] + [case testAttrsAutoMustBeAll] import attr @attr.s(auto_attribs=True) @@ -1073,7 +1156,7 @@ c = NonFrozenFrozen(1, 2) c.a = 17 # E: Property "a" defined in "NonFrozenFrozen" is read-only c.b = 17 # E: Property "b" defined in "NonFrozenFrozen" is read-only -[builtins fixtures/bool.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsCallableAttributes] from typing import Callable import attr @@ -1096,7 +1179,7 @@ class G: class FFrozen(F): def bar(self) -> bool: return self._cb(5, 6) -[builtins fixtures/callable.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsWithFactory] from typing import List @@ -1118,12 +1201,13 @@ class A: [builtins fixtures/bool.pyi] [case testAttrsFactoryBadReturn] +# flags: --new-type-inference import attr def my_factory() -> int: return 7 @attr.s class A: - x: int = attr.ib(factory=list) # E: Incompatible types in assignment (expression has type "List[T]", variable has type "int") + x: int = attr.ib(factory=list) # E: Incompatible types in assignment (expression has type "List[Never]", variable has type "int") y: str = attr.ib(factory=my_factory) # E: Incompatible types in assignment (expression has type "int", variable has type "str") [builtins fixtures/list.pyi] @@ -1141,7 +1225,6 @@ class C: [builtins fixtures/bool.pyi] [case testAttrsOptionalConverter] -# flags: --strict-optional import attr from attr.converters import optional from typing import Optional @@ -1158,10 +1241,9 @@ class A: A(None, None) -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsOptionalConverterNewPackage] -# flags: --strict-optional import attrs from attrs.converters import optional from typing import Optional @@ -1178,7 +1260,7 @@ class A: A(None, None) -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsTypeVarNoCollision] @@ -1191,7 +1273,7 @@ T = TypeVar("T", bytes, str) @attr.s(auto_attribs=True) class A(Generic[T]): v: T -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsKwOnlyAttrib] import attr @@ -1201,7 +1283,7 @@ class A: A() # E: Missing named argument "a" for "A" A(15) # E: Too many positional arguments for "A" A(a=15) -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsKwOnlyClass] import attr @@ -1211,7 +1293,7 @@ class A: b: bool A() # E: Missing named argument "a" for "A" # E: Missing named argument "b" for "A" A(b=True, a=15) -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsKwOnlyClassNoInit] import attr @@ -1220,7 +1302,7 @@ class B: a = attr.ib(init=False) b = attr.ib() B(b=True) -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsKwOnlyWithDefault] import attr @@ -1230,7 +1312,7 @@ class C: b = attr.ib(kw_only=True) c = attr.ib(16, kw_only=True) C(b=17) -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsKwOnlyClassWithMixedDefaults] import attr @@ -1240,7 +1322,7 @@ class D: b = attr.ib() c = attr.ib(15) D(b=17) -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsKwOnlySubclass] @@ -1252,7 +1334,7 @@ class A2: class B2(A2): b = attr.ib(kw_only=True) B2(b=1) -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsNonKwOnlyAfterKwOnly] import attr @@ -1267,7 +1349,7 @@ class C: a = attr.ib(kw_only=True) b = attr.ib(15) -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsDisallowUntypedWorksForward] # flags: --disallow-untyped-defs @@ -1369,7 +1451,7 @@ class C: total = attr.ib(type=Bad) # E: Name "Bad" is not defined C(0).total = 1 # E: Property "total" defined in "C" is read-only -[builtins fixtures/bool.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testTypeInAttrDeferredStar] import lib @@ -1441,7 +1523,7 @@ reveal_type(A.__attrs_attrs__[0]) # N: Revealed type is "attr.Attribute[builtin reveal_type(A.__attrs_attrs__.b) # N: Revealed type is "attr.Attribute[builtins.int]" A.__attrs_attrs__.x # E: "____main___A_AttrsAttributes__" has no attribute "x" -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsBareClassHasMagicAttribute] import attr @@ -1456,7 +1538,7 @@ reveal_type(A.__attrs_attrs__[0]) # N: Revealed type is "attr.Attribute[Any]" reveal_type(A.__attrs_attrs__.b) # N: Revealed type is "attr.Attribute[Any]" A.__attrs_attrs__.x # E: "____main___A_AttrsAttributes__" has no attribute "x" -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsNGClassHasMagicAttribute] import attr @@ -1471,7 +1553,7 @@ reveal_type(A.__attrs_attrs__[0]) # N: Revealed type is "attr.Attribute[builtin reveal_type(A.__attrs_attrs__.b) # N: Revealed type is "attr.Attribute[builtins.int]" A.__attrs_attrs__.x # E: "____main___A_AttrsAttributes__" has no attribute "x" -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsMagicAttributeProtocol] import attr @@ -1496,7 +1578,66 @@ takes_attrs_instance(A(1, "")) takes_attrs_cls(A(1, "")) # E: Argument 1 to "takes_attrs_cls" has incompatible type "A"; expected "Type[AttrsInstance]" takes_attrs_instance(A) # E: Argument 1 to "takes_attrs_instance" has incompatible type "Type[A]"; expected "AttrsInstance" # N: ClassVar protocol member AttrsInstance.__attrs_attrs__ can never be matched by a class object -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsFields] +import attr +from attrs import fields as f # Common usage. + +@attr.define +class A: + b: int + c: str + +reveal_type(f(A)) # N: Revealed type is "Tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str], fallback=__main__.A.____main___A_AttrsAttributes__]" +reveal_type(f(A)[0]) # N: Revealed type is "attr.Attribute[builtins.int]" +reveal_type(f(A).b) # N: Revealed type is "attr.Attribute[builtins.int]" +f(A).x # E: "____main___A_AttrsAttributes__" has no attribute "x" + +for ff in f(A): + reveal_type(ff) # N: Revealed type is "attr.Attribute[Any]" + +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsGenericFields] +from typing import TypeVar + +import attr +from attrs import fields + +@attr.define +class A: + b: int + c: str + +TA = TypeVar('TA', bound=A) + +def f(t: TA) -> None: + reveal_type(fields(t)) # N: Revealed type is "Tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str], fallback=__main__.A.____main___A_AttrsAttributes__]" + reveal_type(fields(t)[0]) # N: Revealed type is "attr.Attribute[builtins.int]" + reveal_type(fields(t).b) # N: Revealed type is "attr.Attribute[builtins.int]" + fields(t).x # E: "____main___A_AttrsAttributes__" has no attribute "x" + + +[builtins fixtures/plugin_attrs.pyi] + +[case testNonattrsFields] +from typing import Any, cast, Type +from attrs import fields, has + +class A: + b: int + c: str + +if has(A): + fields(A) +else: + fields(A) # E: Argument 1 to "fields" has incompatible type "Type[A]"; expected "Type[AttrsInstance]" +fields(None) # E: Argument 1 to "fields" has incompatible type "None"; expected "Type[AttrsInstance]" +fields(cast(Any, 42)) +fields(cast(Type[Any], 43)) + +[builtins fixtures/plugin_attrs.pyi] [case testAttrsInitMethodAlwaysGenerates] from typing import Tuple @@ -1514,11 +1655,29 @@ reveal_type(A) # N: Revealed type is "def (bc: Tuple[builtins.int, builtins.str reveal_type(A.__init__) # N: Revealed type is "def (self: __main__.A, bc: Tuple[builtins.int, builtins.str])" reveal_type(A.__attrs_init__) # N: Revealed type is "def (self: __main__.A, b: builtins.int, c: builtins.str)" -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsClassWithSlots] import attr +@attr.define +class Define: + b: int = attr.ib() + + def __attrs_post_init__(self) -> None: + self.b = 1 + self.c = 2 # E: Trying to assign name "c" that is not in "__slots__" of type "__main__.Define" + + +@attr.define(slots=False) +class DefineSlotsFalse: + b: int = attr.ib() + + def __attrs_post_init__(self) -> None: + self.b = 1 + self.c = 2 + + @attr.s(slots=True) class A: b: int = attr.ib() @@ -1544,7 +1703,49 @@ class C: def __attrs_post_init__(self) -> None: self.b = 1 # E: Trying to assign name "b" that is not in "__slots__" of type "__main__.C" self.c = 2 # E: Trying to assign name "c" that is not in "__slots__" of type "__main__.C" -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsClassWithSlotsDerivedFromNonSlots] +import attrs + +class A: + pass + +@attrs.define(slots=True) +class B(A): + x: int + + def __attrs_post_init__(self) -> None: + self.y = 42 + +[builtins fixtures/plugin_attrs.pyi] + +[case testRuntimeSlotsAttr] +from attr import dataclass + +@dataclass(slots=True) +class Some: + x: int + y: str + z: bool + +reveal_type(Some.__slots__) # N: Revealed type is "Tuple[builtins.str, builtins.str, builtins.str]" + +@dataclass(slots=True) +class Other: + x: int + y: str + +reveal_type(Other.__slots__) # N: Revealed type is "Tuple[builtins.str, builtins.str]" + + +@dataclass +class NoSlots: + x: int + y: str + +NoSlots.__slots__ # E: "Type[NoSlots]" has no attribute "__slots__" +[builtins fixtures/plugin_attrs.pyi] [case testAttrsWithMatchArgs] # flags: --python-version 3.10 @@ -1560,7 +1761,7 @@ class ToMatch: reveal_type(ToMatch(x=1, y=2, z=3).__match_args__) # N: Revealed type is "Tuple[Literal['x']?, Literal['y']?]" reveal_type(ToMatch(1, 2, z=3).__match_args__) # N: Revealed type is "Tuple[Literal['x']?, Literal['y']?]" -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsWithMatchArgsDefaultCase] # flags: --python-version 3.10 @@ -1581,7 +1782,7 @@ class ToMatch2: t2: ToMatch2 reveal_type(t2.__match_args__) # N: Revealed type is "Tuple[Literal['x']?, Literal['y']?]" -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsWithMatchArgsOverrideExisting] # flags: --python-version 3.10 @@ -1604,7 +1805,7 @@ class WithoutMatch: y: int reveal_type(WithoutMatch(x=1, y=2).__match_args__) # N: Revealed type is "Tuple[Literal['a']?, Literal['b']?]" -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsWithMatchArgsOldVersion] # flags: --python-version 3.9 @@ -1618,7 +1819,7 @@ n: NoMatchArgs reveal_type(n.__match_args__) # E: "NoMatchArgs" has no attribute "__match_args__" \ # N: Revealed type is "Any" -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsMultipleInheritance] # flags: --python-version 3.10 @@ -1634,7 +1835,7 @@ class B: class AB(A, B): pass -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsForwardReferenceInTypeVarBound] from typing import TypeVar, Generic @@ -1648,7 +1849,7 @@ class D(Generic[T]): class C: pass -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testComplexTypeInAttrIb] import a @@ -1664,8 +1865,6 @@ class C: # Note that for this test, the 'Value of type "int" is not indexable' errors are silly, # and a consequence of Callable etc. being set to an int in the test stub. b = attr.ib(type=Callable[[], C]) -[builtins fixtures/bool.pyi] - [file b.py] import attr import a @@ -1743,7 +1942,7 @@ class C: default=None, converter=default_if_none(factory=dict) \ # E: Unsupported converter, only named functions, types and lambdas are currently supported ) -[builtins fixtures/dict.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testAttrsUnannotatedConverter] import attr @@ -1814,7 +2013,7 @@ class Sub(Base): @property def name(self) -> str: ... -[builtins fixtures/property.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testOverrideWithPropertyInFrozenClassChecked] from attrs import frozen @@ -1829,11 +2028,15 @@ class Sub(Base): last_name: str @property - def name(self) -> int: ... # E: Signature of "name" incompatible with supertype "Base" + def name(self) -> int: ... # E: Signature of "name" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: str \ + # N: Subclass: \ + # N: int # This matches runtime semantics reveal_type(Sub) # N: Revealed type is "def (*, name: builtins.str, first_name: builtins.str, last_name: builtins.str) -> __main__.Sub" -[builtins fixtures/property.pyi] +[builtins fixtures/plugin_attrs.pyi] [case testFinalInstanceAttribute] from attrs import define @@ -1867,3 +2070,408 @@ D(1, "").a = 2 # E: Cannot assign to final attribute "a" D(1, "").b = "2" # E: Cannot assign to final attribute "b" [builtins fixtures/property.pyi] + +[case testEvolve] +import attr + +class Base: + pass + +class Derived(Base): + pass + +class Other: + pass + +@attr.s(auto_attribs=True) +class C: + name: str + b: Base + +c = C(name='foo', b=Derived()) +c = attr.evolve(c) +c = attr.evolve(c, name='foo') +c = attr.evolve(c, 'foo') # E: Too many positional arguments for "evolve" of "C" +c = attr.evolve(c, b=Derived()) +c = attr.evolve(c, b=Base()) +c = attr.evolve(c, b=Other()) # E: Argument "b" to "evolve" of "C" has incompatible type "Other"; expected "Base" +c = attr.evolve(c, name=42) # E: Argument "name" to "evolve" of "C" has incompatible type "int"; expected "str" +c = attr.evolve(c, foobar=42) # E: Unexpected keyword argument "foobar" for "evolve" of "C" + +# test passing instance as 'inst' kw +c = attr.evolve(inst=c, name='foo') +c = attr.evolve(not_inst=c, name='foo') # E: Missing positional argument "inst" in call to "evolve" + +# test determining type of first argument's expression from something that's not NameExpr +def f() -> C: + return c + +c = attr.evolve(f(), name='foo') + +[builtins fixtures/plugin_attrs.pyi] + +[case testEvolveFromNonAttrs] +import attr + +attr.evolve(42, name='foo') # E: Argument 1 to "evolve" has incompatible type "int"; expected an attrs class +attr.evolve(None, name='foo') # E: Argument 1 to "evolve" has incompatible type "None"; expected an attrs class +[case testEvolveFromAny] +from typing import Any +import attr + +any: Any = 42 +ret = attr.evolve(any, name='foo') +reveal_type(ret) # N: Revealed type is "Any" + +[typing fixtures/typing-medium.pyi] + +[case testEvolveGeneric] +import attrs +from typing import Generic, TypeVar + +T = TypeVar('T') + +@attrs.define +class A(Generic[T]): + x: T + + +a = A(x=42) +reveal_type(a) # N: Revealed type is "__main__.A[builtins.int]" +a2 = attrs.evolve(a, x=42) +reveal_type(a2) # N: Revealed type is "__main__.A[builtins.int]" +a2 = attrs.evolve(a, x='42') # E: Argument "x" to "evolve" of "A[int]" has incompatible type "str"; expected "int" +reveal_type(a2) # N: Revealed type is "__main__.A[builtins.int]" + +[builtins fixtures/plugin_attrs.pyi] + +[case testEvolveUnion] +# flags: --python-version 3.10 +from typing import Generic, TypeVar +import attrs + +T = TypeVar('T') + + +@attrs.define +class A(Generic[T]): + x: T # exercises meet(T=int, int) = int + y: bool # exercises meet(bool, int) = bool + z: str # exercises meet(str, bytes) = Never + w: dict # exercises meet(dict, Never) = Never + + +@attrs.define +class B: + x: int + y: bool + z: bytes + + +a_or_b: A[int] | B +a2 = attrs.evolve(a_or_b, x=42, y=True) +a2 = attrs.evolve(a_or_b, x=42, y=True, z='42') # E: Argument "z" to "evolve" of "Union[A[int], B]" has incompatible type "str"; expected Never +a2 = attrs.evolve(a_or_b, x=42, y=True, w={}) # E: Argument "w" to "evolve" of "Union[A[int], B]" has incompatible type "Dict[Never, Never]"; expected Never + +[builtins fixtures/plugin_attrs.pyi] + +[case testEvolveUnionOfTypeVar] +# flags: --python-version 3.10 +import attrs +from typing import TypeVar + +@attrs.define +class A: + x: int + y: int + z: str + w: dict + + +class B: + pass + +TA = TypeVar('TA', bound=A) +TB = TypeVar('TB', bound=B) + +def f(b_or_t: TA | TB | int) -> None: + a2 = attrs.evolve(b_or_t) # E: Argument 1 to "evolve" has type "Union[TA, TB, int]" whose item "TB" is not bound to an attrs class \ + # E: Argument 1 to "evolve" has incompatible type "Union[TA, TB, int]" whose item "int" is not an attrs class + + +[builtins fixtures/plugin_attrs.pyi] + +[case testEvolveTypeVarBound] +import attrs +from typing import TypeVar + +@attrs.define +class A: + x: int + +@attrs.define +class B(A): + pass + +TA = TypeVar('TA', bound=A) + +def f(t: TA) -> TA: + t2 = attrs.evolve(t, x=42) + reveal_type(t2) # N: Revealed type is "TA`-1" + t3 = attrs.evolve(t, x='42') # E: Argument "x" to "evolve" of "TA" has incompatible type "str"; expected "int" + return t2 + +f(A(x=42)) +f(B(x=42)) + +[builtins fixtures/plugin_attrs.pyi] + +[case testEvolveTypeVarBoundNonAttrs] +import attrs +from typing import Union, TypeVar + +TInt = TypeVar('TInt', bound=int) +TAny = TypeVar('TAny') +TNone = TypeVar('TNone', bound=None) +TUnion = TypeVar('TUnion', bound=Union[str, int]) + +def f(t: TInt) -> None: + _ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has a variable type "TInt" not bound to an attrs class + +def g(t: TAny) -> None: + _ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has a variable type "TAny" not bound to an attrs class + +def h(t: TNone) -> None: + _ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has a variable type "TNone" not bound to an attrs class + +def x(t: TUnion) -> None: + _ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has incompatible type "TUnion" whose item "str" is not an attrs class \ + # E: Argument 1 to "evolve" has incompatible type "TUnion" whose item "int" is not an attrs class + +[builtins fixtures/plugin_attrs.pyi] + +[case testEvolveTypeVarConstrained] +import attrs +from typing import TypeVar + +@attrs.define +class A: + x: int + +@attrs.define +class B: + x: str # conflicting with A.x + +T = TypeVar('T', A, B) + +def f(t: T) -> T: + t2 = attrs.evolve(t, x=42) # E: Argument "x" to "evolve" of "B" has incompatible type "int"; expected "str" + reveal_type(t2) # N: Revealed type is "__main__.A" # N: Revealed type is "__main__.B" + t2 = attrs.evolve(t, x='42') # E: Argument "x" to "evolve" of "A" has incompatible type "str"; expected "int" + return t2 + +f(A(x=42)) +f(B(x='42')) + +[builtins fixtures/plugin_attrs.pyi] + +[case testEvolveVariants] +from typing import Any +import attr +import attrs + + +@attr.s(auto_attribs=True) +class C: + name: str + +c = C(name='foo') + +c = attr.assoc(c, name='test') +c = attr.assoc(c, name=42) # E: Argument "name" to "assoc" of "C" has incompatible type "int"; expected "str" + +c = attrs.evolve(c, name='test') +c = attrs.evolve(c, name=42) # E: Argument "name" to "evolve" of "C" has incompatible type "int"; expected "str" + +c = attrs.assoc(c, name='test') +c = attrs.assoc(c, name=42) # E: Argument "name" to "assoc" of "C" has incompatible type "int"; expected "str" + +[builtins fixtures/plugin_attrs.pyi] +[typing fixtures/typing-medium.pyi] + +[case testFrozenInheritFromGeneric] +from typing import Generic, TypeVar +from attrs import field, frozen + +T = TypeVar('T') + +def f(s: str) -> int: + ... + +@frozen +class A(Generic[T]): + x: T + y: int = field(converter=f) + +@frozen +class B(A[int]): + pass + +b = B(42, 'spam') +reveal_type(b.x) # N: Revealed type is "builtins.int" +reveal_type(b.y) # N: Revealed type is "builtins.int" + +[builtins fixtures/plugin_attrs.pyi] + +[case testDefaultHashability] +from attrs import define + +@define +class A: + a: int + +reveal_type(A.__hash__) # N: Revealed type is "None" + +[builtins fixtures/plugin_attrs.pyi] + +[case testFrozenHashability] +from attrs import frozen + +@frozen +class A: + a: int + +reveal_type(A.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int" + +[builtins fixtures/plugin_attrs.pyi] + +[case testManualHashHashability] +from attrs import define + +@define(hash=True) +class A: + a: int + +reveal_type(A.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int" + +[builtins fixtures/plugin_attrs.pyi] + +[case testManualUnsafeHashHashability] +from attrs import define + +@define(unsafe_hash=True) +class A: + a: int + +reveal_type(A.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int" + +[builtins fixtures/plugin_attrs.pyi] + +[case testSubclassingHashability] +from attrs import define + +@define(unsafe_hash=True) +class A: + a: int + +@define +class B(A): + pass + +reveal_type(B.__hash__) # N: Revealed type is "None" + +[builtins fixtures/plugin_attrs.pyi] + +[case testManualOwnHashability] +from attrs import define, frozen + +@define +class A: + a: int + def __hash__(self) -> int: + ... + +reveal_type(A.__hash__) # N: Revealed type is "def (self: __main__.A) -> builtins.int" + +[builtins fixtures/plugin_attrs.pyi] + +[case testSubclassDefaultLosesHashability] +from attrs import define, frozen + +@define +class A: + a: int + def __hash__(self) -> int: + ... + +@define +class B(A): + pass + +reveal_type(B.__hash__) # N: Revealed type is "None" + +[builtins fixtures/plugin_attrs.pyi] + +[case testSubclassEqFalseKeepsHashability] +from attrs import define, frozen + +@define +class A: + a: int + def __hash__(self) -> int: + ... + +@define(eq=False) +class B(A): + pass + +reveal_type(B.__hash__) # N: Revealed type is "def (self: __main__.A) -> builtins.int" + +[builtins fixtures/plugin_attrs.pyi] + +[case testSubclassingFrozenHashability] +from attrs import define, frozen + +@define +class A: + a: int + +@frozen +class B(A): + pass + +reveal_type(B.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int" + +[builtins fixtures/plugin_attrs.pyi] + +[case testSubclassingFrozenHashOffHashability] +from attrs import define, frozen + +@define +class A: + a: int + def __hash__(self) -> int: + ... + +@frozen(unsafe_hash=False) +class B(A): + pass + +reveal_type(B.__hash__) # N: Revealed type is "None" + +[builtins fixtures/plugin_attrs.pyi] + +[case testUnsafeHashPrecedence] +from attrs import define, frozen + +@define(unsafe_hash=True, hash=False) +class A: + pass +reveal_type(A.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int" + +@define(unsafe_hash=False, hash=True) +class B: + pass +reveal_type(B.__hash__) # N: Revealed type is "None" + +[builtins fixtures/plugin_attrs.pyi] diff --git a/test-data/unit/check-possibly-undefined.test b/test-data/unit/check-possibly-undefined.test index 29c4868e97af..ae277949c049 100644 --- a/test-data/unit/check-possibly-undefined.test +++ b/test-data/unit/check-possibly-undefined.test @@ -210,7 +210,6 @@ def f0() -> None: y = x x = 1 # No error. - [case testGlobalDeclarationAfterUsage] # flags: --enable-error-code possibly-undefined --enable-error-code used-before-def def f0() -> None: @@ -219,6 +218,7 @@ def f0() -> None: x = 1 # No error. x = 2 + [case testVarDefinedInOuterScope] # flags: --enable-error-code possibly-undefined --enable-error-code used-before-def def f0() -> None: @@ -227,6 +227,7 @@ def f0() -> None: f0() x = 1 + [case testDefinedInOuterScopeNoError] # flags: --enable-error-code possibly-undefined --enable-error-code used-before-def def foo() -> None: @@ -234,6 +235,51 @@ def foo() -> None: def bar() -> None: foo() + +[case testClassFromOuterScopeRedefined] +# flags: --enable-error-code possibly-undefined --enable-error-code used-before-def +class c: pass + +def f0() -> None: + s = c() # E: Name "c" is used before definition + class c: pass + +def f1() -> None: + s = c() # No error. + +def f2() -> None: + s = c() # E: Name "c" is used before definition + if int(): + class c: pass + +glob = c() +def f3(x: c = glob) -> None: + glob = 123 + +[case testVarFromOuterScopeRedefined] +# flags: --enable-error-code possibly-undefined --enable-error-code used-before-def +x = 0 + +def f0() -> None: + y = x # E: Name "x" is used before definition + x = 0 + +def f1() -> None: + y = x # No error. + +def f2() -> None: + y = x # E: Name "x" is used before definition + global x + +def f3() -> None: + global x + y = x # No error. + +def f4() -> None: + if int(): + x = 0 + y = x # E: Name "x" may be undefined + [case testFuncParams] # flags: --enable-error-code possibly-undefined def foo(a: int) -> None: @@ -829,67 +875,56 @@ def f4() -> None: x = z # E: Name "z" is used before definition z: int = 2 -[case testUsedBeforeDefImportsBasic] -# flags: --enable-error-code used-before-def +[case testUsedBeforeDefImportsBasicImportNoError] +# flags: --enable-error-code used-before-def --enable-error-code possibly-undefined --disable-error-code no-redef import foo # type: ignore -import x.y # type: ignore -def f0() -> None: - a = foo # No error. - foo: int = 1 +a = foo # No error. +foo: int = 1 -def f1() -> None: - a = y # E: Name "y" is used before definition - y: int = 1 +[case testUsedBeforeDefImportsDotImport] +# flags: --enable-error-code used-before-def --enable-error-code possibly-undefined --disable-error-code no-redef +import x.y # type: ignore -def f2() -> None: - a = x # No error. - x: int = 1 +a = y # E: Name "y" is used before definition +y: int = 1 -def f3() -> None: - a = x.y # No error. - x: int = 1 +b = x # No error. +x: int = 1 + +c = x.y # No error. +x: int = 1 [case testUsedBeforeDefImportBasicRename] -# flags: --enable-error-code used-before-def +# flags: --enable-error-code used-before-def --disable-error-code=no-redef import x.y as z # type: ignore from typing import Any -def f0() -> None: - a = z # No error. - z: int = 1 +a = z # No error. +z: int = 1 -def f1() -> None: - a = x # E: Name "x" is used before definition - x: int = 1 +a = x # E: Name "x" is used before definition +x: int = 1 -def f2() -> None: - a = x.y # E: Name "x" is used before definition - x: Any = 1 - -def f3() -> None: - a = y # E: Name "y" is used before definition - y: int = 1 +a = y # E: Name "y" is used before definition +y: int = 1 [case testUsedBeforeDefImportFrom] -# flags: --enable-error-code used-before-def +# flags: --enable-error-code used-before-def --disable-error-code no-redef from foo import x # type: ignore -def f0() -> None: - a = x # No error. - x: int = 1 +a = x # No error. +x: int = 1 [case testUsedBeforeDefImportFromRename] -# flags: --enable-error-code used-before-def +# flags: --enable-error-code used-before-def --disable-error-code no-redef from foo import x as y # type: ignore -def f0() -> None: - a = y # No error. - y: int = 1 +a = y # No error. +y: int = 1 -def f1() -> None: - a = x # E: Name "x" is used before definition - x: int = 1 +a = x # E: Name "x" is used before definition +x: int = 1 [case testUsedBeforeDefFunctionDeclarations] # flags: --enable-error-code used-before-def @@ -901,14 +936,37 @@ def f0() -> None: inner() # No error. inner = lambda: None -[case testUsedBeforeDefBuiltins] +[case testUsedBeforeDefBuiltinsFunc] # flags: --enable-error-code used-before-def def f0() -> None: - s = type(123) + s = type(123) # E: Name "type" is used before definition type = "abc" a = type +def f1() -> None: + s = type(123) + +[case testUsedBeforeDefBuiltinsGlobal] +# flags: --enable-error-code used-before-def + +s = type(123) +type = "abc" +a = type + +[case testUsedBeforeDefBuiltinsClass] +# flags: --enable-error-code used-before-def + +class C: + s = type + type = s + +[case testUsedBeforeDefBuiltinsGenerator] +# flags: --enable-error-code used-before-def + +def f0() -> None: + _ = [type for type in [type("a"), type(1)]] + [case testUsedBeforeDefBuiltinsMultipass] # flags: --enable-error-code used-before-def @@ -968,3 +1026,20 @@ class B: else: # Same as above but in a loop. b = a # E: Name "a" may be undefined + +[case testUnreachableCausingMissingTypeMap] +# flags: --enable-error-code possibly-undefined --enable-error-code used-before-def --no-warn-unreachable +# Regression test for https://github.com/python/mypy/issues/15958 +from typing import Union, NoReturn + +def assert_never(__x: NoReturn) -> NoReturn: ... + +def foo(x: Union[int, str]) -> None: + if isinstance(x, str): + f = "foo" + elif isinstance(x, int): + f = "bar" + else: + assert_never(x) + f # OK +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index c787b34bf26b..e73add454a67 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -319,10 +319,11 @@ class MyHashable(Protocol): class C: __my_hash__ = None -var: MyHashable = C() # E: Incompatible types in assignment (expression has type "C", variable has type "MyHashable") +var: MyHashable = C() # E: Incompatible types in assignment (expression has type "C", variable has type "MyHashable") \ + # N: Following member(s) of "C" have conflicts: \ + # N: __my_hash__: expected "Callable[[], int]", got "None" [case testNoneDisablesProtocolSubclassingWithStrictOptional] -# flags: --strict-optional from typing import Protocol class MyHashable(Protocol): @@ -334,7 +335,6 @@ class C(MyHashable): (expression has type "None", base class "MyHashable" defined the type as "Callable[[MyHashable], int]") [case testProtocolsWithNoneAndStrictOptional] -# flags: --strict-optional from typing import Protocol class P(Protocol): x = 0 # type: int @@ -346,12 +346,12 @@ x: P = C() # Error! def f(x: P) -> None: pass f(C()) # Error! [out] -main:9: error: Incompatible types in assignment (expression has type "C", variable has type "P") -main:9: note: Following member(s) of "C" have conflicts: -main:9: note: x: expected "int", got "None" -main:11: error: Argument 1 to "f" has incompatible type "C"; expected "P" -main:11: note: Following member(s) of "C" have conflicts: -main:11: note: x: expected "int", got "None" +main:8: error: Incompatible types in assignment (expression has type "C", variable has type "P") +main:8: note: Following member(s) of "C" have conflicts: +main:8: note: x: expected "int", got "None" +main:10: error: Argument 1 to "f" has incompatible type "C"; expected "P" +main:10: note: Following member(s) of "C" have conflicts: +main:10: note: x: expected "int", got "None" -- Semanal errors in protocol types -- -------------------------------- @@ -928,7 +928,7 @@ class L: def last(seq: Linked[T]) -> T: pass -last(L()) # E: Argument 1 to "last" has incompatible type "L"; expected "Linked[]" +last(L()) # E: Argument 1 to "last" has incompatible type "L"; expected "Linked[Never]" [case testMutuallyRecursiveProtocols] from typing import Protocol, Sequence, List @@ -1263,13 +1263,13 @@ if int(): [builtins fixtures/classmethod.pyi] [case testOverloadedMethodsInProtocols] -from typing import overload, Protocol, Union +from typing import overload, Protocol, Union, Optional class P(Protocol): @overload - def f(self, x: int) -> int: pass + def f(self, x: int) -> Optional[int]: pass @overload - def f(self, x: str) -> str: pass + def f(self, x: str) -> Optional[str]: pass class C: def f(self, x: Union[int, str]) -> None: @@ -1286,9 +1286,9 @@ main:18: error: Incompatible types in assignment (expression has type "D", varia main:18: note: Following member(s) of "D" have conflicts: main:18: note: Expected: main:18: note: @overload -main:18: note: def f(self, x: int) -> int +main:18: note: def f(self, x: int) -> Optional[int] main:18: note: @overload -main:18: note: def f(self, x: str) -> str +main:18: note: def f(self, x: str) -> Optional[str] main:18: note: Got: main:18: note: def f(self, x: int) -> None @@ -1441,6 +1441,7 @@ def g(x: P, y: P2) -> None: pass reveal_type(f(g)) # N: Revealed type is "__main__.P2" [case testMeetOfIncompatibleProtocols] +# flags: --no-strict-optional from typing import Protocol, Callable, TypeVar class P(Protocol): @@ -1634,6 +1635,7 @@ f(Alias) # E: Only concrete class can be given where "Type[P]" is expected f(GoodAlias) [case testInstantiationProtocolInTypeForVariables] +# flags: --no-strict-optional from typing import Type, Protocol class P(Protocol): @@ -2280,7 +2282,6 @@ def func2(arg: Optional[A]) -> None: ... x: B func1(x) func2(x) -[builtins fixtures/tuple.pyi] [builtins fixtures/dict.pyi] [out] main:14: error: Argument 1 to "func1" has incompatible type "B"; expected "A" @@ -2398,6 +2399,7 @@ x: P = None [out] [case testNoneSubtypeOfAllProtocolsWithoutStrictOptional] +# flags: --no-strict-optional from typing import Protocol class P(Protocol): attr: int @@ -2408,7 +2410,6 @@ x: P = None [out] [case testNoneSubtypeOfEmptyProtocolStrict] -# flags: --strict-optional from typing import Protocol class P(Protocol): pass @@ -2790,6 +2791,70 @@ class A(Protocol): [builtins fixtures/tuple.pyi] +[case testProtocolSlotsIsNotProtocolMember] +# https://github.com/python/mypy/issues/11884 +from typing import Protocol + +class Foo(Protocol): + __slots__ = () +class NoSlots: + pass +class EmptySlots: + __slots__ = () +class TupleSlots: + __slots__ = ('x', 'y') +class StringSlots: + __slots__ = 'x y' +class InitSlots: + __slots__ = ('x',) + def __init__(self) -> None: + self.x = None +def foo(f: Foo): + pass + +# All should pass: +foo(NoSlots()) +foo(EmptySlots()) +foo(TupleSlots()) +foo(StringSlots()) +foo(InitSlots()) +[builtins fixtures/tuple.pyi] + +[case testProtocolSlotsAndRuntimeCheckable] +from typing import Protocol, runtime_checkable + +@runtime_checkable +class Foo(Protocol): + __slots__ = () +class Bar: + pass +issubclass(Bar, Foo) # Used to be an error, when `__slots__` counted as a protocol member +[builtins fixtures/isinstance.pyi] +[typing fixtures/typing-full.pyi] + + +[case testProtocolWithClassGetItem] +# https://github.com/python/mypy/issues/11886 +from typing import Any, Iterable, Protocol, Union + +class B: + ... + +class C: + def __class_getitem__(cls, __item: Any) -> Any: + ... + +class SupportsClassGetItem(Protocol): + __slots__: Union[str, Iterable[str]] = () + def __class_getitem__(cls, __item: Any) -> Any: + ... + +b1: SupportsClassGetItem = B() +c1: SupportsClassGetItem = C() +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] + + [case testNoneVsProtocol] # mypy: strict-optional from typing_extensions import Protocol @@ -2891,7 +2956,6 @@ class MyClass: [case testPartialAttributeNoneTypeStrictOptional] -# flags: --strict-optional from typing import Optional, Protocol, runtime_checkable @runtime_checkable @@ -3012,7 +3076,6 @@ def round(number: SupportsRound[_T], ndigits: int) -> _T: ... round(C(), 1) [case testEmptyBodyImplicitlyAbstractProtocol] -# flags: --strict-optional from typing import Protocol, overload, Union class P1(Protocol): @@ -3059,7 +3122,6 @@ C3() [builtins fixtures/classmethod.pyi] [case testEmptyBodyImplicitlyAbstractProtocolProperty] -# flags: --strict-optional from typing import Protocol class P1(Protocol): @@ -3154,7 +3216,6 @@ D() # E: Cannot instantiate abstract class "D" with abstract attribute "meth" [builtins fixtures/exception.pyi] [case testEmptyBodyNoneCompatibleProtocol] -# flags: --strict-optional from abc import abstractmethod from typing import Any, Optional, Protocol, Union, overload from typing_extensions import TypeAlias @@ -3191,7 +3252,6 @@ class NoneCompatible3(Protocol): class C(NoneCompatible3): ... C() # E: Cannot instantiate abstract class "C" with abstract attributes "f", "g" and "h" -[builtins fixtures/tuple.pyi] [builtins fixtures/classmethod.pyi] [case testEmptyBodyWithFinal] @@ -3524,7 +3584,12 @@ class C: def test(arg: P) -> None: ... test(B) # OK test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ - # N: "C" has constructor incompatible with "__call__" of "P" + # N: "C" has constructor incompatible with "__call__" of "P" \ + # N: Following member(s) of "C" have conflicts: \ + # N: Expected: \ + # N: def __call__(x: int, y: int) -> Any \ + # N: Got: \ + # N: def __init__(x: int, y: str) -> C [case testProtocolClassObjectPureCallback] from typing import Any, ClassVar, Protocol @@ -3540,7 +3605,36 @@ class C: def test(arg: P) -> None: ... test(B) # OK test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ - # N: "C" has constructor incompatible with "__call__" of "P" + # N: "C" has constructor incompatible with "__call__" of "P" \ + # N: Following member(s) of "C" have conflicts: \ + # N: Expected: \ + # N: def __call__(x: int, y: int) -> Any \ + # N: Got: \ + # N: def __init__(x: int, y: str) -> C +[builtins fixtures/type.pyi] + +[case testProtocolClassObjectCallableError] +from typing import Protocol, Any, Callable + +class P(Protocol): + def __call__(self, app: int) -> Callable[[str], None]: + ... + +class C: + def __init__(self, app: str) -> None: + pass + + def __call__(self, el: str) -> None: + return None + +p: P = C # E: Incompatible types in assignment (expression has type "Type[C]", variable has type "P") \ + # N: Following member(s) of "C" have conflicts: \ + # N: Expected: \ + # N: def __call__(app: int) -> Callable[[str], None] \ + # N: Got: \ + # N: def __init__(app: str) -> C \ + # N: "P.__call__" has type "Callable[[Arg(int, 'app')], Callable[[str], None]]" + [builtins fixtures/type.pyi] [case testProtocolTypeTypeAttribute] @@ -3998,3 +4092,38 @@ TF = TypeVar("TF", bound=Foo) def outer(cls: Type[TF]) -> TF: reveal_type(test(cls)) # N: Revealed type is "TF`-1" return cls() + +[case testProtocolImportNotMember] +import m +import lib + +class Bad: + x: int +class Good: + x: lib.C + +x: m.P = Bad() # E: Incompatible types in assignment (expression has type "Bad", variable has type "P") \ + # N: Following member(s) of "Bad" have conflicts: \ + # N: x: expected "C", got "int" +x = Good() + +[file m.py] +from typing import Protocol + +class P(Protocol): + import lib + x: lib.C + +[file lib.py] +class C: ... + +[case testAllowDefaultConstructorInProtocols] +from typing import Protocol + +class P(Protocol): + x: int + def __init__(self, x: int) -> None: + self.x = x + +class C(P): ... +C(0) # OK diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 7a934348aaf2..2b56d2db07a9 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -341,6 +341,72 @@ match m: reveal_type(m) # N: Revealed type is "builtins.list[builtins.list[builtins.str]]" [builtins fixtures/list.pyi] +[case testMatchSequencePatternNarrowSubjectItems] +m: int +n: str +o: bool + +match m, n, o: + case [3, "foo", True]: + reveal_type(m) # N: Revealed type is "Literal[3]" + reveal_type(n) # N: Revealed type is "Literal['foo']" + reveal_type(o) # N: Revealed type is "Literal[True]" + case [a, b, c]: + reveal_type(m) # N: Revealed type is "builtins.int" + reveal_type(n) # N: Revealed type is "builtins.str" + reveal_type(o) # N: Revealed type is "builtins.bool" + +reveal_type(m) # N: Revealed type is "builtins.int" +reveal_type(n) # N: Revealed type is "builtins.str" +reveal_type(o) # N: Revealed type is "builtins.bool" +[builtins fixtures/tuple.pyi] + +[case testMatchSequencePatternNarrowSubjectItemsRecursive] +m: int +n: int +o: int +p: int +q: int +r: int + +match m, (n, o), (p, (q, r)): + case [0, [1, 2], [3, [4, 5]]]: + reveal_type(m) # N: Revealed type is "Literal[0]" + reveal_type(n) # N: Revealed type is "Literal[1]" + reveal_type(o) # N: Revealed type is "Literal[2]" + reveal_type(p) # N: Revealed type is "Literal[3]" + reveal_type(q) # N: Revealed type is "Literal[4]" + reveal_type(r) # N: Revealed type is "Literal[5]" +[builtins fixtures/tuple.pyi] + +[case testMatchSequencePatternSequencesLengthMismatchNoNarrowing] +m: int +n: str +o: bool + +match m, n, o: + case [3, "foo"]: + pass + case [3, "foo", True, True]: + pass +[builtins fixtures/tuple.pyi] + +[case testMatchSequencePatternSequencesLengthMismatchNoNarrowingRecursive] +m: int +n: int +o: int + +match m, (n, o): + case [0]: + pass + case [0, 1, [2]]: + pass + case [0, [1]]: + pass + case [0, [1, 2, 3]]: + pass +[builtins fixtures/tuple.pyi] + -- Mapping Pattern -- [case testMatchMappingPatternCaptures] @@ -700,6 +766,21 @@ match m: reveal_type(m) # N: Revealed type is "__main__.A[Any]" reveal_type(i) # N: Revealed type is "Any" +[case testMatchClassPatternCaptureVariadicGeneric] +from typing import Generic, Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple('Ts') +class A(Generic[Unpack[Ts]]): + a: Tuple[Unpack[Ts]] + +m: object +match m: + case A(a=i): + reveal_type(m) # N: Revealed type is "__main__.A[Unpack[builtins.tuple[Any, ...]]]" + reveal_type(i) # N: Revealed type is "builtins.tuple[Any, ...]" +[builtins fixtures/tuple.pyi] + [case testMatchClassPatternCaptureGenericAlreadyKnown] from typing import Generic, TypeVar @@ -896,6 +977,16 @@ match m: reveal_type(i) reveal_type(j) +[case testMatchClassPatternAny] +from typing import Any + +Foo: Any +m: object + +match m: + case Foo(): + pass + [case testMatchClassPatternNestedGenerics] # From cpython test_patma.py x = [[{0: 0}]] @@ -906,7 +997,7 @@ match x: reveal_type(x) # N: Revealed type is "builtins.list[builtins.list[builtins.dict[builtins.int, builtins.int]]]" reveal_type(y) # N: Revealed type is "builtins.int" reveal_type(z) # N: Revealed type is "builtins.int" -[builtins fixtures/dict.pyi] +[builtins fixtures/dict-full.pyi] [case testMatchNonFinalMatchArgs] class A: @@ -1114,6 +1205,21 @@ match m: reveal_type(a) # N: Revealed type is "builtins.str" +[case testMatchCapturePatternFromFunctionReturningUnion] +def func1(arg: bool) -> str | int: ... +def func2(arg: bool) -> bytes | int: ... + +def main() -> None: + match func1(True): + case str(a): + match func2(True): + case c: + reveal_type(a) # N: Revealed type is "builtins.str" + reveal_type(c) # N: Revealed type is "Union[builtins.bytes, builtins.int]" + reveal_type(a) # N: Revealed type is "builtins.str" + case a: + reveal_type(a) # N: Revealed type is "builtins.int" + -- Guards -- [case testMatchSimplePatternGuard] @@ -1140,12 +1246,11 @@ match m: reveal_type(a) [case testMatchRedefiningPatternGuard] -# flags: --strict-optional m: str match m: case a if a := 1: # E: Incompatible types in assignment (expression has type "int", variable has type "str") - reveal_type(a) # N: Revealed type is "" + reveal_type(a) # N: Revealed type is "Never" [case testMatchAssigningPatternGuard] m: str @@ -1330,6 +1435,27 @@ match m3: reveal_type(m3) # N: Revealed type is "Tuple[Union[builtins.int, builtins.str]]" [builtins fixtures/tuple.pyi] +[case testMatchEnumSingleChoice] +from enum import Enum +from typing import NoReturn + +def assert_never(x: NoReturn) -> None: ... + +class Medal(Enum): + gold = 1 + +def f(m: Medal) -> None: + always_assigned: int | None = None + match m: + case Medal.gold: + always_assigned = 1 + reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.gold]" + case _: + assert_never(m) + + reveal_type(always_assigned) # N: Revealed type is "builtins.int" +[builtins fixtures/bool.pyi] + [case testMatchLiteralPatternEnumNegativeNarrowing] from enum import Enum class Medal(Enum): @@ -1349,10 +1475,13 @@ def f(m: Medal) -> int: def g(m: Medal) -> int: match m: case Medal.gold: + reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.gold]" return 0 case Medal.silver: + reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.silver]" return 1 case Medal.bronze: + reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.bronze]" return 2 [case testMatchLiteralPatternEnumCustomEquals-skip] @@ -1373,7 +1502,7 @@ match m: reveal_type(m) # N: Revealed type is "__main__.Medal" [case testMatchNarrowUsingPatternGuardSpecialCase] -def f(x: int | str) -> int: # E: Missing return statement +def f(x: int | str) -> int: match x: case x if isinstance(x, str): return 0 @@ -1382,7 +1511,6 @@ def f(x: int | str) -> int: # E: Missing return statement [builtins fixtures/isinstance.pyi] [case testMatchNarrowDownUnionPartially] -# flags: --strict-optional def f(x: int | str) -> None: match x: @@ -1493,7 +1621,6 @@ def f(x: A) -> None: reveal_type(y) # N: Revealed type is "Union[__main__., __main__.]" [case testMatchWithBreakAndContinue] -# flags: --strict-optional def f(x: int | str | None) -> None: i = int() while i: @@ -1571,8 +1698,8 @@ class AnnAssign(stmt): value: str simple: int -reveal_type(AST.__match_args__) # N: Revealed type is "Tuple[]" -reveal_type(stmt.__match_args__) # N: Revealed type is "Tuple[]" +reveal_type(AST.__match_args__) # N: Revealed type is "Tuple[()]" +reveal_type(stmt.__match_args__) # N: Revealed type is "Tuple[()]" reveal_type(AnnAssign.__match_args__) # N: Revealed type is "Tuple[Literal['target']?, Literal['annotation']?, Literal['value']?, Literal['simple']?]" AnnAssign.__match_args__ = ('a', 'b', 'c', 'd') # E: Cannot assign to "__match_args__" @@ -1626,7 +1753,6 @@ def func(e: Union[str, tuple[str]]) -> None: [builtins fixtures/tuple.pyi] [case testMatchTupleOptionalNoCrash] -# flags: --strict-optional foo: tuple[int] | None match foo: case x,: @@ -1841,3 +1967,284 @@ class D: pass X = None | C Y = None | D [builtins fixtures/type.pyi] + +[case testMatchStatementWalrus] +class A: + a = 1 + +def returns_a_or_none() -> A | None: + return A() + +def returns_a() -> A: + return A() + +def f() -> None: + match x := returns_a_or_none(): + case A(): + reveal_type(x.a) # N: Revealed type is "builtins.int" + match x := returns_a(): + case A(): + reveal_type(x.a) # N: Revealed type is "builtins.int" + y = returns_a_or_none() + match y: + case A(): + reveal_type(y.a) # N: Revealed type is "builtins.int" + +[case testNarrowedVariableInNestedModifiedInMatch] +from typing import Optional + +def match_stmt_error1(x: Optional[str]) -> None: + if x is None: + x = "a" + def nested() -> str: + return x # E: Incompatible return value type (got "Optional[str]", expected "str") + match object(): + case str(x): + pass + nested() + +def foo(x): pass + +def match_stmt_ok1(x: Optional[str]) -> None: + if x is None: + x = "a" + def nested() -> str: + return x + match foo(x): + case str(y): + z = x + nested() + +def match_stmt_error2(x: Optional[str]) -> None: + if x is None: + x = "a" + def nested() -> str: + return x # E: Incompatible return value type (got "Optional[str]", expected "str") + match [None]: + case [x]: + pass + nested() + +def match_stmt_error3(x: Optional[str]) -> None: + if x is None: + x = "a" + def nested() -> str: + return x # E: Incompatible return value type (got "Optional[str]", expected "str") + match {'a': None}: + case {'a': x}: + pass + nested() + +def match_stmt_error4(x: Optional[list[str]]) -> None: + if x is None: + x = ["a"] + def nested() -> list[str]: + return x # E: Incompatible return value type (got "Optional[List[str]]", expected "List[str]") + match ["a"]: + case [*x]: + pass + nested() + +class C: + a: str + +def match_stmt_error5(x: Optional[str]) -> None: + if x is None: + x = "a" + def nested() -> str: + return x # E: Incompatible return value type (got "Optional[str]", expected "str") + match C(): + case C(a=x): + pass + nested() +[builtins fixtures/tuple.pyi] + +[case testMatchSubjectRedefinition] +# flags: --allow-redefinition +def transform1(a: str) -> int: + ... + +def transform2(a: int) -> str: + ... + +def redefinition_good(a: str): + a = transform1(a) + + match (a + 1): + case _: + ... + + +def redefinition_bad(a: int): + a = transform2(a) + + match (a + 1): # E: Unsupported operand types for + ("str" and "int") + case _: + ... + +[builtins fixtures/primitives.pyi] + +[case testPatternMatchingClassPatternLocation] +# See https://github.com/python/mypy/issues/15496 +from some_missing_lib import DataFrame, Series # type: ignore[import] +from typing import TypeVar + +T = TypeVar("T", Series, DataFrame) + +def f(x: T) -> None: + match x: + case Series() | DataFrame(): # type: ignore[misc] + pass + +def f2(x: T) -> None: + match x: + case Series(): # type: ignore[misc] + pass + case DataFrame(): # type: ignore[misc] + pass +[builtins fixtures/primitives.pyi] + +[case testMatchGuardReachability] +# flags: --warn-unreachable +def f1(e: int) -> int: + match e: + case x if True: + return x + case _: + return 0 # E: Statement is unreachable + e = 0 # E: Statement is unreachable + + +def f2(e: int) -> int: + match e: + case x if bool(): + return x + case _: + return 0 + e = 0 # E: Statement is unreachable + +def f3(e: int | str | bytes) -> int: + match e: + case x if isinstance(x, int): + return x + case [x]: + return 0 # E: Statement is unreachable + case str(x): + return 0 + reveal_type(e) # N: Revealed type is "builtins.bytes" + return 0 + +def f4(e: int | str | bytes) -> int: + match e: + case int(x): + pass + case [x]: + return 0 # E: Statement is unreachable + case x if isinstance(x, str): + return 0 + reveal_type(e) # N: Revealed type is "Union[builtins.int, builtins.bytes]" + return 0 + +[builtins fixtures/primitives.pyi] + +[case testMatchSequencePatternVariadicTupleNotTooShort] +from typing import Tuple +from typing_extensions import Unpack + +fm1: Tuple[int, int, Unpack[Tuple[str, ...]], int] +match fm1: + case [fa1, fb1, fc1]: + reveal_type(fa1) # N: Revealed type is "builtins.int" + reveal_type(fb1) # N: Revealed type is "builtins.int" + reveal_type(fc1) # N: Revealed type is "builtins.int" + +fm2: Tuple[int, int, Unpack[Tuple[str, ...]], int] +match fm2: + case [fa2, fb2]: + reveal_type(fa2) + reveal_type(fb2) + +fm3: Tuple[int, int, Unpack[Tuple[str, ...]], int] +match fm3: + case [fa3, fb3, fc3, fd3, fe3]: + reveal_type(fa3) # N: Revealed type is "builtins.int" + reveal_type(fb3) # N: Revealed type is "builtins.int" + reveal_type(fc3) # N: Revealed type is "builtins.str" + reveal_type(fd3) # N: Revealed type is "builtins.str" + reveal_type(fe3) # N: Revealed type is "builtins.int" + +m1: Tuple[int, Unpack[Tuple[str, ...]], int] +match m1: + case [a1, *b1, c1]: + reveal_type(a1) # N: Revealed type is "builtins.int" + reveal_type(b1) # N: Revealed type is "builtins.list[builtins.str]" + reveal_type(c1) # N: Revealed type is "builtins.int" + +m2: Tuple[int, Unpack[Tuple[str, ...]], int] +match m2: + case [a2, b2, *c2, d2, e2]: + reveal_type(a2) # N: Revealed type is "builtins.int" + reveal_type(b2) # N: Revealed type is "builtins.str" + reveal_type(c2) # N: Revealed type is "builtins.list[builtins.str]" + reveal_type(d2) # N: Revealed type is "builtins.str" + reveal_type(e2) # N: Revealed type is "builtins.int" + +m3: Tuple[int, int, Unpack[Tuple[str, ...]], int, int] +match m3: + case [a3, *b3, c3]: + reveal_type(a3) # N: Revealed type is "builtins.int" + reveal_type(b3) # N: Revealed type is "builtins.list[Union[builtins.int, builtins.str]]" + reveal_type(c3) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] + +[case testMatchSequencePatternTypeVarTupleNotTooShort] +from typing import Tuple +from typing_extensions import Unpack, TypeVarTuple + +Ts = TypeVarTuple("Ts") +def test(xs: Tuple[Unpack[Ts]]) -> None: + fm1: Tuple[int, int, Unpack[Ts], int] + match fm1: + case [fa1, fb1, fc1]: + reveal_type(fa1) # N: Revealed type is "builtins.int" + reveal_type(fb1) # N: Revealed type is "builtins.int" + reveal_type(fc1) # N: Revealed type is "builtins.int" + + fm2: Tuple[int, int, Unpack[Ts], int] + match fm2: + case [fa2, fb2]: + reveal_type(fa2) + reveal_type(fb2) + + fm3: Tuple[int, int, Unpack[Ts], int] + match fm3: + case [fa3, fb3, fc3, fd3, fe3]: + reveal_type(fa3) # N: Revealed type is "builtins.int" + reveal_type(fb3) # N: Revealed type is "builtins.int" + reveal_type(fc3) # N: Revealed type is "builtins.object" + reveal_type(fd3) # N: Revealed type is "builtins.object" + reveal_type(fe3) # N: Revealed type is "builtins.int" + + m1: Tuple[int, Unpack[Ts], int] + match m1: + case [a1, *b1, c1]: + reveal_type(a1) # N: Revealed type is "builtins.int" + reveal_type(b1) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(c1) # N: Revealed type is "builtins.int" + + m2: Tuple[int, Unpack[Ts], int] + match m2: + case [a2, b2, *c2, d2, e2]: + reveal_type(a2) # N: Revealed type is "builtins.int" + reveal_type(b2) # N: Revealed type is "builtins.object" + reveal_type(c2) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(d2) # N: Revealed type is "builtins.object" + reveal_type(e2) # N: Revealed type is "builtins.int" + + m3: Tuple[int, int, Unpack[Ts], int, int] + match m3: + case [a3, *b3, c3]: + reveal_type(a3) # N: Revealed type is "builtins.int" + reveal_type(b3) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(c3) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-python311.test b/test-data/unit/check-python311.test index 7196f10f8863..2d1a09ef3336 100644 --- a/test-data/unit/check-python311.test +++ b/test-data/unit/check-python311.test @@ -34,7 +34,7 @@ except* (RuntimeError, Custom) as e: class Bad: ... try: pass -except* (RuntimeError, Bad) as e: # E: Exception type must be derived from BaseException +except* (RuntimeError, Bad) as e: # E: Exception type must be derived from BaseException (or be a tuple of exception classes) reveal_type(e) # N: Revealed type is "builtins.ExceptionGroup[Any]" [builtins fixtures/exception.pyi] @@ -63,3 +63,113 @@ class Variadic(Generic[Unpack[Ts]]): variadic: Variadic[int, str] reveal_type(variadic) # N: Revealed type is "__main__.Variadic[builtins.int, builtins.str]" [builtins fixtures/tuple.pyi] + +[case testAsyncGeneratorWithinComprehension] +# flags: --python-version 3.11 +from typing import Any, Generator, List + +async def asynciter(iterable): + for x in iterable: + yield x + +async def coro() -> Generator[List[Any], None, None]: + return ([i async for i in asynciter([0,j])] for j in [3, 5]) +reveal_type(coro) # N: Revealed type is "def () -> typing.Coroutine[Any, Any, typing.Generator[builtins.list[Any], None, None]]" +[builtins fixtures/async_await.pyi] +[typing fixtures/typing-async.pyi] + +[case testTypeVarTupleNewSyntaxAnnotations] +Ints = tuple[int, int, int] +x: tuple[str, *Ints] +reveal_type(x) # N: Revealed type is "Tuple[builtins.str, builtins.int, builtins.int, builtins.int]" +y: tuple[int, *tuple[int, ...]] +reveal_type(y) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.int, ...]]]" +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleNewSyntaxGenerics] +from typing import Generic, TypeVar, TypeVarTuple + +T = TypeVar("T") +Ts = TypeVarTuple("Ts") +class C(Generic[T, *Ts]): + attr: tuple[int, *Ts, str] + + def test(self) -> None: + reveal_type(self.attr) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`2], builtins.str]" + self.attr = ci # E: Incompatible types in assignment (expression has type "C[*Tuple[int, ...]]", variable has type "Tuple[int, *Ts, str]") + def meth(self, *args: *Ts) -> T: ... + +ci: C[*tuple[int, ...]] +reveal_type(ci) # N: Revealed type is "__main__.C[Unpack[builtins.tuple[builtins.int, ...]]]" +reveal_type(ci.meth) # N: Revealed type is "def (*args: builtins.int) -> builtins.int" +c3: C[str, str, str] +reveal_type(c3) # N: Revealed type is "__main__.C[builtins.str, builtins.str, builtins.str]" + +A = C[int, *Ts] +B = tuple[str, *tuple[str, str], str] +z: A[*B] +reveal_type(z) # N: Revealed type is "__main__.C[builtins.int, builtins.str, builtins.str, builtins.str, builtins.str]" +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleNewSyntaxCallables] +from typing import Generic, overload, TypeVar + +T1 = TypeVar("T1") +T2 = TypeVar("T2") +class MyClass(Generic[T1, T2]): + @overload + def __init__(self: MyClass[None, None]) -> None: ... + + @overload + def __init__(self: MyClass[T1, None], *types: *tuple[type[T1]]) -> None: ... + + @overload + def __init__(self: MyClass[T1, T2], *types: *tuple[type[T1], type[T2]]) -> None: ... + + def __init__(self: MyClass[T1, T2], *types: *tuple[type, ...]) -> None: + pass + +myclass = MyClass() +reveal_type(myclass) # N: Revealed type is "__main__.MyClass[None, None]" +myclass1 = MyClass(float) +reveal_type(myclass1) # N: Revealed type is "__main__.MyClass[builtins.float, None]" +myclass2 = MyClass(float, float) +reveal_type(myclass2) # N: Revealed type is "__main__.MyClass[builtins.float, builtins.float]" +myclass3 = MyClass(float, float, float) # E: No overload variant of "MyClass" matches argument types "Type[float]", "Type[float]", "Type[float]" \ + # N: Possible overload variants: \ + # N: def [T1, T2] __init__(self) -> MyClass[None, None] \ + # N: def [T1, T2] __init__(self, Type[T1], /) -> MyClass[T1, None] \ + # N: def [T1, T2] __init__(Type[T1], Type[T2], /) -> MyClass[T1, T2] +reveal_type(myclass3) # N: Revealed type is "Any" +[builtins fixtures/tuple.pyi] + +[case testUnpackNewSyntaxInvalidCallableAlias] +from typing import Any, Callable, List, Tuple, TypeVar, Unpack + +T = TypeVar("T") +Ts = TypeVarTuple("Ts") # E: Name "TypeVarTuple" is not defined + +def good(*x: int) -> int: ... +def bad(*x: int, y: int) -> int: ... + +Alias1 = Callable[[*Ts], int] # E: Variable "__main__.Ts" is not valid as a type \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +x1: Alias1[int] # E: Bad number of arguments for type alias, expected 0, given 1 +reveal_type(x1) # N: Revealed type is "def (*Any) -> builtins.int" +x1 = good +x1 = bad # E: Incompatible types in assignment (expression has type "Callable[[VarArg(int), NamedArg(int, 'y')], int]", variable has type "Callable[[VarArg(Any)], int]") + +Alias2 = Callable[[*T], int] # E: "T" cannot be unpacked (must be tuple or TypeVarTuple) +x2: Alias2[int] +reveal_type(x2) # N: Revealed type is "def (*Any) -> builtins.int" + +Unknown = Any +Alias3 = Callable[[*Unknown], int] +x3: Alias3[int] # E: Bad number of arguments for type alias, expected 0, given 1 +reveal_type(x3) # N: Revealed type is "def (*Any) -> builtins.int" + +IntList = List[int] +Alias4 = Callable[[*IntList], int] # E: "List[int]" cannot be unpacked (must be tuple or TypeVarTuple) +x4: Alias4[int] # E: Bad number of arguments for type alias, expected 0, given 1 +reveal_type(x4) # N: Revealed type is "def (*Unpack[builtins.tuple[Any, ...]]) -> builtins.int" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test new file mode 100644 index 000000000000..cce22634df6d --- /dev/null +++ b/test-data/unit/check-python312.test @@ -0,0 +1,1163 @@ +[case test695TypeAlias] +type MyInt = int # E: PEP 695 type aliases are not yet supported + +def f(x: MyInt) -> MyInt: + return reveal_type(x) # N: Revealed type is "builtins.int" + +type MyList[T] = list[T] # E: PEP 695 type aliases are not yet supported \ + # E: Name "T" is not defined + +def g(x: MyList[int]) -> MyList[int]: # E: Variable "__main__.MyList" is not valid as a type \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases + return reveal_type(x) # N: Revealed type is "MyList?[builtins.int]" + +type MyInt2 = int # type: ignore[valid-type] + +def h(x: MyInt2) -> MyInt2: + return reveal_type(x) # N: Revealed type is "builtins.int" + +[case test695Class] +class MyGen[T]: # E: PEP 695 generics are not yet supported + def __init__(self, x: T) -> None: # E: Name "T" is not defined + self.x = x + +def f(x: MyGen[int]): # E: "MyGen" expects no type arguments, but 1 given + reveal_type(x.x) # N: Revealed type is "Any" + +[case test695Function] +def f[T](x: T) -> T: # E: PEP 695 generics are not yet supported \ + # E: Name "T" is not defined + return reveal_type(x) # N: Revealed type is "Any" + +reveal_type(f(1)) # N: Revealed type is "Any" + +async def g[T](x: T) -> T: # E: PEP 695 generics are not yet supported \ + # E: Name "T" is not defined + return reveal_type(x) # N: Revealed type is "Any" + +reveal_type(g(1)) # E: Value of type "Coroutine[Any, Any, Any]" must be used \ + # N: Are you missing an await? \ + # N: Revealed type is "typing.Coroutine[Any, Any, Any]" + +[case test695TypeVar] +from typing import Callable +type Alias1[T: int] = list[T] # E: PEP 695 type aliases are not yet supported +type Alias2[**P] = Callable[P, int] # E: PEP 695 type aliases are not yet supported \ + # E: Value of type "int" is not indexable \ + # E: Name "P" is not defined +type Alias3[*Ts] = tuple[*Ts] # E: PEP 695 type aliases are not yet supported \ + # E: Name "Ts" is not defined + +class Cls1[T: int]: ... # E: PEP 695 generics are not yet supported +class Cls2[**P]: ... # E: PEP 695 generics are not yet supported +class Cls3[*Ts]: ... # E: PEP 695 generics are not yet supported + +def func1[T: int](x: T) -> T: ... # E: PEP 695 generics are not yet supported +def func2[**P](x: Callable[P, int]) -> Callable[P, str]: ... # E: PEP 695 generics are not yet supported \ + # E: The first argument to Callable must be a list of types, parameter specification, or "..." \ + # N: See https://mypy.readthedocs.io/en/stable/kinds_of_types.html#callable-types-and-lambdas \ + # E: Name "P" is not defined +def func3[*Ts](x: tuple[*Ts]) -> tuple[int, *Ts]: ... # E: PEP 695 generics are not yet supported \ + # E: Name "Ts" is not defined +[builtins fixtures/tuple.pyi] + +[case test695TypeAliasType] +from typing import Callable, TypeAliasType, TypeVar, TypeVarTuple + +T = TypeVar("T") +Ts = TypeVarTuple("Ts") + +TestType = TypeAliasType("TestType", int | str) +x: TestType = 42 +y: TestType = 'a' +z: TestType = object() # E: Incompatible types in assignment (expression has type "object", variable has type "Union[int, str]") + +BadAlias1 = TypeAliasType("BadAlias1", tuple[*Ts]) # E: TypeVarTuple "Ts" is not included in type_params +ba1: BadAlias1[int] # E: Bad number of arguments for type alias, expected 0, given 1 +reveal_type(ba1) # N: Revealed type is "builtins.tuple[Any, ...]" + +BadAlias2 = TypeAliasType("BadAlias2", Callable[[*Ts], str]) # E: TypeVarTuple "Ts" is not included in type_params +ba2: BadAlias2[int] # E: Bad number of arguments for type alias, expected 0, given 1 +reveal_type(ba2) # N: Revealed type is "def (*Any) -> builtins.str" + +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] + +[case testPEP695GenericFunctionSyntax] +# flags: --enable-incomplete-feature=NewGenericSyntax + +def ident[TV](x: TV) -> TV: + y: TV = x + y = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "TV") + return x + +reveal_type(ident(1)) # N: Revealed type is "builtins.int" +reveal_type(ident('x')) # N: Revealed type is "builtins.str" + +a: TV # E: Name "TV" is not defined + +def tup[T, S](x: T, y: S) -> tuple[T, S]: + reveal_type((x, y)) # N: Revealed type is "Tuple[T`-1, S`-2]" + return (x, y) + +reveal_type(tup(1, 'x')) # N: Revealed type is "Tuple[builtins.int, builtins.str]" +[builtins fixtures/tuple.pyi] + +[case testPEP695GenericClassSyntax] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class C[T]: + x: T + + def __init__(self, x: T) -> None: + self.x = x + + def ident(self, x: T) -> T: + y: T = x + if int(): + return self.x + else: + return y + +reveal_type(C("x")) # N: Revealed type is "__main__.C[builtins.str]" +c: C[int] = C(1) +reveal_type(c.x) # N: Revealed type is "builtins.int" +reveal_type(c.ident(1)) # N: Revealed type is "builtins.int" + +[case testPEP695GenericMethodInGenericClass] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class C[T]: + def m[S](self, x: S) -> T | S: ... + +a: C[int] = C[object]() # E: Incompatible types in assignment (expression has type "C[object]", variable has type "C[int]") +b: C[object] = C[int]() + +reveal_type(C[str]().m(1)) # N: Revealed type is "Union[builtins.str, builtins.int]" + +[case testPEP695InferVarianceSimpleFromMethod] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class Invariant[T]: + def f(self, x: T) -> None: + pass + + def g(self) -> T | None: + return None + +a: Invariant[object] +b: Invariant[int] +if int(): + a = b # E: Incompatible types in assignment (expression has type "Invariant[int]", variable has type "Invariant[object]") +if int(): + b = a # E: Incompatible types in assignment (expression has type "Invariant[object]", variable has type "Invariant[int]") + +class Covariant[T]: + def g(self) -> T | None: + return None + +c: Covariant[object] +d: Covariant[int] +if int(): + c = d +if int(): + d = c # E: Incompatible types in assignment (expression has type "Covariant[object]", variable has type "Covariant[int]") + +class Contravariant[T]: + def f(self, x: T) -> None: + pass + +e: Contravariant[object] +f: Contravariant[int] +if int(): + e = f # E: Incompatible types in assignment (expression has type "Contravariant[int]", variable has type "Contravariant[object]") +if int(): + f = e + +[case testPEP695InferVarianceSimpleFromAttribute] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class Invariant1[T]: + def __init__(self, x: T) -> None: + self.x = x + +a: Invariant1[object] +b: Invariant1[int] +if int(): + a = b # E: Incompatible types in assignment (expression has type "Invariant1[int]", variable has type "Invariant1[object]") +if int(): + b = a # E: Incompatible types in assignment (expression has type "Invariant1[object]", variable has type "Invariant1[int]") + +class Invariant2[T]: + def __init__(self) -> None: + self.x: list[T] = [] + +a2: Invariant2[object] +b2: Invariant2[int] +if int(): + a2 = b2 # E: Incompatible types in assignment (expression has type "Invariant2[int]", variable has type "Invariant2[object]") +if int(): + b2 = a2 # E: Incompatible types in assignment (expression has type "Invariant2[object]", variable has type "Invariant2[int]") + +class Invariant3[T]: + def __init__(self) -> None: + self.x: T | None = None + +a3: Invariant3[object] +b3: Invariant3[int] +if int(): + a3 = b3 # E: Incompatible types in assignment (expression has type "Invariant3[int]", variable has type "Invariant3[object]") +if int(): + b3 = a3 # E: Incompatible types in assignment (expression has type "Invariant3[object]", variable has type "Invariant3[int]") + +[case testPEP695InferVarianceRecursive] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class Invariant[T]: + def f(self, x: Invariant[T]) -> Invariant[T]: + return x + +class Covariant[T]: + def f(self) -> Covariant[T]: + return self + +class Contravariant[T]: + def f(self, x: Contravariant[T]) -> None: + pass + +a: Invariant[object] +b: Invariant[int] +if int(): + a = b # E: Incompatible types in assignment (expression has type "Invariant[int]", variable has type "Invariant[object]") +if int(): + b = a # E: Incompatible types in assignment (expression has type "Invariant[object]", variable has type "Invariant[int]") + +c: Covariant[object] +d: Covariant[int] +if int(): + c = d +if int(): + d = c # E: Incompatible types in assignment (expression has type "Covariant[object]", variable has type "Covariant[int]") + +e: Contravariant[object] +f: Contravariant[int] +if int(): + e = f # E: Incompatible types in assignment (expression has type "Contravariant[int]", variable has type "Contravariant[object]") +if int(): + f = e + +[case testPEP695InferVarianceCalculateOnDemand] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class Covariant[T]: + def __init__(self) -> None: + self.x = [1] + + def f(self) -> None: + c = Covariant[int]() + # We need to know that T is covariant here + self.g(c) + c2 = Covariant[object]() + self.h(c2) # E: Argument 1 to "h" of "Covariant" has incompatible type "Covariant[object]"; expected "Covariant[int]" + + def g(self, x: Covariant[object]) -> None: pass + def h(self, x: Covariant[int]) -> None: pass + +[case testPEP695InferVarianceNotReadyWhenNeeded] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class Covariant[T]: + def f(self) -> None: + c = Covariant[int]() + # We need to know that T is covariant here + self.g(c) + c2 = Covariant[object]() + self.h(c2) # E: Argument 1 to "h" of "Covariant" has incompatible type "Covariant[object]"; expected "Covariant[int]" + + def g(self, x: Covariant[object]) -> None: pass + def h(self, x: Covariant[int]) -> None: pass + + def __init__(self) -> None: + self.x = [1] + +class Invariant[T]: + def f(self) -> None: + c = Invariant(1) + # We need to know that T is invariant here, and for this we need the type + # of self.x, which won't be available on the first type checking pass, + # since __init__ is defined later in the file. In this case we fall back + # covariance. + self.g(c) + c2 = Invariant(object()) + self.h(c2) # E: Argument 1 to "h" of "Invariant" has incompatible type "Invariant[object]"; expected "Invariant[int]" + + def g(self, x: Invariant[object]) -> None: pass + def h(self, x: Invariant[int]) -> None: pass + + def __init__(self, x: T) -> None: + self.x = x + +# Now we should have the variance correct. +a: Invariant[object] +b: Invariant[int] +if int(): + a = b # E: Incompatible types in assignment (expression has type "Invariant[int]", variable has type "Invariant[object]") +if int(): + b = a # E: Incompatible types in assignment (expression has type "Invariant[object]", variable has type "Invariant[int]") + +[case testPEP695InferVarianceNotReadyForJoin] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class Invariant[T]: + def f(self) -> None: + # Assume covariance if variance us not ready + reveal_type([Invariant(1), Invariant(object())]) \ + # N: Revealed type is "builtins.list[__main__.Invariant[builtins.object]]" + + def __init__(self, x: T) -> None: + self.x = x + +reveal_type([Invariant(1), Invariant(object())]) # N: Revealed type is "builtins.list[builtins.object]" + +[case testPEP695InferVarianceNotReadyForMeet] +# flags: --enable-incomplete-feature=NewGenericSyntax + +from typing import TypeVar, Callable + +S = TypeVar("S") +def c(a: Callable[[S], None], b: Callable[[S], None]) -> S: ... + +def a1(x: Invariant[int]) -> None: pass +def a2(x: Invariant[object]) -> None: pass + +class Invariant[T]: + def f(self) -> None: + reveal_type(c(a1, a2)) # N: Revealed type is "__main__.Invariant[builtins.int]" + + def __init__(self, x: T) -> None: + self.x = x + +reveal_type(c(a1, a2)) # N: Revealed type is "Never" + +[case testPEP695InheritInvariant] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class Invariant[T]: + x: T + +class Subclass[T](Invariant[T]): + pass + +x: Invariant[int] +y: Invariant[object] +if int(): + x = y # E: Incompatible types in assignment (expression has type "Invariant[object]", variable has type "Invariant[int]") +if int(): + y = x # E: Incompatible types in assignment (expression has type "Invariant[int]", variable has type "Invariant[object]") + +a: Subclass[int] +b: Subclass[object] +if int(): + a = b # E: Incompatible types in assignment (expression has type "Subclass[object]", variable has type "Subclass[int]") +if int(): + b = a # E: Incompatible types in assignment (expression has type "Subclass[int]", variable has type "Subclass[object]") + +[case testPEP695InheritanceMakesInvariant] +# flags: --enable-incomplete-feature=NewGenericSyntax +class Covariant[T]: + def f(self) -> T: + ... + +class Subclass[T](Covariant[list[T]]): + pass + +x: Covariant[int] = Covariant[object]() # E: Incompatible types in assignment (expression has type "Covariant[object]", variable has type "Covariant[int]") +y: Covariant[object] = Covariant[int]() + +a: Subclass[int] = Subclass[object]() # E: Incompatible types in assignment (expression has type "Subclass[object]", variable has type "Subclass[int]") +b: Subclass[object] = Subclass[int]() # E: Incompatible types in assignment (expression has type "Subclass[int]", variable has type "Subclass[object]") + +[case testPEP695InheritCoOrContravariant] +# flags: --enable-incomplete-feature=NewGenericSyntax +class Contravariant[T]: + def f(self, x: T) -> None: pass + +class CovSubclass[T](Contravariant[T]): + pass + +a: CovSubclass[int] = CovSubclass[object]() +b: CovSubclass[object] = CovSubclass[int]() # E: Incompatible types in assignment (expression has type "CovSubclass[int]", variable has type "CovSubclass[object]") + +class Covariant[T]: + def f(self) -> T: ... + +class CoSubclass[T](Covariant[T]): + pass + +c: CoSubclass[int] = CoSubclass[object]() # E: Incompatible types in assignment (expression has type "CoSubclass[object]", variable has type "CoSubclass[int]") +d: CoSubclass[object] = CoSubclass[int]() + +class InvSubclass[T](Covariant[T]): + def g(self, x: T) -> None: pass + +e: InvSubclass[int] = InvSubclass[object]() # E: Incompatible types in assignment (expression has type "InvSubclass[object]", variable has type "InvSubclass[int]") +f: InvSubclass[object] = InvSubclass[int]() # E: Incompatible types in assignment (expression has type "InvSubclass[int]", variable has type "InvSubclass[object]") + +[case testPEP695FinalAttribute] +# flags: --enable-incomplete-feature=NewGenericSyntax +from typing import Final + +class C[T]: + def __init__(self, x: T) -> None: + self.x: Final = x + +a: C[int] = C[object](1) # E: Incompatible types in assignment (expression has type "C[object]", variable has type "C[int]") +b: C[object] = C[int](1) + +[case testPEP695TwoTypeVariables] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class C[T, S]: + def f(self, x: T) -> None: ... + def g(self) -> S: ... + +a: C[int, int] = C[object, int]() +b: C[object, int] = C[int, int]() # E: Incompatible types in assignment (expression has type "C[int, int]", variable has type "C[object, int]") +c: C[int, int] = C[int, object]() # E: Incompatible types in assignment (expression has type "C[int, object]", variable has type "C[int, int]") +d: C[int, object] = C[int, int]() + +[case testPEP695Properties] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class R[T]: + @property + def p(self) -> T: ... + +class RW[T]: + @property + def p(self) -> T: ... + @p.setter + def p(self, x: T) -> None: ... + +a: R[int] = R[object]() # E: Incompatible types in assignment (expression has type "R[object]", variable has type "R[int]") +b: R[object] = R[int]() +c: RW[int] = RW[object]() # E: Incompatible types in assignment (expression has type "RW[object]", variable has type "RW[int]") +d: RW[object] = RW[int]() # E: Incompatible types in assignment (expression has type "RW[int]", variable has type "RW[object]") +[builtins fixtures/property.pyi] + +[case testPEP695Protocol] +# flags: --enable-incomplete-feature=NewGenericSyntax +from typing import Protocol + +class PContra[T](Protocol): + def f(self, x: T) -> None: ... + +PContra() # E: Cannot instantiate protocol class "PContra" +a: PContra[int] +b: PContra[object] +if int(): + a = b +if int(): + b = a # E: Incompatible types in assignment (expression has type "PContra[int]", variable has type "PContra[object]") + +class PCov[T](Protocol): + def f(self) -> T: ... + +PCov() # E: Cannot instantiate protocol class "PCov" +c: PCov[int] +d: PCov[object] +if int(): + c = d # E: Incompatible types in assignment (expression has type "PCov[object]", variable has type "PCov[int]") +if int(): + d = c + +class PInv[T](Protocol): + def f(self, x: T) -> T: ... + +PInv() # E: Cannot instantiate protocol class "PInv" +e: PInv[int] +f: PInv[object] +if int(): + e = f # E: Incompatible types in assignment (expression has type "PInv[object]", variable has type "PInv[int]") +if int(): + f = e # E: Incompatible types in assignment (expression has type "PInv[int]", variable has type "PInv[object]") + +[case testPEP695TypeAlias] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class C[T]: pass +class D[T, S]: pass + +type A[S] = C[S] + +a: A[int] +reveal_type(a) # N: Revealed type is "__main__.C[builtins.int]" + +type A2[T] = C[C[T]] +a2: A2[str] +reveal_type(a2) # N: Revealed type is "__main__.C[__main__.C[builtins.str]]" + +type A3[T, S] = D[S, C[T]] +a3: A3[int, str] +reveal_type(a3) # N: Revealed type is "__main__.D[builtins.str, __main__.C[builtins.int]]" + +type A4 = int | str +a4: A4 +reveal_type(a4) # N: Revealed type is "Union[builtins.int, builtins.str]" + +[case testPEP695TypeAliasWithUnusedTypeParams] +# flags: --enable-incomplete-feature=NewGenericSyntax +type A[T] = int +a: A[str] +reveal_type(a) # N: Revealed type is "builtins.int" + +[case testPEP695TypeAliasForwardReference1] +# flags: --enable-incomplete-feature=NewGenericSyntax + +type A[T] = C[T] + +a: A[int] +reveal_type(a) # N: Revealed type is "__main__.C[builtins.int]" + +class C[T]: pass + +[case testPEP695TypeAliasForwardReference2] +# flags: --enable-incomplete-feature=NewGenericSyntax + +type X = C +type A = X + +a: A +reveal_type(a) # N: Revealed type is "__main__.C" + +class C: pass + +[case testPEP695TypeAliasForwardReference3] +# flags: --enable-incomplete-feature=NewGenericSyntax + +type X = D +type A = C[X] + +a: A +reveal_type(a) # N: Revealed type is "__main__.C[__main__.D]" + +class C[T]: pass +class D: pass + +[case testPEP695TypeAliasForwardReference4] +# flags: --enable-incomplete-feature=NewGenericSyntax + +type A = C + +# Note that this doesn't actually work at runtime, but we currently don't +# keep track whether a type alias is valid in various runtime type contexts. +class D(A): + pass + +class C: pass + +x: C = D() +y: D = C() # E: Incompatible types in assignment (expression has type "C", variable has type "D") + +[case testPEP695TypeAliasForwardReference5] +# flags: --enable-incomplete-feature=NewGenericSyntax +type A = str +type B[T] = C[T] +class C[T]: pass +a: A +b: B[int] +c: C[str] +reveal_type(a) # N: Revealed type is "builtins.str" +reveal_type(b) # N: Revealed type is "__main__.C[builtins.int]" +reveal_type(c) # N: Revealed type is "__main__.C[builtins.str]" + +[case testPEP695TypeAliasWithUndefineName] +# flags: --enable-incomplete-feature=NewGenericSyntax +type A[T] = XXX # E: Name "XXX" is not defined +a: A[int] +reveal_type(a) # N: Revealed type is "Any" + +[case testPEP695TypeAliasInvalidType] +# flags: --enable-incomplete-feature=NewGenericSyntax +type A = int | 1 # E: Invalid type: try using Literal[1] instead? +a: A +reveal_type(a) # N: Revealed type is "Union[builtins.int, Any]" +type B = int + str # E: Invalid type alias: expression is not a valid type +b: B +reveal_type(b) # N: Revealed type is "Any" + +[case testPEP695TypeAliasBoundForwardReference] +# mypy: enable-incomplete-feature=NewGenericSyntax +type B[T: Foo] = list[T] +class Foo: pass + +[case testPEP695UpperBound] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class D: + x: int +class E(D): pass + +class C[T: D]: pass + +a: C[D] +b: C[E] +reveal_type(a) # N: Revealed type is "__main__.C[__main__.D]" +reveal_type(b) # N: Revealed type is "__main__.C[__main__.E]" + +c: C[int] # E: Type argument "int" of "C" must be a subtype of "D" + +def f[T: D](a: T) -> T: + reveal_type(a.x) # N: Revealed type is "builtins.int" + return a + +reveal_type(f(D())) # N: Revealed type is "__main__.D" +reveal_type(f(E())) # N: Revealed type is "__main__.E" +f(1) # E: Value of type variable "T" of "f" cannot be "int" + +[case testPEP695UpperBoundForwardReference1] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class C[T: D]: pass + +a: C[D] +b: C[E] +reveal_type(a) # N: Revealed type is "__main__.C[__main__.D]" +reveal_type(b) # N: Revealed type is "__main__.C[__main__.E]" + +c: C[int] # E: Type argument "int" of "C" must be a subtype of "D" + +class D: pass +class E(D): pass + +[case testPEP695UpperBoundForwardReference2] +# flags: --enable-incomplete-feature=NewGenericSyntax + +type A = D +class C[T: A]: pass + +class D: pass +class E(D): pass + +a: C[D] +b: C[E] +reveal_type(a) # N: Revealed type is "__main__.C[__main__.D]" +reveal_type(b) # N: Revealed type is "__main__.C[__main__.E]" + +c: C[int] # E: Type argument "int" of "C" must be a subtype of "D" + +[case testPEP695UpperBoundForwardReference3] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class D[T]: pass +class E[T](D[T]): pass + +type A = D[X] + +class C[T: A]: pass + +class X: pass + +a: C[D[X]] +b: C[E[X]] +reveal_type(a) # N: Revealed type is "__main__.C[__main__.D[__main__.X]]" +reveal_type(b) # N: Revealed type is "__main__.C[__main__.E[__main__.X]]" + +c: C[D[int]] # E: Type argument "D[int]" of "C" must be a subtype of "D[X]" + +[case testPEP695UpperBoundForwardReference4] +# flags: --enable-incomplete-feature=NewGenericSyntax + +def f[T: D](a: T) -> T: + reveal_type(a.x) # N: Revealed type is "builtins.int" + return a + +class D: + x: int +class E(D): pass + +reveal_type(f(D())) # N: Revealed type is "__main__.D" +reveal_type(f(E())) # N: Revealed type is "__main__.E" +f(1) # E: Value of type variable "T" of "f" cannot be "int" + +[case testPEP695UpperBoundUndefinedName] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class C[T: XX]: # E: Name "XX" is not defined + pass + +a: C[int] + +def f[T: YY](x: T) -> T: # E: Name "YY" is not defined + return x +reveal_type(f) # N: Revealed type is "def [T <: Any] (x: T`-1) -> T`-1" + +[case testPEP695UpperBoundWithMultipleParams] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class C[T, S: int]: pass +class D[A: int, B]: pass + +def f[T: int, S: int | str](x: T, y: S) -> T | S: + return x + +C[str, int]() +C[str, str]() # E: Value of type variable "S" of "C" cannot be "str" +D[int, str]() +D[str, str]() # E: Value of type variable "A" of "D" cannot be "str" +f(1, 1) +u: int | str +f(1, u) +f('x', None) # E: Value of type variable "T" of "f" cannot be "str" \ + # E: Value of type variable "S" of "f" cannot be "None" + +[case testPEP695InferVarianceOfTupleType] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class Cov[T](tuple[int, str]): + def f(self) -> T: pass + +class Cov2[T](tuple[T, T]): + pass + +class Contra[T](tuple[int, str]): + def f(self, x: T) -> None: pass + +a: Cov[object] = Cov[int]() +b: Cov[int] = Cov[object]() # E: Incompatible types in assignment (expression has type "Cov[object]", variable has type "Cov[int]") + +c: Cov2[object] = Cov2[int]() +d: Cov2[int] = Cov2[object]() # E: Incompatible types in assignment (expression has type "Cov2[object]", variable has type "Cov2[int]") + +e: Contra[int] = Contra[object]() +f: Contra[object] = Contra[int]() # E: Incompatible types in assignment (expression has type "Contra[int]", variable has type "Contra[object]") +[builtins fixtures/tuple-simple.pyi] + +[case testPEP695ValueRestiction] +# flags: --enable-incomplete-feature=NewGenericSyntax + +def f[T: (int, str)](x: T) -> T: + reveal_type(x) # N: Revealed type is "builtins.int" \ + # N: Revealed type is "builtins.str" + return x + +reveal_type(f(1)) # N: Revealed type is "builtins.int" +reveal_type(f('x')) # N: Revealed type is "builtins.str" +f(None) # E: Value of type variable "T" of "f" cannot be "None" + +class C[T: (object, None)]: pass + +a: C[object] +b: C[None] +c: C[int] # E: Value of type variable "T" of "C" cannot be "int" + +[case testPEP695ValueRestictionForwardReference] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class C[T: (int, D)]: + def __init__(self, x: T) -> None: + a = x + if int(): + a = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "int") \ + # E: Incompatible types in assignment (expression has type "str", variable has type "D") + self.x: T = x + +reveal_type(C(1).x) # N: Revealed type is "builtins.int" +C(None) # E: Value of type variable "T" of "C" cannot be "None" + +class D: pass + +C(D()) + +[case testPEP695ValueRestictionUndefinedName] +# flags: --enable-incomplete-feature=NewGenericSyntax + +class C[T: (int, XX)]: # E: Name "XX" is not defined + pass + +def f[S: (int, YY)](x: S) -> S: # E: Name "YY" is not defined + return x + +[case testPEP695ParamSpec] +# flags: --enable-incomplete-feature=NewGenericSyntax +from typing import Callable + +def g[**P](f: Callable[P, None], *args: P.args, **kwargs: P.kwargs) -> None: + f(*args, **kwargs) + f(1, *args, **kwargs) # E: Argument 1 has incompatible type "int"; expected "P.args" + +def h(x: int, y: str) -> None: pass + +g(h, 1, y='x') +g(h, 1, x=1) # E: "g" gets multiple values for keyword argument "x" \ + # E: Missing positional argument "y" in call to "g" + +class C[**P, T]: + def m(self, *args: P.args, **kwargs: P.kwargs) -> T: ... + +a: C[[int, str], None] +reveal_type(a) # N: Revealed type is "__main__.C[[builtins.int, builtins.str], None]" +reveal_type(a.m) # N: Revealed type is "def (builtins.int, builtins.str)" +[builtins fixtures/tuple.pyi] + +[case testPEP695ParamSpecTypeAlias] +# flags: --enable-incomplete-feature=NewGenericSyntax +from typing import Callable + +type C[**P] = Callable[P, int] + +f: C[[str, int | None]] +reveal_type(f) # N: Revealed type is "def (builtins.str, Union[builtins.int, None]) -> builtins.int" +[builtins fixtures/tuple.pyi] + +[case testPEP695TypeVarTuple] +# flags: --enable-incomplete-feature=NewGenericSyntax + +def f[*Ts](t: tuple[*Ts]) -> tuple[*Ts]: + reveal_type(t) # N: Revealed type is "Tuple[Unpack[Ts`-1]]" + return t + +reveal_type(f((1, 'x'))) # N: Revealed type is "Tuple[Literal[1]?, Literal['x']?]" +a: tuple[int, ...] +reveal_type(f(a)) # N: Revealed type is "builtins.tuple[builtins.int, ...]" + +class C[T, *Ts]: + pass + +b: C[int, str, None] +reveal_type(b) # N: Revealed type is "__main__.C[builtins.int, builtins.str, None]" +c: C[str] +reveal_type(c) # N: Revealed type is "__main__.C[builtins.str]" +b = c # E: Incompatible types in assignment (expression has type "C[str]", variable has type "C[int, str, None]") +[builtins fixtures/tuple.pyi] + +[case testPEP695TypeVarTupleAlias] +# flags: --enable-incomplete-feature=NewGenericSyntax +from typing import Callable + +type C[*Ts] = tuple[*Ts, int] + +a: C[str, None] +reveal_type(a) # N: Revealed type is "Tuple[builtins.str, None, builtins.int]" +[builtins fixtures/tuple.pyi] + +[case testPEP695IncrementalFunction] +# flags: --enable-incomplete-feature=NewGenericSyntax +import a + +[file a.py] +import b + +[file a.py.2] +import b +reveal_type(b.f(1)) +reveal_type(b.g(1, 'x')) +b.g('x', 'x') +b.g(1, 2) + +[file b.py] +def f[T](x: T) -> T: + return x + +def g[T: int, S: (str, None)](x: T, y: S) -> T | S: + return x + +[out2] +tmp/a.py:2: note: Revealed type is "builtins.int" +tmp/a.py:3: note: Revealed type is "Union[builtins.int, builtins.str]" +tmp/a.py:4: error: Value of type variable "T" of "g" cannot be "str" +tmp/a.py:5: error: Value of type variable "S" of "g" cannot be "int" + +[case testPEP695IncrementalClass] +# flags: --enable-incomplete-feature=NewGenericSyntax +import a + +[file a.py] +import b + +[file a.py.2] +from b import C, D +x: C[int] +reveal_type(x) + +class N(int): pass +class SS(str): pass + +y1: D[int, str] +y2: D[N, str] +y3: D[int, None] +y4: D[int, None] +y5: D[int, SS] # Error +y6: D[object, str] # Error + +[file b.py] +class C[T]: pass + +class D[T: int, S: (str, None)]: + pass + +[out2] +tmp/a.py:3: note: Revealed type is "b.C[builtins.int]" +tmp/a.py:12: error: Value of type variable "S" of "D" cannot be "SS" +tmp/a.py:13: error: Type argument "object" of "D" must be a subtype of "int" + +[case testPEP695IncrementalParamSpecAndTypeVarTuple] +# flags: --enable-incomplete-feature=NewGenericSyntax +import a + +[file a.py] +import b + +[file a.py.2] +from b import C, D +x1: C[()] +x2: C[int] +x3: C[int, str] +y: D[[int, str]] +reveal_type(y.m) + +[file b.py] +class C[*Ts]: pass +class D[**P]: + def m(self, *args: P.args, **kwargs: P.kwargs) -> None: pass + +[builtins fixtures/tuple.pyi] +[out2] +tmp/a.py:6: note: Revealed type is "def (builtins.int, builtins.str)" + +[case testPEP695IncrementalTypeAlias] +# flags: --enable-incomplete-feature=NewGenericSyntax +import a + +[file a.py] +import b + +[file a.py.2] +from b import A, B +a: A +reveal_type(a) +b: B[int] +reveal_type(b) + +[file b.py] +type A = str +class Foo[T]: pass +type B[T] = Foo[T] + +[builtins fixtures/tuple.pyi] +[out2] +tmp/a.py:3: note: Revealed type is "builtins.str" +tmp/a.py:5: note: Revealed type is "b.Foo[builtins.int]" + +[case testPEP695UndefinedNameInGenericFunction] +# mypy: enable-incomplete-feature=NewGenericSyntax + +def f[T](x: T) -> T: + return unknown() # E: Name "unknown" is not defined + +class C: + def m[T](self, x: T) -> T: + return unknown() # E: Name "unknown" is not defined + +[case testPEP695FunctionTypeVarAccessInFunction] +# mypy: enable-incomplete-feature=NewGenericSyntax +from typing import cast + +class C: + def m[T](self, x: T) -> T: + y: T = x + reveal_type(y) # N: Revealed type is "T`-1" + return cast(T, y) + +reveal_type(C().m(1)) # N: Revealed type is "builtins.int" + +[case testPEP695ScopingBasics] +# mypy: enable-incomplete-feature=NewGenericSyntax + +T = 1 + +def f[T](x: T) -> T: + T = 'a' + reveal_type(T) # N: Revealed type is "builtins.str" + return x + +reveal_type(T) # N: Revealed type is "builtins.int" + +class C[T]: + T = 1.2 + reveal_type(T) # N: Revealed type is "builtins.float" + +reveal_type(T) # N: Revealed type is "builtins.int" + +[case testPEP695ClassScoping] +# mypy: enable-incomplete-feature=NewGenericSyntax + +class C: + class D: pass + + def m[T: D](self, x: T, y: D) -> T: + return x + +C().m(C.D(), C.D()) +C().m(1, C.D()) # E: Value of type variable "T" of "m" of "C" cannot be "int" + +[case testPEP695NestedGenericFunction] +# mypy: enable-incomplete-feature=NewGenericSyntax +def f[T](x: T) -> T: + reveal_type(f(x)) # N: Revealed type is "T`-1" + reveal_type(f(1)) # N: Revealed type is "builtins.int" + + def ff(x: T) -> T: + y: T = x + return y + reveal_type(ff(x)) # N: Revealed type is "T`-1" + ff(1) # E: Argument 1 to "ff" has incompatible type "int"; expected "T" + + def g[S](a: S) -> S: + ff(a) # E: Argument 1 to "ff" has incompatible type "S"; expected "T" + return a + reveal_type(g(1)) # N: Revealed type is "builtins.int" + reveal_type(g(x)) # N: Revealed type is "T`-1" + + def h[S](a: S) -> S: + return a + reveal_type(h(1)) # N: Revealed type is "builtins.int" + reveal_type(h(x)) # N: Revealed type is "T`-1" + return x + +[case testPEP695NonLocalAndGlobal] +# mypy: enable-incomplete-feature=NewGenericSyntax +def f() -> None: + T = 1 + def g[T](x: T) -> T: + nonlocal T # E: nonlocal binding not allowed for type parameter "T" + T = 'x' # E: "T" is a type variable and only valid in type context + return x + reveal_type(T) # N: Revealed type is "builtins.int" + +def g() -> None: + a = 1 + def g[T](x: T) -> T: + nonlocal a + a = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "int") + return x + +x = 1 + +def h[T](a: T) -> T: + global x + x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") + return a + +class C[T]: + def m[S](self, a: S) -> S: + global x + x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") + return a + +[case testPEP695ArgumentDefault] +# mypy: enable-incomplete-feature=NewGenericSyntax +from typing import cast + +def f[T]( + x: T = + T # E: Name "T" is not defined \ + # E: Incompatible default for argument "x" (default has type "object", argument has type "T") +) -> T: + return x + +def g[T](x: T = cast(T, None)) -> T: # E: Name "T" is not defined + return x + +class C: + def m[T](self, x: T = cast(T, None)) -> T: # E: Name "T" is not defined + return x + +[case testPEP695ListComprehension] +# mypy: enable-incomplete-feature=NewGenericSyntax +from typing import cast + +def f[T](x: T) -> T: + b = [cast(T, a) for a in [1, 2]] + reveal_type(b) # N: Revealed type is "builtins.list[T`-1]" + return x + +[case testPEP695ReuseNameInSameScope] +# mypy: enable-incomplete-feature=NewGenericSyntax + +class C[T]: + def m[S](self, x: S, y: T) -> S | T: + return x + + def m2[S](self, x: S, y: T) -> S | T: + return x + +class D[T]: + pass + +def f[T](x: T) -> T: + return x + +def g[T](x: T) -> T: + def nested[S](y: S) -> S: + return y + def nested2[S](y: S) -> S: + return y + return x + +[case testPEP695NestedScopingSpecialCases] +# mypy: enable-incomplete-feature=NewGenericSyntax +# This is adapted from PEP 695 +S = 0 + +def outer1[S]() -> None: + S = 1 + T = 1 + + def outer2[T]() -> None: + def inner1() -> None: + nonlocal S + nonlocal T # E: nonlocal binding not allowed for type parameter "T" + + def inner2() -> None: + global S + +[case testPEP695ScopingWithBaseClasses] +# mypy: enable-incomplete-feature=NewGenericSyntax +# This is adapted from PEP 695 +class Outer: + class Private: + pass + + # If the type parameter scope was like a traditional scope, + # the base class 'Private' would not be accessible here. + class Inner[T](Private, list[T]): + pass + + # Likewise, 'Inner' would not be available in these type annotations. + def method1[T](self, a: Inner[T]) -> Inner[T]: + return a + +[case testPEP695RedefineTypeParameterInScope] +# mypy: enable-incomplete-feature=NewGenericSyntax +class C[T]: + def m[T](self, x: T) -> T: # E: "T" already defined as a type parameter + return x + def m2(self) -> None: + def nested[T](x: T) -> T: # E: "T" already defined as a type parameter + return x + +def f[S, S](x: S) -> S: # E: "S" already defined as a type parameter + return x + +[case testPEP695ClassDecorator] +# mypy: enable-incomplete-feature=NewGenericSyntax +from typing import Any + +T = 0 + +def decorator(x: str) -> Any: ... + +@decorator(T) # E: Argument 1 to "decorator" has incompatible type "int"; expected "str" +class C[T]: + pass diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index b9f9f2173ae1..0f1cbb6e81c4 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -115,25 +115,21 @@ def g(x: int): ... ) # type: ignore # E: Unused "type: ignore" comment [case testPEP570ArgTypesMissing] -# flags: --python-version 3.8 --disallow-untyped-defs +# flags: --disallow-untyped-defs def f(arg, /) -> None: ... # E: Function is missing a type annotation for one or more arguments [case testPEP570ArgTypesBadDefault] -# flags: --python-version 3.8 def f(arg: int = "ERROR", /) -> None: ... # E: Incompatible default for argument "arg" (default has type "str", argument has type "int") [case testPEP570ArgTypesDefault] -# flags: --python-version 3.8 def f(arg: int = 0, /) -> None: reveal_type(arg) # N: Revealed type is "builtins.int" [case testPEP570ArgTypesRequired] -# flags: --python-version 3.8 def f(arg: int, /) -> None: reveal_type(arg) # N: Revealed type is "builtins.int" [case testPEP570Required] -# flags: --python-version 3.8 def f(arg: int, /) -> None: ... # N: "f" defined here f(1) f("ERROR") # E: Argument 1 to "f" has incompatible type "str"; expected "int" @@ -141,7 +137,6 @@ f(arg=1) # E: Unexpected keyword argument "arg" for "f" f(arg="ERROR") # E: Unexpected keyword argument "arg" for "f" [case testPEP570Default] -# flags: --python-version 3.8 def f(arg: int = 0, /) -> None: ... # N: "f" defined here f() f(1) @@ -150,7 +145,7 @@ f(arg=1) # E: Unexpected keyword argument "arg" for "f" f(arg="ERROR") # E: Unexpected keyword argument "arg" for "f" [case testPEP570Calls] -# flags: --python-version 3.8 --no-strict-optional +# flags: --no-strict-optional from typing import Any, Dict def f(p, /, p_or_kw, *, kw) -> None: ... # N: "f" defined here d = None # type: Dict[Any, Any] @@ -163,7 +158,6 @@ f(**d) # E: Missing positional argument "p_or_kw" in call to "f" [builtins fixtures/dict.pyi] [case testPEP570Signatures1] -# flags: --python-version 3.8 def f(p1: bytes, p2: float, /, p_or_kw: int, *, kw: str) -> None: reveal_type(p1) # N: Revealed type is "builtins.bytes" reveal_type(p2) # N: Revealed type is "builtins.float" @@ -171,7 +165,6 @@ def f(p1: bytes, p2: float, /, p_or_kw: int, *, kw: str) -> None: reveal_type(kw) # N: Revealed type is "builtins.str" [case testPEP570Signatures2] -# flags: --python-version 3.8 def f(p1: bytes, p2: float = 0.0, /, p_or_kw: int = 0, *, kw: str) -> None: reveal_type(p1) # N: Revealed type is "builtins.bytes" reveal_type(p2) # N: Revealed type is "builtins.float" @@ -179,33 +172,28 @@ def f(p1: bytes, p2: float = 0.0, /, p_or_kw: int = 0, *, kw: str) -> None: reveal_type(kw) # N: Revealed type is "builtins.str" [case testPEP570Signatures3] -# flags: --python-version 3.8 def f(p1: bytes, p2: float = 0.0, /, *, kw: int) -> None: reveal_type(p1) # N: Revealed type is "builtins.bytes" reveal_type(p2) # N: Revealed type is "builtins.float" reveal_type(kw) # N: Revealed type is "builtins.int" [case testPEP570Signatures4] -# flags: --python-version 3.8 def f(p1: bytes, p2: int = 0, /) -> None: reveal_type(p1) # N: Revealed type is "builtins.bytes" reveal_type(p2) # N: Revealed type is "builtins.int" [case testPEP570Signatures5] -# flags: --python-version 3.8 def f(p1: bytes, p2: float, /, p_or_kw: int) -> None: reveal_type(p1) # N: Revealed type is "builtins.bytes" reveal_type(p2) # N: Revealed type is "builtins.float" reveal_type(p_or_kw) # N: Revealed type is "builtins.int" [case testPEP570Signatures6] -# flags: --python-version 3.8 def f(p1: bytes, p2: float, /) -> None: reveal_type(p1) # N: Revealed type is "builtins.bytes" reveal_type(p2) # N: Revealed type is "builtins.float" [case testPEP570Unannotated] -# flags: --python-version 3.8 def f(arg, /): ... # N: "f" defined here g = lambda arg, /: arg def h(arg=0, /): ... # N: "h" defined here @@ -223,7 +211,6 @@ h(arg=0) # E: Unexpected keyword argument "arg" for "h" i(arg=0) # E: Unexpected keyword argument "arg" [case testWalrus] -# flags: --strict-optional --python-version 3.8 from typing import NamedTuple, Optional, List from typing_extensions import Final @@ -310,7 +297,8 @@ def f(x: int = (c := 4)) -> int: z2: NT # E: Variable "NT" is not valid as a type \ # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases - if Alias := int: # E: Function "Type[int]" could always be true in boolean context + if Alias := int: # E: Function "Alias" could always be true in boolean context \ + # E: Function "int" could always be true in boolean context z3: Alias # E: Variable "Alias" is not valid as a type \ # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases @@ -398,7 +386,6 @@ reveal_type(z2) # E: Name "z2" is not defined # N: Revealed type is "Any" [builtins fixtures/isinstancelist.pyi] [case testWalrusConditionalTypeBinder] -# flags: --python-version 3.8 from typing import Tuple, Union from typing_extensions import Literal @@ -426,7 +413,6 @@ else: [builtins fixtures/list.pyi] [case testWalrusConditionalTypeCheck] -# flags: --strict-optional --python-version 3.8 from typing import Optional maybe_str: Optional[str] @@ -442,7 +428,6 @@ reveal_type(maybe_str) # N: Revealed type is "Union[builtins.str, None]" [builtins fixtures/bool.pyi] [case testWalrusConditionalTypeCheck2] -# flags: --python-version 3.8 from typing import Optional maybe_str: Optional[str] @@ -458,7 +443,6 @@ reveal_type(maybe_str) # N: Revealed type is "Union[builtins.str, None]" [builtins fixtures/bool.pyi] [case testWalrusPartialTypes] -# flags: --python-version 3.8 from typing import List def check_partial_list() -> None: @@ -475,7 +459,7 @@ def check_partial_list() -> None: [builtins fixtures/list.pyi] [case testWalrusAssignmentAndConditionScopeForLiteral] -# flags: --warn-unreachable --python-version 3.8 +# flags: --warn-unreachable if (x := 0): reveal_type(x) # E: Statement is unreachable @@ -485,7 +469,7 @@ else: reveal_type(x) # N: Revealed type is "builtins.int" [case testWalrusAssignmentAndConditionScopeForProperty] -# flags: --warn-unreachable --python-version 3.8 +# flags: --warn-unreachable from typing_extensions import Literal @@ -513,7 +497,7 @@ reveal_type(y) # N: Revealed type is "Literal[False]" [builtins fixtures/property.pyi] [case testWalrusAssignmentAndConditionScopeForFunction] -# flags: --warn-unreachable --python-version 3.8 +# flags: --warn-unreachable from typing_extensions import Literal @@ -546,7 +530,6 @@ reveal_type(z) # N: Revealed type is "Literal[False]" [builtins fixtures/tuple.pyi] [case testWalrusExpr] -# flags: --python-version 3.8 def func() -> None: foo = Foo() if x := foo.x: @@ -557,7 +540,6 @@ class Foo: self.x = 123 [case testWalrusTypeGuard] -# flags: --python-version 3.8 from typing_extensions import TypeGuard def is_float(a: object) -> TypeGuard[float]: pass def main(a: object) -> None: @@ -567,14 +549,12 @@ def main(a: object) -> None: [builtins fixtures/tuple.pyi] [case testWalrusRedefined] -# flags: --python-version 3.8 def foo() -> None: x = 0 [x := x + y for y in [1, 2, 3]] [builtins fixtures/dict.pyi] [case testWalrusUsedBeforeDef] -# flags: --python-version 3.8 class C: def f(self, c: 'C') -> None: pass @@ -582,7 +562,6 @@ class C: (y := C()).f(y) [case testOverloadWithPositionalOnlySelf] -# flags: --python-version 3.8 from typing import overload, Optional class Foo: @@ -607,7 +586,6 @@ class Bar: [builtins fixtures/bool.pyi] [case testOverloadPositionalOnlyErrorMessage] -# flags: --python-version 3.8 from typing import overload @overload @@ -618,13 +596,12 @@ def foo(a): ... foo(a=1) [out] -main:10: error: No overload variant of "foo" matches argument type "int" -main:10: note: Possible overload variants: -main:10: note: def foo(int, /) -> Any -main:10: note: def foo(a: str) -> Any +main:9: error: No overload variant of "foo" matches argument type "int" +main:9: note: Possible overload variants: +main:9: note: def foo(int, /) -> Any +main:9: note: def foo(a: str) -> Any [case testOverloadPositionalOnlyErrorMessageAllTypes] -# flags: --python-version 3.8 from typing import overload @overload @@ -635,13 +612,12 @@ def foo(a, b, *, c): ... foo(a=1) [out] -main:10: error: No overload variant of "foo" matches argument type "int" -main:10: note: Possible overload variants: -main:10: note: def foo(int, /, b: int, *, c: int) -> Any -main:10: note: def foo(a: str, b: int, *, c: int) -> Any +main:9: error: No overload variant of "foo" matches argument type "int" +main:9: note: Possible overload variants: +main:9: note: def foo(int, /, b: int, *, c: int) -> Any +main:9: note: def foo(a: str, b: int, *, c: int) -> Any [case testOverloadPositionalOnlyErrorMessageMultiplePosArgs] -# flags: --python-version 3.8 from typing import overload @overload @@ -652,13 +628,12 @@ def foo(a, b, c, d): ... foo(a=1) [out] -main:10: error: No overload variant of "foo" matches argument type "int" -main:10: note: Possible overload variants: -main:10: note: def foo(int, int, int, /, d: str) -> Any -main:10: note: def foo(a: str, b: int, c: int, d: str) -> Any +main:9: error: No overload variant of "foo" matches argument type "int" +main:9: note: Possible overload variants: +main:9: note: def foo(int, int, int, /, d: str) -> Any +main:9: note: def foo(a: str, b: int, c: int, d: str) -> Any [case testOverloadPositionalOnlyErrorMessageMethod] -# flags: --python-version 3.8 from typing import overload class Some: @@ -672,14 +647,13 @@ class Some: Some().foo(a=1) [out] -main:13: error: No overload variant of "foo" of "Some" matches argument type "int" -main:13: note: Possible overload variants: -main:13: note: def foo(self, int, /) -> Any -main:13: note: def foo(self, float, /) -> Any -main:13: note: def foo(self, a: str) -> Any +main:12: error: No overload variant of "foo" of "Some" matches argument type "int" +main:12: note: Possible overload variants: +main:12: note: def foo(self, int, /) -> Any +main:12: note: def foo(self, float, /) -> Any +main:12: note: def foo(self, a: str) -> Any [case testOverloadPositionalOnlyErrorMessageClassMethod] -# flags: --python-version 3.8 from typing import overload class Some: @@ -698,14 +672,13 @@ class Some: Some.foo(a=1) [builtins fixtures/classmethod.pyi] [out] -main:17: error: No overload variant of "foo" of "Some" matches argument type "int" -main:17: note: Possible overload variants: -main:17: note: def foo(cls, int, /) -> Any -main:17: note: def foo(cls, float, /) -> Any -main:17: note: def foo(cls, a: str) -> Any +main:16: error: No overload variant of "foo" of "Some" matches argument type "int" +main:16: note: Possible overload variants: +main:16: note: def foo(cls, int, /) -> Any +main:16: note: def foo(cls, float, /) -> Any +main:16: note: def foo(cls, a: str) -> Any [case testUnpackWithDuplicateNamePositionalOnly] -# flags: --python-version 3.8 from typing_extensions import Unpack, TypedDict class Person(TypedDict): @@ -716,7 +689,7 @@ def foo(name: str, /, **kwargs: Unpack[Person]) -> None: # Allowed [builtins fixtures/dict.pyi] [case testPossiblyUndefinedWithAssignmentExpr] -# flags: --python-version 3.8 --enable-error-code possibly-undefined +# flags: --enable-error-code possibly-undefined def f1() -> None: d = {0: 1} if int(): @@ -728,7 +701,6 @@ def f1() -> None: [builtins fixtures/dict.pyi] [case testNarrowOnSelfInGeneric] -# flags: --strict-optional from typing import Generic, TypeVar, Optional T = TypeVar("T", int, str) @@ -740,11 +712,10 @@ class C(Generic[T]): reveal_type(y) return None [out] -main:10: note: Revealed type is "builtins.int" -main:10: note: Revealed type is "builtins.str" +main:9: note: Revealed type is "builtins.int" +main:9: note: Revealed type is "builtins.str" [case testTypeGuardWithPositionalOnlyArg] -# flags: --python-version 3.8 from typing_extensions import TypeGuard def typeguard(x: object, /) -> TypeGuard[int]: @@ -755,10 +726,9 @@ if typeguard(n): reveal_type(n) [builtins fixtures/tuple.pyi] [out] -main:9: note: Revealed type is "builtins.int" +main:8: note: Revealed type is "builtins.int" [case testTypeGuardKeywordFollowingWalrus] -# flags: --python-version 3.8 from typing import cast from typing_extensions import TypeGuard @@ -769,9 +739,68 @@ if typeguard(x=(n := cast(object, "hi"))): reveal_type(n) [builtins fixtures/tuple.pyi] [out] -main:9: note: Revealed type is "builtins.int" +main:8: note: Revealed type is "builtins.int" [case testNoCrashOnAssignmentExprClass] class C: [(j := i) for i in [1, 2, 3]] # E: Assignment expression within a comprehension cannot be used in a class body [builtins fixtures/list.pyi] + +[case testNarrowedVariableInNestedModifiedInWalrus] +from typing import Optional + +def walrus_with_nested_error(x: Optional[str]) -> None: + if x is None: + x = "a" + def nested() -> str: + return x # E: Incompatible return value type (got "Optional[str]", expected "str") + if x := None: + pass + nested() + +def walrus_with_nested_ok(x: Optional[str]) -> None: + if x is None: + x = "a" + def nested() -> str: + return x + if y := x: + pass + nested() + +[case testIgnoreWholeModule] +# flags: --warn-unused-ignores +# type: ignore +IGNORE # type: ignore + +[case testUnusedIgnoreVersionCheck] +# flags: --warn-unused-ignores +import sys + +if sys.version_info < (3, 6): + 42 # type: ignore +else: + 42 # type: ignore # E: Unused "type: ignore" comment +[builtins fixtures/ops.pyi] + +[case testDictExpressionErrorLocations] +# flags: --pretty +from typing import Dict + +other: Dict[str, str] +dct: Dict[str, int] = {"a": "b", **other} +[builtins fixtures/dict.pyi] +[out] +main:5: error: Dict entry 0 has incompatible type "str": "str"; expected "str": "int" + dct: Dict[str, int] = {"a": "b", **other} + ^~~~~~~~ +main:5: error: Unpacked dict entry 1 has incompatible type "Dict[str, str]"; expected "SupportsKeysAndGetItem[str, int]" + dct: Dict[str, int] = {"a": "b", **other} + ^~~~~ + +[case testWalrusAssignmentEmptyCollection] +from typing import List + +y: List[int] +if (y := []): + reveal_type(y) # N: Revealed type is "builtins.list[builtins.int]" +[builtins fixtures/list.pyi] diff --git a/test-data/unit/check-python39.test b/test-data/unit/check-python39.test index 105051a840bb..e17bf1e7ab5b 100644 --- a/test-data/unit/check-python39.test +++ b/test-data/unit/check-python39.test @@ -17,3 +17,11 @@ decorator_list: List[Callable[..., Callable[[int], str]]] def f(x: float) -> float: ... reveal_type(f) # N: Revealed type is "def (builtins.int) -> builtins.str" [builtins fixtures/list.pyi] + +[case testStarredExpressionsInForLoop] +# flags: --python-version 3.9 + +a = b = c = [1, 2, 3] +for x in *a, *b, *c: + reveal_type(x) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-recursive-types.test b/test-data/unit/check-recursive-types.test index b7b4372ecc12..84593933a2de 100644 --- a/test-data/unit/check-recursive-types.test +++ b/test-data/unit/check-recursive-types.test @@ -409,8 +409,8 @@ def local() -> None: x: L reveal_type(x) # N: Revealed type is "builtins.list[Union[builtins.int, Any]]" -S = Type[S] # E: Type[...] cannot contain another Type[...] -U = Type[Union[int, U]] # E: Type[...] cannot contain another Type[...] +S = Type[S] # E: Type[...] can't contain another Type[...] +U = Type[Union[int, U]] # E: Type[...] can't contain another Type[...] x: U reveal_type(x) # N: Revealed type is "Type[Any]" @@ -422,7 +422,6 @@ reveal_type(d) # N: Revealed type is "Any" [builtins fixtures/isinstancelist.pyi] [case testBasicRecursiveNamedTuple] -# flags: --strict-optional from typing import NamedTuple, Optional NT = NamedTuple("NT", [("x", Optional[NT]), ("y", int)]) @@ -457,7 +456,6 @@ reveal_type(f(tnt, nt)) # N: Revealed type is "builtins.tuple[Any, ...]" [builtins fixtures/tuple.pyi] [case testBasicRecursiveNamedTupleClass] -# flags: --strict-optional from typing import NamedTuple, Optional class NT(NamedTuple): @@ -684,7 +682,6 @@ itd2 = TD(x=0, y=TD(x=0, y=TD(x=0, y=None))) [typing fixtures/typing-typeddict.pyi] [case testRecursiveTypedDictMethods] -# flags: --strict-optional from typing import TypedDict class TD(TypedDict, total=False): @@ -787,7 +784,6 @@ reveal_type(std) # N: Revealed type is "TypedDict('__main__.STD', {'val': built [typing fixtures/typing-typeddict.pyi] [case testRecursiveClassLevelAlias] -# flags: --strict-optional from typing import Union, Sequence class A: @@ -897,3 +893,52 @@ Example = NamedTuple("Example", [("rec", List["Example"])]) e: Example reveal_type(e) # N: Revealed type is "Tuple[builtins.list[...], fallback=__main__.Example]" [builtins fixtures/tuple.pyi] + +[case testRecursiveBoundFunctionScopeNoCrash] +from typing import TypeVar, Union, Dict + +def dummy() -> None: + A = Union[str, Dict[str, "A"]] # E: Cannot resolve name "A" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope + T = TypeVar("T", bound=A) + + def bar(x: T) -> T: + pass + reveal_type(bar) # N: Revealed type is "def [T <: Union[builtins.str, builtins.dict[builtins.str, Any]]] (x: T`-1) -> T`-1" +[builtins fixtures/dict.pyi] + +[case testForwardBoundFunctionScopeWorks] +from typing import TypeVar, Dict + +def dummy() -> None: + A = Dict[str, "B"] + B = Dict[str, str] + T = TypeVar("T", bound=A) + + def bar(x: T) -> T: + pass + reveal_type(bar) # N: Revealed type is "def [T <: builtins.dict[builtins.str, builtins.dict[builtins.str, builtins.str]]] (x: T`-1) -> T`-1" +[builtins fixtures/dict.pyi] + +[case testAliasRecursiveUnpackMultiple] +from typing import Tuple, TypeVar, Optional + +T = TypeVar("T") +S = TypeVar("S") + +A = Tuple[T, S, Optional[A[T, S]]] +x: A[int, str] + +*_, last = x +if last is not None: + reveal_type(last) # N: Revealed type is "Tuple[builtins.int, builtins.str, Union[Tuple[builtins.int, builtins.str, Union[..., None]], None]]" +[builtins fixtures/tuple.pyi] + +[case testRecursiveAliasLiteral] +from typing import Tuple +from typing_extensions import Literal + +NotFilter = Tuple[Literal["not"], "NotFilter"] +n: NotFilter +reveal_type(n[1][1][0]) # N: Revealed type is "Literal['not']" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 555cef3641f8..e49a7a0e2e2f 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -208,7 +208,31 @@ class J(A[int]): [builtins fixtures/tuple.pyi] -[case testSelfTypeOverrideCompatibilityTypeVar-xfail] +[case testSelfTypeOverrideCompatibilityGeneric] +from typing import TypeVar, Generic, overload + +T = TypeVar("T", str, int, None) + +class A(Generic[T]): + @overload + def f(self, s: T) -> T: ... + @overload + def f(self: A[str], s: bytes) -> str: ... + def f(self, s: object): ... + +class B(A[int]): + def f(self, s: int) -> int: ... + +class C(A[None]): + def f(self, s: int) -> int: ... # E: Signature of "f" incompatible with supertype "A" \ + # N: Superclass: \ + # N: @overload \ + # N: def f(self, s: None) -> None \ + # N: Subclass: \ + # N: def f(self, s: int) -> int +[builtins fixtures/tuple.pyi] + +[case testSelfTypeOverrideCompatibilityTypeVar] from typing import overload, TypeVar, Union AT = TypeVar("AT", bound="A") @@ -242,6 +266,26 @@ class B(A): def f(*a, **kw): ... [builtins fixtures/dict.pyi] +[case testSelfTypeOverrideCompatibilitySelfTypeVar] +from typing import Any, Generic, Self, TypeVar, overload + +T_co = TypeVar('T_co', covariant=True) + +class Config(Generic[T_co]): + @overload + def get(self, instance: None) -> Self: ... + @overload + def get(self, instance: Any) -> T_co: ... + def get(self, *a, **kw): ... + +class MultiConfig(Config[T_co]): + @overload + def get(self, instance: None) -> Self: ... + @overload + def get(self, instance: Any) -> T_co: ... + def get(self, *a, **kw): ... +[builtins fixtures/dict.pyi] + [case testSelfTypeSuper] from typing import TypeVar, cast @@ -465,6 +509,58 @@ class E: def __init_subclass__(cls) -> None: reveal_type(cls) # N: Revealed type is "Type[__main__.E]" +[case testSelfTypeNew_explicit] +from typing import TypeVar, Type + +T = TypeVar('T', bound='A') +class A: + @staticmethod + def __new__(cls: Type[T]) -> T: + return cls() + + @classmethod + def __init_subclass__(cls: Type[T]) -> None: + pass + +class B: + @staticmethod + def __new__(cls: Type[T]) -> T: # E: The erased type of self "Type[__main__.A]" is not a supertype of its class "Type[__main__.B]" + return cls() + + @classmethod + def __init_subclass__(cls: Type[T]) -> None: # E: The erased type of self "Type[__main__.A]" is not a supertype of its class "Type[__main__.B]" + pass + +class C: + @staticmethod + def __new__(cls: Type[C]) -> C: + return cls() + + @classmethod + def __init_subclass__(cls: Type[C]) -> None: + pass + +class D: + @staticmethod + def __new__(cls: D) -> D: # E: The erased type of self "__main__.D" is not a supertype of its class "Type[__main__.D]" + return cls + + @classmethod + def __init_subclass__(cls: D) -> None: # E: The erased type of self "__main__.D" is not a supertype of its class "Type[__main__.D]" + pass + +class E: + @staticmethod + def __new__(cls) -> E: + reveal_type(cls) # N: Revealed type is "Type[__main__.E]" + return cls() + + @classmethod + def __init_subclass__(cls) -> None: + reveal_type(cls) # N: Revealed type is "Type[__main__.E]" + +[builtins fixtures/classmethod.pyi] + [case testSelfTypePropertyUnion] from typing import Union class A: @@ -801,7 +897,8 @@ class Base(Protocol): class TweakFunc: def func(self: Base) -> int: - return reveal_type(super().func()) # N: Revealed type is "builtins.int" + return reveal_type(super().func()) # E: Call to abstract method "func" of "Base" with trivial body via super() is unsafe \ + # N: Revealed type is "builtins.int" class Good: def func(self) -> int: ... @@ -1173,14 +1270,14 @@ class AClass: ... def foo(x: Type[AClass]) -> None: - reveal_type(x.delete) # N: Revealed type is "Overload(def (id: builtins.int, id2: builtins.int) -> builtins.int, def (id: __main__.AClass, id2: None =) -> builtins.int)" + reveal_type(x.delete) # N: Revealed type is "Overload(def (id: builtins.int, id2: builtins.int) -> Union[builtins.int, None], def (id: __main__.AClass, id2: None =) -> Union[builtins.int, None])" y = x() - reveal_type(y.delete) # N: Revealed type is "Overload(def (id: builtins.int, id2: builtins.int) -> builtins.int, def (id: __main__.AClass, id2: None =) -> builtins.int)" + reveal_type(y.delete) # N: Revealed type is "Overload(def (id: builtins.int, id2: builtins.int) -> Union[builtins.int, None], def (id: __main__.AClass, id2: None =) -> Union[builtins.int, None])" y.delete(10, 20) y.delete(y) def bar(x: AClass) -> None: - reveal_type(x.delete) # N: Revealed type is "Overload(def (id: builtins.int, id2: builtins.int) -> builtins.int, def (id: __main__.AClass, id2: None =) -> builtins.int)" + reveal_type(x.delete) # N: Revealed type is "Overload(def (id: builtins.int, id2: builtins.int) -> Union[builtins.int, None], def (id: __main__.AClass, id2: None =) -> Union[builtins.int, None])" x.delete(10, 20) [builtins fixtures/classmethod.pyi] @@ -1387,7 +1484,7 @@ class C: return self class D(C): ... -reveal_type(C.meth) # N: Revealed type is "def [Self <: __main__.C] (self: Self`0) -> builtins.list[Self`0]" +reveal_type(C.meth) # N: Revealed type is "def [Self <: __main__.C] (self: Self`1) -> builtins.list[Self`1]" C.attr # E: Access to generic instance variables via class is ambiguous reveal_type(D().meth()) # N: Revealed type is "builtins.list[__main__.D]" reveal_type(D().attr) # N: Revealed type is "builtins.list[__main__.D]" @@ -1423,7 +1520,7 @@ from typing import Self, TypeVar, Tuple T = TypeVar("T") class C: def meth(self: T) -> Tuple[Self, T]: ... # E: Method cannot have explicit self annotation and Self type -reveal_type(C().meth()) # N: Revealed type is "Tuple[, __main__.C]" +reveal_type(C().meth()) # N: Revealed type is "Tuple[Never, __main__.C]" [builtins fixtures/property.pyi] [case testTypingSelfProperty] @@ -1461,7 +1558,7 @@ class C: class D(C): ... reveal_type(D.meth()) # N: Revealed type is "__main__.D" -reveal_type(D.bad()) # N: Revealed type is "" +reveal_type(D.bad()) # N: Revealed type is "Never" [builtins fixtures/classmethod.pyi] [case testTypingSelfOverload] @@ -1621,6 +1718,23 @@ class C: return cls() [builtins fixtures/classmethod.pyi] +[case testTypingSelfRedundantAllowed_pep585] +# flags: --python-version 3.9 +from typing import Self + +class C: + def f(self: Self) -> Self: + d: Defer + class Defer: ... + return self + + @classmethod + def g(cls: type[Self]) -> Self: + d: DeferAgain + class DeferAgain: ... + return cls() +[builtins fixtures/classmethod.pyi] + [case testTypingSelfRedundantWarning] # mypy: enable-error-code="redundant-self" @@ -1639,6 +1753,25 @@ class C: return cls() [builtins fixtures/classmethod.pyi] +[case testTypingSelfRedundantWarning_pep585] +# flags: --python-version 3.9 +# mypy: enable-error-code="redundant-self" + +from typing import Self + +class C: + def copy(self: Self) -> Self: # E: Redundant "Self" annotation for the first method argument + d: Defer + class Defer: ... + return self + + @classmethod + def g(cls: type[Self]) -> Self: # E: Redundant "Self" annotation for the first method argument + d: DeferAgain + class DeferAgain: ... + return cls() +[builtins fixtures/classmethod.pyi] + [case testTypingSelfAssertType] from typing import Self, assert_type @@ -1660,7 +1793,7 @@ class C: def bar(self) -> Self: ... def foo(self, x: S) -> Tuple[Self, S]: ... -reveal_type(C.foo) # N: Revealed type is "def [Self <: __main__.C, S] (self: Self`0, x: S`-1) -> Tuple[Self`0, S`-1]" +reveal_type(C.foo) # N: Revealed type is "def [Self <: __main__.C, S] (self: Self`1, x: S`2) -> Tuple[Self`1, S`2]" reveal_type(C().foo(42)) # N: Revealed type is "Tuple[__main__.C, builtins.int]" [builtins fixtures/tuple.pyi] @@ -1770,7 +1903,7 @@ class C: class D(C): ... -reveal_type(D.f) # N: Revealed type is "def [T] (T`-1) -> T`-1" +reveal_type(D.f) # N: Revealed type is "def [T] (T`1) -> T`1" reveal_type(D().f) # N: Revealed type is "def () -> __main__.D" [case testTypingSelfOnSuperTypeVarValues] @@ -1805,3 +1938,136 @@ class C(Generic[T]): reveal_type(self.val) # N: Revealed type is "__main__.A" \ # N: Revealed type is "__main__.B" self.val = x + +[case testNarrowSelfType] +from typing import Self, Union + +class A: ... +class B: + def f1(self, v: Union[Self, A]) -> A: + if isinstance(v, B): + return A() + else: + return v + def f2(self, v: Union[Self, A]) -> A: + if isinstance(v, B): + return A() + else: + return B() # E: Incompatible return value type (got "B", expected "A") + +[builtins fixtures/isinstancelist.pyi] + +[case testAttributeOnSelfAttributeInSubclass] +from typing import List, Self + +class A: + x: Self + xs: List[Self] + +class B(A): + extra: int + + def meth(self) -> None: + reveal_type(self.x) # N: Revealed type is "Self`0" + reveal_type(self.xs[0]) # N: Revealed type is "Self`0" + reveal_type(self.x.extra) # N: Revealed type is "builtins.int" + reveal_type(self.xs[0].extra) # N: Revealed type is "builtins.int" +[builtins fixtures/list.pyi] + +[case testSelfTypesWithParamSpecExtract] +from typing import Any, Callable, Generic, TypeVar +from typing_extensions import ParamSpec + +P = ParamSpec("P") +F = TypeVar("F", bound=Callable[..., Any]) +class Example(Generic[F]): + def __init__(self, fn: F) -> None: + ... + def __call__(self: Example[Callable[P, Any]], *args: P.args, **kwargs: P.kwargs) -> None: + ... + +def test_fn(a: int, b: str) -> None: + ... + +example = Example(test_fn) +example() # E: Missing positional arguments "a", "b" in call to "__call__" of "Example" +example(1, "b") # OK +[builtins fixtures/list.pyi] + +[case testSelfTypesWithParamSpecInfer] +from typing import TypeVar, Protocol, Type, Callable +from typing_extensions import ParamSpec + +R = TypeVar("R", covariant=True) +P = ParamSpec("P") +class AsyncP(Protocol[P]): + def meth(self, *args: P.args, **kwargs: P.kwargs) -> None: + ... + +class Async: + @classmethod + def async_func(cls: Type[AsyncP[P]]) -> Callable[P, int]: + ... + +class Add(Async): + def meth(self, x: int, y: int) -> None: ... + +reveal_type(Add.async_func()) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> builtins.int" +reveal_type(Add().async_func()) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> builtins.int" +[builtins fixtures/classmethod.pyi] + +[case testSelfTypeMethodOnClassObject] +from typing import Self + +class Object: # Needed to mimic object in typeshed + ref: Self + +class Foo: + def foo(self) -> Self: + return self + +class Ben(Object): + MY_MAP = { + "foo": Foo.foo, + } + @classmethod + def doit(cls) -> Foo: + reveal_type(cls.MY_MAP) # N: Revealed type is "builtins.dict[builtins.str, def [Self <: __main__.Foo] (self: Self`4) -> Self`4]" + foo_method = cls.MY_MAP["foo"] + return foo_method(Foo()) +[builtins fixtures/isinstancelist.pyi] + +[case testSelfTypeOnGenericClassObjectNewStyleBound] +from typing import Generic, TypeVar, Self + +T = TypeVar("T") +S = TypeVar("S") +class B(Generic[T, S]): + def copy(self) -> Self: ... + +b: B[int, str] +reveal_type(B.copy(b)) # N: Revealed type is "__main__.B[builtins.int, builtins.str]" + +class C(B[T, S]): ... + +c: C[int, str] +reveal_type(C.copy(c)) # N: Revealed type is "__main__.C[builtins.int, builtins.str]" + +B.copy(42) # E: Value of type variable "Self" of "copy" of "B" cannot be "int" +C.copy(42) # E: Value of type variable "Self" of "copy" of "B" cannot be "int" +[builtins fixtures/tuple.pyi] + +[case testRecursiveSelfTypeCallMethodNoCrash] +from typing import Callable, TypeVar + +T = TypeVar("T") +class Partial: + def __call__(self: Callable[..., T]) -> T: ... + +class Partial2: + def __call__(self: Callable[..., T], x: T) -> T: ... + +p: Partial +reveal_type(p()) # N: Revealed type is "Never" +p2: Partial2 +reveal_type(p2(42)) # N: Revealed type is "builtins.int" diff --git a/test-data/unit/check-serialize.test b/test-data/unit/check-serialize.test index 66d5d879ae68..81da94c0591c 100644 --- a/test-data/unit/check-serialize.test +++ b/test-data/unit/check-serialize.test @@ -740,7 +740,6 @@ main:4: note: Revealed type is "def (x: builtins.int) -> Tuple[builtins.int, fal -- [case testSerializeOptionalType] -# flags: --strict-optional import a [file a.py] import b @@ -1275,6 +1274,7 @@ main:2: error: Too many arguments for "f" main:2: error: Too many arguments for "f" [case testSerializeDummyType] +# flags: --no-strict-optional import a [file a.py] import b diff --git a/test-data/unit/check-singledispatch.test b/test-data/unit/check-singledispatch.test index 8fe049437c57..e63d4c073e86 100644 --- a/test-data/unit/check-singledispatch.test +++ b/test-data/unit/check-singledispatch.test @@ -71,7 +71,7 @@ def g(arg: int) -> None: from functools import singledispatch @singledispatch -def f(arg) -> None: +def f(arg) -> None: pass @f.register(str) @@ -80,20 +80,6 @@ def g(arg: int) -> None: # E: Argument to register "str" is incompatible with ty [builtins fixtures/args.pyi] -[case testDispatchBasedOnTypeAnnotationsRequires37-xfail] -# flags: --python-version 3.6 -# the docs for singledispatch say that register didn't accept type annotations until python 3.7 -from functools import singledispatch - -@singledispatch -def f(arg) -> None: - pass -@f.register -def g(arg: int) -> None: # E: Singledispatch based on type annotations is only supported in Python 3.7 and greater - pass - -[builtins fixtures/args.pyi] - [case testTypePassedAsArgumentToRegister] from functools import singledispatch @@ -180,9 +166,6 @@ def f(arg) -> None: @f.register def g(arg: Mapping) -> None: pass - -[builtins fixtures/args.pyi] -[builtins fixtures/list.pyi] [builtins fixtures/dict.pyi] [case testIncorrectArgumentsInSingledispatchFunctionDefinition] @@ -317,7 +300,7 @@ h('a', 1) # E: Argument 2 to "h" has incompatible type "int"; expected "str" [case testDontCrashWhenRegisteringAfterError] import functools -a = functools.singledispatch('a') # E: Need type annotation for "a" # E: Argument 1 to "singledispatch" has incompatible type "str"; expected "Callable[..., ]" +a = functools.singledispatch('a') # E: Need type annotation for "a" # E: Argument 1 to "singledispatch" has incompatible type "str"; expected "Callable[..., Never]" @a.register(int) def default(val) -> int: diff --git a/test-data/unit/check-slots.test b/test-data/unit/check-slots.test index 8beb0d8bf3f7..b7ce5e596101 100644 --- a/test-data/unit/check-slots.test +++ b/test-data/unit/check-slots.test @@ -26,7 +26,6 @@ class WithVariable: self.a = 1 self.b = 2 self.c = 3 -[builtins fixtures/tuple.pyi] [builtins fixtures/list.pyi] @@ -332,7 +331,6 @@ b.extra = 'extra' main:22: error: Trying to assign name "c" that is not in "__slots__" of type "__main__.B" main:43: error: Trying to assign name "c" that is not in "__slots__" of type "__main__.B" main:47: error: "B" has no attribute "extra" -[builtins fixtures/tuple.pyi] [builtins fixtures/property.pyi] @@ -363,7 +361,6 @@ a.c = custom_obj a.d = custom_obj a.e = custom_obj [out] -[builtins fixtures/tuple.pyi] [builtins fixtures/dict.pyi] @@ -473,7 +470,6 @@ class A: self.a = 1 self.b = 2 self.missing = 3 -[builtins fixtures/tuple.pyi] [builtins fixtures/list.pyi] @@ -486,7 +482,6 @@ class A: self.a = 1 self.b = 2 self.missing = 3 -[builtins fixtures/tuple.pyi] [builtins fixtures/set.pyi] @@ -499,7 +494,6 @@ class A: self.a = 1 self.b = 2 self.missing = 3 -[builtins fixtures/tuple.pyi] [builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index b9551870ddfc..71cc80719779 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -85,7 +85,7 @@ def f() -> Generator[int, None, None]: from typing import Iterator def f() -> Iterator[int]: yield 1 - return "foo" + return "foo" # E: No return value expected [out] @@ -95,10 +95,10 @@ def f() -> Iterator[int]: [case testIfStatement] -a = None # type: A -a2 = None # type: A -a3 = None # type: A -b = None # type: bool +a: A +a2: A +a3: A +b: bool if a: a = b # E: Incompatible types in assignment (expression has type "bool", variable has type "A") elif a2: @@ -124,8 +124,8 @@ class A: pass [case testWhileStatement] -a = None # type: A -b = None # type: bool +a: A +b: bool while a: a = b # Fail else: @@ -142,13 +142,14 @@ main:7: error: Incompatible types in assignment (expression has type "bool", var [case testForStatement] class A: pass -a = None # type: A -b = None # type: object +a: A +b: object for a in [A()]: a = b # E: Incompatible types in assignment (expression has type "object", variable has type "A") else: a = b # E: Incompatible types in assignment (expression has type "object", variable has type "A") [builtins fixtures/list.pyi] + [case testBreakStatement] import typing while None: @@ -205,8 +206,9 @@ for a, b in x: # type: int, int, int # E: Incompatible number of tuple items [case testPlusAssign] - -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C a += b # Fail b += a # Fail c += a # Fail @@ -221,13 +223,14 @@ class B: class C: pass [builtins fixtures/tuple.pyi] [out] -main:3: error: Unsupported operand types for + ("A" and "B") -main:4: error: Incompatible types in assignment (expression has type "C", variable has type "B") -main:5: error: Unsupported left operand type for + ("C") +main:4: error: Unsupported operand types for + ("A" and "B") +main:5: error: Incompatible types in assignment (expression has type "C", variable has type "B") +main:6: error: Unsupported left operand type for + ("C") [case testMinusAssign] - -a, b, c = None, None, None # type: (A, B, C) +a: A +b: B +c: C a -= b # Fail b -= a # Fail c -= a # Fail @@ -242,13 +245,13 @@ class B: class C: pass [builtins fixtures/tuple.pyi] [out] -main:3: error: Unsupported operand types for - ("A" and "B") -main:4: error: Incompatible types in assignment (expression has type "C", variable has type "B") -main:5: error: Unsupported left operand type for - ("C") +main:4: error: Unsupported operand types for - ("A" and "B") +main:5: error: Incompatible types in assignment (expression has type "C", variable has type "B") +main:6: error: Unsupported left operand type for - ("C") [case testMulAssign] - -a, c = None, None # type: (A, C) +a: A +c: C a *= a # Fail c *= a # Fail a *= c @@ -263,7 +266,8 @@ main:3: error: Unsupported operand types for * ("A" and "A") main:4: error: Unsupported left operand type for * ("C") [case testMatMulAssign] -a, c = None, None # type: (A, C) +a: A +c: C a @= a # E: Unsupported operand types for @ ("A" and "A") c @= a # E: Unsupported left operand type for @ ("C") a @= c @@ -275,8 +279,8 @@ class C: pass [builtins fixtures/tuple.pyi] [case testDivAssign] - -a, c = None, None # type: (A, C) +a: A +c: C a /= a # Fail c /= a # Fail a /= c @@ -291,8 +295,8 @@ main:3: error: Unsupported operand types for / ("A" and "A") main:4: error: Unsupported left operand type for / ("C") [case testPowAssign] - -a, c = None, None # type: (A, C) +a: A +c: C a **= a # Fail c **= a # Fail a **= c @@ -307,8 +311,8 @@ main:3: error: Unsupported operand types for ** ("A" and "A") main:4: error: Unsupported left operand type for ** ("C") [case testSubtypesInOperatorAssignment] - -a, b = None, None # type: (A, B) +a: A +b: B b += b b += a a += b @@ -321,8 +325,8 @@ class B(A): pass [out] [case testAdditionalOperatorsInOpAssign] - -a, c = None, None # type: (A, C) +a: A +c: C a &= a # Fail a >>= a # Fail a //= a # Fail @@ -389,9 +393,9 @@ main:2: error: Unsupported left operand type for + ("None") [case testRaiseStatement] -e = None # type: BaseException -f = None # type: MyError -a = None # type: A +e: BaseException +f: MyError +a: A raise a # Fail raise e raise f @@ -405,11 +409,16 @@ main:5: error: Exception must be derived from BaseException class A: pass class MyError(BaseException): pass def f(): pass -raise BaseException -raise MyError -raise A # E: Exception must be derived from BaseException -raise object # E: Exception must be derived from BaseException -raise f # E: Exception must be derived from BaseException +if object(): + raise BaseException +if object(): + raise MyError +if object(): + raise A # E: Exception must be derived from BaseException +if object(): + raise object # E: Exception must be derived from BaseException +if object(): + raise f # E: Exception must be derived from BaseException [builtins fixtures/exception.pyi] [case testRaiseClassObjectCustomInit] @@ -425,23 +434,35 @@ class MyKwError(Exception): class MyErrorWithDefault(Exception): def __init__(self, optional=1) -> None: ... -raise BaseException -raise Exception -raise BaseException(1) -raise Exception(2) -raise MyBaseError(4) -raise MyError(5, 6) -raise MyKwError(kwonly=7) -raise MyErrorWithDefault(8) -raise MyErrorWithDefault -raise MyBaseError # E: Too few arguments for "MyBaseError" -raise MyError # E: Too few arguments for "MyError" -raise MyKwError # E: Missing named argument "kwonly" for "MyKwError" +if object(): + raise BaseException +if object(): + raise Exception +if object(): + raise BaseException(1) +if object(): + raise Exception(2) +if object(): + raise MyBaseError(4) +if object(): + raise MyError(5, 6) +if object(): + raise MyKwError(kwonly=7) +if object(): + raise MyErrorWithDefault(8) +if object(): + raise MyErrorWithDefault +if object(): + raise MyBaseError # E: Too few arguments for "MyBaseError" +if object(): + raise MyError # E: Too few arguments for "MyError" +if object(): + raise MyKwError # E: Missing named argument "kwonly" for "MyKwError" [builtins fixtures/exception.pyi] [case testRaiseExceptionType] import typing -x = None # type: typing.Type[BaseException] +x: typing.Type[BaseException] raise x [builtins fixtures/exception.pyi] @@ -453,26 +474,30 @@ raise x # E: Exception must be derived from BaseException [case testRaiseUnion] import typing -x = None # type: typing.Union[BaseException, typing.Type[BaseException]] +x: typing.Union[BaseException, typing.Type[BaseException]] raise x [builtins fixtures/exception.pyi] [case testRaiseNonExceptionUnionFails] import typing -x = None # type: typing.Union[BaseException, int] +x: typing.Union[BaseException, int] raise x # E: Exception must be derived from BaseException [builtins fixtures/exception.pyi] [case testRaiseFromStatement] -e = None # type: BaseException -f = None # type: MyError -a = None # type: A -x = None # type: BaseException +e: BaseException +f: MyError +a: A +x: BaseException del x -raise e from a # E: Exception must be derived from BaseException -raise e from e -raise e from f -raise e from x # E: Trying to read deleted variable "x" +if object(): + raise e from a # E: Exception must be derived from BaseException +if object(): + raise e from e +if object(): + raise e from f +if object(): + raise e from x # E: Trying to read deleted variable "x" class A: pass class MyError(BaseException): pass [builtins fixtures/exception.pyi] @@ -482,11 +507,16 @@ import typing class A: pass class MyError(BaseException): pass def f(): pass -raise BaseException from BaseException -raise BaseException from MyError -raise BaseException from A # E: Exception must be derived from BaseException -raise BaseException from object # E: Exception must be derived from BaseException -raise BaseException from f # E: Exception must be derived from BaseException +if object(): + raise BaseException from BaseException +if object(): + raise BaseException from MyError +if object(): + raise BaseException from A # E: Exception must be derived from BaseException +if object(): + raise BaseException from object # E: Exception must be derived from BaseException +if object(): + raise BaseException from f # E: Exception must be derived from BaseException [builtins fixtures/exception.pyi] [case testTryFinallyStatement] @@ -505,27 +535,30 @@ main:5: error: Incompatible types in assignment (expression has type "object", v try: pass except BaseException as e: - a, o = None, None # type: (BaseException, object) + a: BaseException + o: object e = a e = o # Fail class A: pass class B: pass [builtins fixtures/exception.pyi] [out] -main:7: error: Incompatible types in assignment (expression has type "object", variable has type "BaseException") +main:8: error: Incompatible types in assignment (expression has type "object", variable has type "BaseException") [case testTypeErrorInBlock] class A: pass class B: pass -while object: - x = None # type: A +while int(): + x: A if int(): x = object() # E: Incompatible types in assignment (expression has type "object", variable has type "A") x = B() # E: Incompatible types in assignment (expression has type "B", variable has type "A") + [case testTypeErrorInvolvingBaseException] class A: pass -x, a = None, None # type: (BaseException, A) +x: BaseException +a: A if int(): a = BaseException() # E: Incompatible types in assignment (expression has type "BaseException", variable has type "A") if int(): @@ -659,9 +692,9 @@ class E2(E1): pass try: pass except (E1, E2): pass -except (E1, object): pass # E: Exception type must be derived from BaseException -except (object, E2): pass # E: Exception type must be derived from BaseException -except (E1, (E2,)): pass # E: Exception type must be derived from BaseException +except (E1, object): pass # E: Exception type must be derived from BaseException (or be a tuple of exception classes) +except (object, E2): pass # E: Exception type must be derived from BaseException (or be a tuple of exception classes) +except (E1, (E2,)): pass # E: Exception type must be derived from BaseException (or be a tuple of exception classes) except (E1, E2): pass except ((E1, E2)): pass @@ -690,7 +723,7 @@ except (E1, E2) as e1: except (E2, E1) as e2: a = e2 # type: E1 b = e2 # type: E2 # E: Incompatible types in assignment (expression has type "E1", variable has type "E2") -except (E1, E2, int) as e3: # E: Exception type must be derived from BaseException +except (E1, E2, int) as e3: # E: Exception type must be derived from BaseException (or be a tuple of exception classes) pass [builtins fixtures/exception.pyi] @@ -750,13 +783,13 @@ def nested_union(exc: Union[Type[E1], Union[Type[E2], Type[E3]]]) -> None: def error_in_union(exc: Union[Type[E1], int]) -> None: try: pass - except exc as e: # E: Exception type must be derived from BaseException + except exc as e: # E: Exception type must be derived from BaseException (or be a tuple of exception classes) pass def error_in_variadic(exc: Tuple[int, ...]) -> None: try: pass - except exc as e: # E: Exception type must be derived from BaseException + except exc as e: # E: Exception type must be derived from BaseException (or be a tuple of exception classes) pass [builtins fixtures/tuple.pyi] @@ -784,15 +817,15 @@ except E1 as e1: reveal_type(e1) # N: Revealed type is "Any" except E2 as e2: reveal_type(e2) # N: Revealed type is "__main__.E2" -except NotBaseDerived as e3: # E: Exception type must be derived from BaseException +except NotBaseDerived as e3: # E: Exception type must be derived from BaseException (or be a tuple of exception classes) pass -except (NotBaseDerived, E1) as e4: # E: Exception type must be derived from BaseException +except (NotBaseDerived, E1) as e4: # E: Exception type must be derived from BaseException (or be a tuple of exception classes) pass -except (NotBaseDerived, E2) as e5: # E: Exception type must be derived from BaseException +except (NotBaseDerived, E2) as e5: # E: Exception type must be derived from BaseException (or be a tuple of exception classes) pass -except (NotBaseDerived, E1, E2) as e6: # E: Exception type must be derived from BaseException +except (NotBaseDerived, E1, E2) as e6: # E: Exception type must be derived from BaseException (or be a tuple of exception classes) pass -except (E1, E2, NotBaseDerived) as e6: # E: Exception type must be derived from BaseException +except (E1, E2, NotBaseDerived) as e6: # E: Exception type must be derived from BaseException (or be a tuple of exception classes) pass [builtins fixtures/exception.pyi] @@ -953,8 +986,8 @@ except a as b: import typing def exc() -> BaseException: pass try: pass -except exc as e: pass # E: Exception type must be derived from BaseException -except BaseException() as b: pass # E: Exception type must be derived from BaseException +except exc as e: pass # E: Exception type must be derived from BaseException (or be a tuple of exception classes) +except BaseException() as b: pass # E: Exception type must be derived from BaseException (or be a tuple of exception classes) [builtins fixtures/exception.pyi] [case testTupleValueAsExceptionType] @@ -980,7 +1013,7 @@ except exs2 as e2: exs3 = (E1, (E1_1, (E1_2,))) try: pass -except exs3 as e3: pass # E: Exception type must be derived from BaseException +except exs3 as e3: pass # E: Exception type must be derived from BaseException (or be a tuple of exception classes) [builtins fixtures/exception.pyi] [case testInvalidTupleValueAsExceptionType] @@ -991,7 +1024,7 @@ class E2(E1): pass exs1 = (E1, E2, int) try: pass -except exs1 as e: pass # E: Exception type must be derived from BaseException +except exs1 as e: pass # E: Exception type must be derived from BaseException (or be a tuple of exception classes) [builtins fixtures/exception.pyi] [case testOverloadedExceptionType] @@ -1034,7 +1067,7 @@ def h(e: Type[int]): [builtins fixtures/exception.pyi] [out] main:9: note: Revealed type is "builtins.BaseException" -main:12: error: Exception type must be derived from BaseException +main:12: error: Exception type must be derived from BaseException (or be a tuple of exception classes) -- Del statement @@ -1042,7 +1075,8 @@ main:12: error: Exception type must be derived from BaseException [case testDelStmtWithIndex] -a, b = None, None # type: (A, B) +a: A +b: B del b[a] del b[b] # E: Argument 1 to "__delitem__" of "B" has incompatible type "B"; expected "A" del a[a] # E: "A" has no attribute "__delitem__" @@ -1965,6 +1999,7 @@ def f() -> None: [out] [case testChainedAssignmentWithType] +# flags: --no-strict-optional x = y = None # type: int if int(): x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") @@ -1982,7 +2017,8 @@ if int(): [case testAssignListToStarExpr] from typing import List -bs, cs = None, None # type: List[A], List[B] +bs: List[A] +cs: List[B] if int(): *bs, b = bs if int(): @@ -2195,8 +2231,53 @@ class B: pass def foo(x: int) -> Union[Generator[A, None, None], Generator[B, None, None]]: yield x # E: Incompatible types in "yield" (actual type "int", expected type "Union[A, B]") +[case testYieldFromUnionOfGenerators] +from typing import Generator, Union + +class T: pass + +def foo(arg: Union[Generator[int, None, T], Generator[str, None, T]]) -> Generator[Union[int, str], None, T]: + return (yield from arg) + +[case testYieldFromInvalidUnionReturn] +from typing import Generator, Union + +class A: pass +class B: pass + +def foo(arg: Union[A, B]) -> Generator[Union[int, str], None, A]: + return (yield from arg) # E: "yield from" can't be applied to "Union[A, B]" + +[case testYieldFromUnionOfGeneratorWithIterableStr] +from typing import Generator, Union, Iterable, Optional + +def foo(arg: Union[Generator[int, None, bytes], Iterable[str]]) -> Generator[Union[int, str], None, Optional[bytes]]: + return (yield from arg) + +def bar(arg: Generator[str, None, str]) -> Generator[str, None, str]: + return foo(arg) # E: Incompatible return value type (got "Generator[Union[int, str], None, Optional[bytes]]", expected "Generator[str, None, str]") + +def launder(arg: Iterable[str]) -> Generator[Union[int, str], None, Optional[bytes]]: + return foo(arg) + +def baz(arg: Generator[str, None, str]) -> Generator[Union[int, str], None, Optional[bytes]]: + # this is unsound, the Generator return type will actually be str + return launder(arg) +[builtins fixtures/tuple.pyi] + +[case testYieldIteratorReturn] +from typing import Iterator + +def get_strings(foo: bool) -> Iterator[str]: + if foo: + return ["foo1", "foo2"] # E: No return value expected + else: + yield "bar1" + yield "bar2" +[builtins fixtures/tuple.pyi] + [case testNoCrashOnStarRightHandSide] -x = *(1, 2, 3) # E: Can use starred expression only as assignment target +x = *(1, 2, 3) # E: can't use starred expression here [builtins fixtures/tuple.pyi] @@ -2212,3 +2293,17 @@ main:1: error: Module "typing" has no attribute "_FutureFeatureFixture" main:1: note: Use `from typing_extensions import _FutureFeatureFixture` instead main:1: note: See https://mypy.readthedocs.io/en/stable/runtime_troubles.html#using-new-additions-to-the-typing-module [builtins fixtures/tuple.pyi] + +[case testNoCrashOnBreakOutsideLoopFunction] +def foo(): + for x in [1, 2]: + def inner(): + break # E: "break" outside loop +[builtins fixtures/list.pyi] + +[case testNoCrashOnBreakOutsideLoopClass] +class Outer: + for x in [1, 2]: + class Inner: + break # E: "break" outside loop +[builtins fixtures/list.pyi] diff --git a/test-data/unit/check-super.test b/test-data/unit/check-super.test index b3379e505be7..8816322a270a 100644 --- a/test-data/unit/check-super.test +++ b/test-data/unit/check-super.test @@ -11,7 +11,8 @@ class B: def f(self) -> 'B': pass class A(B): def f(self) -> 'A': - a, b = None, None # type: (A, B) + a: A + b: B if int(): a = super().f() # E: Incompatible types in assignment (expression has type "B", variable has type "A") a = super().g() # E: "g" undefined in superclass @@ -26,7 +27,8 @@ class B: def f(self, y: 'A') -> None: pass class A(B): def f(self, y: Any) -> None: - a, b = None, None # type: (A, B) + a: A + b: B super().f(b) # E: Argument 1 to "f" of "B" has incompatible type "B"; expected "A" super().f(a) self.f(b) @@ -35,6 +37,7 @@ class A(B): [out] [case testAccessingSuperInit] +# flags: --no-strict-optional import typing class B: def __init__(self, x: A) -> None: pass @@ -90,7 +93,7 @@ class B(A): def __new__(cls, x: int, y: str = '') -> 'B': super().__new__(cls, 1) super().__new__(cls, 1, '') # E: Too many arguments for "__new__" of "A" - return None + return cls(1) B('') # E: Argument 1 to "B" has incompatible type "str"; expected "int" B(1) B(1, 'x') @@ -277,8 +280,12 @@ class B(A): [case testSuperOutsideMethodNoCrash] -class C: - a = super().whatever # E: super() outside of a method is not supported +class A: + x = 1 +class B(A): pass +class C(B): + b = super(B, B).x + a = super().whatever # E: "super()" outside of a method is not supported [case testSuperWithObjectClassAsFirstArgument] class A: @@ -363,13 +370,22 @@ class C(B): [case testSuperInMethodWithNoArguments] class A: def f(self) -> None: pass + @staticmethod + def st() -> int: + return 1 class B(A): def g() -> None: # E: Method must have at least one argument. Did you forget the "self" argument? - super().f() # E: super() requires one or more positional arguments in enclosing function + super().f() # E: "super()" requires one or two positional arguments in enclosing function def h(self) -> None: def a() -> None: - super().f() # E: super() requires one or more positional arguments in enclosing function + super().f() # E: "super()" requires one or two positional arguments in enclosing function + @staticmethod + def st() -> int: + reveal_type(super(B, B).st()) # N: Revealed type is "builtins.int" + super().st() # E: "super()" requires one or two positional arguments in enclosing function + return 2 +[builtins fixtures/staticmethod.pyi] [case testSuperWithUnsupportedTypeObject] from typing import Type diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 266bfbf97888..ad4893c2890a 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -4,11 +4,11 @@ [case testTupleAssignmentWithTupleTypes] from typing import Tuple -t1 = None # type: Tuple[A] -t2 = None # type: Tuple[B] -t3 = None # type: Tuple[A, A] -t4 = None # type: Tuple[A, B] -t5 = None # type: Tuple[B, A] +t1: Tuple[A] +t2: Tuple[B] +t3: Tuple[A, A] +t4: Tuple[A, B] +t5: Tuple[B, A] if int(): t1 = t2 # E: Incompatible types in assignment (expression has type "Tuple[B]", variable has type "Tuple[A]") @@ -39,9 +39,9 @@ class B: pass [case testTupleSubtyping] from typing import Tuple -t1 = None # type: Tuple[A, A] -t2 = None # type: Tuple[A, B] -t3 = None # type: Tuple[B, A] +t1: Tuple[A, A] +t2: Tuple[A, B] +t3: Tuple[B, A] if int(): t2 = t1 # E: Incompatible types in assignment (expression has type "Tuple[A, A]", variable has type "Tuple[A, B]") @@ -57,6 +57,7 @@ class B(A): pass [builtins fixtures/tuple.pyi] [case testTupleCompatibilityWithOtherTypes] +# flags: --no-strict-optional from typing import Tuple a, o = None, None # type: (A, object) t = None # type: Tuple[A, A] @@ -80,8 +81,8 @@ class A: pass [case testNestedTupleTypes] from typing import Tuple -t1 = None # type: Tuple[A, Tuple[A, A]] -t2 = None # type: Tuple[B, Tuple[B, B]] +t1: Tuple[A, Tuple[A, A]] +t2: Tuple[B, Tuple[B, B]] if int(): t2 = t1 # E: Incompatible types in assignment (expression has type "Tuple[A, Tuple[A, A]]", variable has type "Tuple[B, Tuple[B, B]]") @@ -94,8 +95,8 @@ class B(A): pass [case testNestedTupleTypes2] from typing import Tuple -t1 = None # type: Tuple[A, Tuple[A, A]] -t2 = None # type: Tuple[B, Tuple[B, B]] +t1: Tuple[A, Tuple[A, A]] +t2: Tuple[B, Tuple[B, B]] if int(): t2 = t1 # E: Incompatible types in assignment (expression has type "Tuple[A, Tuple[A, A]]", variable has type "Tuple[B, Tuple[B, B]]") @@ -106,20 +107,149 @@ class A: pass class B(A): pass [builtins fixtures/tuple.pyi] -[case testSubtypingWithNamedTupleType] -from typing import Tuple -t1 = None # type: Tuple[A, A] -t2 = None # type: tuple - -if int(): - t1 = t2 # E: Incompatible types in assignment (expression has type "Tuple[Any, ...]", variable has type "Tuple[A, A]") -if int(): - t2 = t1 +[case testSubtypingWithTupleType] +from __future__ import annotations +from typing import Any, Tuple + +tuple_aa: tuple[A, A] +Tuple_aa: Tuple[A, A] + +tuple_obj: tuple[object, ...] +Tuple_obj: Tuple[object, ...] + +tuple_obj_one: tuple[object] +Tuple_obj_one: Tuple[object] + +tuple_obj_two: tuple[object, object] +Tuple_obj_two: Tuple[object, object] + +tuple_any_implicit: tuple +Tuple_any_implicit: Tuple + +tuple_any: tuple[Any, ...] +Tuple_any: Tuple[Any, ...] + +tuple_any_one: tuple[Any] +Tuple_any_one: Tuple[Any] + +tuple_any_two: tuple[Any, Any] +Tuple_any_two: Tuple[Any, Any] + +def takes_tuple_aa(t: tuple[A, A]): ... + +takes_tuple_aa(tuple_aa) +takes_tuple_aa(Tuple_aa) +takes_tuple_aa(tuple_obj) # E: Argument 1 to "takes_tuple_aa" has incompatible type "Tuple[object, ...]"; expected "Tuple[A, A]" +takes_tuple_aa(Tuple_obj) # E: Argument 1 to "takes_tuple_aa" has incompatible type "Tuple[object, ...]"; expected "Tuple[A, A]" +takes_tuple_aa(tuple_obj_one) # E: Argument 1 to "takes_tuple_aa" has incompatible type "Tuple[object]"; expected "Tuple[A, A]" +takes_tuple_aa(Tuple_obj_one) # E: Argument 1 to "takes_tuple_aa" has incompatible type "Tuple[object]"; expected "Tuple[A, A]" +takes_tuple_aa(tuple_obj_two) # E: Argument 1 to "takes_tuple_aa" has incompatible type "Tuple[object, object]"; expected "Tuple[A, A]" +takes_tuple_aa(Tuple_obj_two) # E: Argument 1 to "takes_tuple_aa" has incompatible type "Tuple[object, object]"; expected "Tuple[A, A]" +takes_tuple_aa(tuple_any_implicit) +takes_tuple_aa(Tuple_any_implicit) +takes_tuple_aa(tuple_any) +takes_tuple_aa(Tuple_any) +takes_tuple_aa(tuple_any_one) # E: Argument 1 to "takes_tuple_aa" has incompatible type "Tuple[Any]"; expected "Tuple[A, A]" +takes_tuple_aa(Tuple_any_one) # E: Argument 1 to "takes_tuple_aa" has incompatible type "Tuple[Any]"; expected "Tuple[A, A]" +takes_tuple_aa(tuple_any_two) +takes_tuple_aa(Tuple_any_two) + +def takes_tuple_any_implicit(t: tuple): ... + +takes_tuple_any_implicit(tuple_aa) +takes_tuple_any_implicit(Tuple_aa) +takes_tuple_any_implicit(tuple_obj) +takes_tuple_any_implicit(Tuple_obj) +takes_tuple_any_implicit(tuple_obj_one) +takes_tuple_any_implicit(Tuple_obj_one) +takes_tuple_any_implicit(tuple_obj_two) +takes_tuple_any_implicit(Tuple_obj_two) +takes_tuple_any_implicit(tuple_any_implicit) +takes_tuple_any_implicit(Tuple_any_implicit) +takes_tuple_any_implicit(tuple_any) +takes_tuple_any_implicit(Tuple_any) +takes_tuple_any_implicit(tuple_any_one) +takes_tuple_any_implicit(Tuple_any_one) +takes_tuple_any_implicit(tuple_any_two) +takes_tuple_any_implicit(Tuple_any_two) + +def takes_tuple_any_one(t: tuple[Any]): ... + +takes_tuple_any_one(tuple_aa) # E: Argument 1 to "takes_tuple_any_one" has incompatible type "Tuple[A, A]"; expected "Tuple[Any]" +takes_tuple_any_one(Tuple_aa) # E: Argument 1 to "takes_tuple_any_one" has incompatible type "Tuple[A, A]"; expected "Tuple[Any]" +takes_tuple_any_one(tuple_obj) # E: Argument 1 to "takes_tuple_any_one" has incompatible type "Tuple[object, ...]"; expected "Tuple[Any]" +takes_tuple_any_one(Tuple_obj) # E: Argument 1 to "takes_tuple_any_one" has incompatible type "Tuple[object, ...]"; expected "Tuple[Any]" +takes_tuple_any_one(tuple_obj_one) +takes_tuple_any_one(Tuple_obj_one) +takes_tuple_any_one(tuple_obj_two) # E: Argument 1 to "takes_tuple_any_one" has incompatible type "Tuple[object, object]"; expected "Tuple[Any]" +takes_tuple_any_one(Tuple_obj_two) # E: Argument 1 to "takes_tuple_any_one" has incompatible type "Tuple[object, object]"; expected "Tuple[Any]" +takes_tuple_any_one(tuple_any_implicit) +takes_tuple_any_one(Tuple_any_implicit) +takes_tuple_any_one(tuple_any) +takes_tuple_any_one(Tuple_any) +takes_tuple_any_one(tuple_any_one) +takes_tuple_any_one(Tuple_any_one) +takes_tuple_any_one(tuple_any_two) # E: Argument 1 to "takes_tuple_any_one" has incompatible type "Tuple[Any, Any]"; expected "Tuple[Any]" +takes_tuple_any_one(Tuple_any_two) # E: Argument 1 to "takes_tuple_any_one" has incompatible type "Tuple[Any, Any]"; expected "Tuple[Any]" class A: pass [builtins fixtures/tuple.pyi] +[case testSubtypingWithTupleTypeSubclass] +from __future__ import annotations +from typing import Any, Tuple + +class A: ... + +inst_tuple_aa: Tuple[A, A] + +class tuple_aa_subclass(Tuple[A, A]): ... +inst_tuple_aa_subclass: tuple_aa_subclass + +class tuple_any_subclass(Tuple[Any, ...]): ... +inst_tuple_any_subclass: tuple_any_subclass + +class tuple_any_one_subclass(Tuple[Any]): ... +inst_tuple_any_one_subclass: tuple_any_one_subclass + +class tuple_any_two_subclass(Tuple[Any, Any]): ... +inst_tuple_any_two_subclass: tuple_any_two_subclass + +class tuple_obj_subclass(Tuple[object, ...]): ... +inst_tuple_obj_subclass: tuple_obj_subclass + +class tuple_obj_one_subclass(Tuple[object]): ... +inst_tuple_obj_one_subclass: tuple_obj_one_subclass + +class tuple_obj_two_subclass(Tuple[object, object]): ... +inst_tuple_obj_two_subclass: tuple_obj_two_subclass + +def takes_tuple_aa(t: Tuple[A, A]): ... + +takes_tuple_aa(inst_tuple_aa) +takes_tuple_aa(inst_tuple_aa_subclass) +takes_tuple_aa(inst_tuple_any_subclass) +takes_tuple_aa(inst_tuple_any_one_subclass) # E: Argument 1 to "takes_tuple_aa" has incompatible type "tuple_any_one_subclass"; expected "Tuple[A, A]" +takes_tuple_aa(inst_tuple_any_two_subclass) +takes_tuple_aa(inst_tuple_obj_subclass) # E: Argument 1 to "takes_tuple_aa" has incompatible type "tuple_obj_subclass"; expected "Tuple[A, A]" +takes_tuple_aa(inst_tuple_obj_one_subclass) # E: Argument 1 to "takes_tuple_aa" has incompatible type "tuple_obj_one_subclass"; expected "Tuple[A, A]" +takes_tuple_aa(inst_tuple_obj_two_subclass) # E: Argument 1 to "takes_tuple_aa" has incompatible type "tuple_obj_two_subclass"; expected "Tuple[A, A]" + +def takes_tuple_aa_subclass(t: tuple_aa_subclass): ... + +takes_tuple_aa_subclass(inst_tuple_aa) # E: Argument 1 to "takes_tuple_aa_subclass" has incompatible type "Tuple[A, A]"; expected "tuple_aa_subclass" +takes_tuple_aa_subclass(inst_tuple_aa_subclass) +takes_tuple_aa_subclass(inst_tuple_any_subclass) # E: Argument 1 to "takes_tuple_aa_subclass" has incompatible type "tuple_any_subclass"; expected "tuple_aa_subclass" +takes_tuple_aa_subclass(inst_tuple_any_one_subclass) # E: Argument 1 to "takes_tuple_aa_subclass" has incompatible type "tuple_any_one_subclass"; expected "tuple_aa_subclass" +takes_tuple_aa_subclass(inst_tuple_any_two_subclass) # E: Argument 1 to "takes_tuple_aa_subclass" has incompatible type "tuple_any_two_subclass"; expected "tuple_aa_subclass" +takes_tuple_aa_subclass(inst_tuple_obj_subclass) # E: Argument 1 to "takes_tuple_aa_subclass" has incompatible type "tuple_obj_subclass"; expected "tuple_aa_subclass" +takes_tuple_aa_subclass(inst_tuple_obj_one_subclass) # E: Argument 1 to "takes_tuple_aa_subclass" has incompatible type "tuple_obj_one_subclass"; expected "tuple_aa_subclass" +takes_tuple_aa_subclass(inst_tuple_obj_two_subclass) # E: Argument 1 to "takes_tuple_aa_subclass" has incompatible type "tuple_obj_two_subclass"; expected "tuple_aa_subclass" + +[builtins fixtures/tuple.pyi] + [case testTupleInitializationWithNone] +# flags: --no-strict-optional from typing import Tuple t = None # type: Tuple[A, A] t = None @@ -132,6 +262,7 @@ class A: pass [case testTupleExpressions] +# flags: --no-strict-optional from typing import Tuple t1 = None # type: tuple t2 = None # type: Tuple[A] @@ -140,7 +271,7 @@ t3 = None # type: Tuple[A, B] a, b, c = None, None, None # type: (A, B, C) if int(): - t2 = () # E: Incompatible types in assignment (expression has type "Tuple[]", variable has type "Tuple[A]") + t2 = () # E: Incompatible types in assignment (expression has type "Tuple[()]", variable has type "Tuple[A]") if int(): t2 = (a, a) # E: Incompatible types in assignment (expression has type "Tuple[A, A]", variable has type "Tuple[A]") if int(): @@ -166,8 +297,8 @@ class C(B): pass import typing def f() -> None: pass -(None, f()) # E: "f" does not return a value -(f(), None) # E: "f" does not return a value +(None, f()) # E: "f" does not return a value (it only ever returns None) +(f(), None) # E: "f" does not return a value (it only ever returns None) [builtins fixtures/tuple.pyi] @@ -177,12 +308,13 @@ def f() -> None: pass [case testIndexingTuples] from typing import Tuple -t1 = None # type: Tuple[A, B] -t2 = None # type: Tuple[A] -t3 = None # type: Tuple[A, B, C, D, E] -a, b = None, None # type: (A, B) -x = None # type: Tuple[A, B, C] -y = None # type: Tuple[A, C, E] +t1: Tuple[A, B] +t2: Tuple[A] +t3: Tuple[A, B, C, D, E] +a: A +b: B +x: Tuple[A, B, C] +y: Tuple[A, C, E] n = 0 if int(): @@ -205,10 +337,12 @@ if int(): b = t1[-1] if int(): a = t1[(0)] +if int(): + b = t1[+1] if int(): x = t3[0:3] # type (A, B, C) if int(): - y = t3[0:5:2] # type (A, C, E) + y = t3[0:+5:2] # type (A, C, E) if int(): x = t3[:-2] # type (A, B, C) @@ -221,9 +355,10 @@ class E: pass [case testIndexingTuplesWithNegativeIntegers] from typing import Tuple -t1 = None # type: Tuple[A, B] -t2 = None # type: Tuple[A] -a, b = None, None # type: A, B +t1: Tuple[A, B] +t2: Tuple[A] +a: A +b: B if int(): a = t1[-1] # E: Incompatible types in assignment (expression has type "B", variable has type "A") @@ -251,7 +386,7 @@ from typing import Tuple class A: pass class B: pass -t = None # type: Tuple[A, B] +t: Tuple[A, B] n = 0 t[0] = A() # E: Unsupported target for indexed assignment ("Tuple[A, B]") @@ -265,6 +400,7 @@ t[n] = A() # E: Unsupported target for indexed assignment ("Tuple[A, B]") [case testMultipleAssignmentWithTuples] +# flags: --no-strict-optional from typing import Tuple t1 = None # type: Tuple[A, B] t2 = None # type: Tuple[A, B, A] @@ -291,6 +427,7 @@ class B: pass [builtins fixtures/tuple.pyi] [case testMultipleAssignmentWithSquareBracketTuples] +# flags: --no-strict-optional from typing import Tuple def avoid_confusing_test_parser() -> None: @@ -322,8 +459,8 @@ class B: pass [case testMultipleAssignmentWithInvalidNumberOfValues] from typing import Tuple -t1 = None # type: Tuple[A, A, A] -a = None # type: A +t1: Tuple[A, A, A] +a: A a, a = t1 # E: Too many values to unpack (2 expected, 3 provided) a, a, a, a = t1 # E: Need more than 3 values to unpack (4 expected) @@ -334,8 +471,8 @@ class A: pass [builtins fixtures/tuple.pyi] [case testMultipleAssignmentWithTupleExpressionRvalue] - -a, b = None, None # type: (A, B) +a: A +b: B if int(): a, b = a, a # E: Incompatible types in assignment (expression has type "A", variable has type "B") @@ -354,7 +491,8 @@ class B: pass [builtins fixtures/tuple.pyi] [case testSubtypingInMultipleAssignment] -a, b = None, None # type: (A, B) +a: A +b: B if int(): b, b = a, b # E: Incompatible types in assignment (expression has type "A", variable has type "B") @@ -371,7 +509,7 @@ class B(A): pass [builtins fixtures/tuple.pyi] [case testInitializationWithMultipleValues] - +# flags: --no-strict-optional a, b = None, None # type: (A, B) a1, b1 = a, a # type: (A, B) # E: Incompatible types in assignment (expression has type "A", variable has type "B") @@ -387,8 +525,8 @@ class B: pass [builtins fixtures/tuple.pyi] [case testMultipleAssignmentWithNonTupleRvalue] - -a, b = None, None # type: (A, B) +a: A +b: B def f(): pass a, b = None # E: "None" object is not iterable @@ -400,9 +538,10 @@ class B: pass [builtins fixtures/tuple.pyi] [case testMultipleAssignmentWithIndexedLvalues] - -a, b = None, None # type: (A, B) -aa, bb = None, None # type: (AA, BB) +a: A +b: B +aa: AA +bb: BB a[a], b[b] = a, bb # E: Incompatible types in assignment (expression has type "A", target has type "AA") a[a], b[b] = aa, b # E: Incompatible types in assignment (expression has type "B", target has type "BB") @@ -420,6 +559,7 @@ class BB: pass [builtins fixtures/tuple.pyi] [case testMultipleDeclarationWithParentheses] +# flags: --no-strict-optional (a, b) = (None, None) # type: int, str if int(): a = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") @@ -430,8 +570,8 @@ if int(): [builtins fixtures/tuple.pyi] [case testMultipleAssignmentWithExtraParentheses] - -a, b = None, None # type: (A, B) +a: A +b: B if int(): (a, b) = (a, a) # E: Incompatible types in assignment (expression has type "A", variable has type "B") @@ -458,6 +598,7 @@ class B: pass [builtins fixtures/tuple.pyi] [case testMultipleAssignmentUsingSingleTupleType] +# flags: --no-strict-optional from typing import Tuple a, b = None, None # type: Tuple[int, str] if int(): @@ -490,6 +631,7 @@ aa, bb, *cc = t # E: Need type annotation for "cc" (hint: "cc: List[] = . [builtins fixtures/list.pyi] [case testAssignmentToStarAnnotation] +# flags: --no-strict-optional from typing import List li, lo = None, None # type: List[int], List[object] a, b, *c = 1, 2 # type: int, int, List[int] @@ -501,7 +643,7 @@ if int(): [case testAssignmentToStarCount1] from typing import List -ca = None # type: List[int] +ca: List[int] c = [1] if int(): a, b, *c = 1, # E: Need more than 1 value to unpack (2 expected) @@ -515,7 +657,7 @@ if int(): [case testAssignmentToStarCount2] from typing import List -ca = None # type: List[int] +ca: List[int] t1 = 1, t2 = 1, 2 t3 = 1, 2, 3 @@ -541,7 +683,7 @@ c = a c = q [case testAssignmentToComplexStar] from typing import List -li = None # type: List[int] +li: List[int] if int(): a, *(li) = 1, a, *(b, c) = 1, 2 # E: Need more than 1 value to unpack (2 expected) @@ -553,9 +695,9 @@ if int(): [case testAssignmentToStarFromTupleType] from typing import List, Tuple -li = None # type: List[int] -la = None # type: List[A] -ta = None # type: Tuple[A, A, A] +li: List[int] +la: List[A] +ta: Tuple[A, A, A] if int(): a, *la = ta if int(): @@ -573,8 +715,8 @@ class A: pass [case testAssignmentToStarFromTupleInference] from typing import List class A: pass -li = None # type: List[int] -la = None # type: List[A] +li: List[int] +la: List[A] a, *l = A(), A() if int(): l = li # E: Incompatible types in assignment (expression has type "List[int]", variable has type "List[A]") @@ -588,8 +730,8 @@ from typing import List class A: pass -li = None # type: List[int] -la = None # type: List[A] +li: List[int] +la: List[A] a, *l = [A(), A()] if int(): l = li # E: Incompatible types in assignment (expression has type "List[int]", variable has type "List[A]") @@ -600,9 +742,9 @@ if int(): [case testAssignmentToStarFromTupleTypeInference] from typing import List, Tuple -li = None # type: List[int] -la = None # type: List[A] -ta = None # type: Tuple[A, A, A] +li: List[int] +la: List[A] +ta: Tuple[A, A, A] a, *l = ta if int(): l = li # E: Incompatible types in assignment (expression has type "List[int]", variable has type "List[A]") @@ -615,8 +757,8 @@ class A: pass [case testAssignmentToStarFromListTypeInference] from typing import List -li = None # type: List[int] -la = None # type: List[A] +li: List[int] +la: List[A] a, *l = la if int(): l = li # E: Incompatible types in assignment (expression has type "List[int]", variable has type "List[A]") @@ -656,9 +798,12 @@ reveal_type(e2) # N: Revealed type is "builtins.list[builtins.int]" [case testNestedTupleAssignment1] - -a1, b1, c1 = None, None, None # type: (A, B, C) -a2, b2, c2 = None, None, None # type: (A, B, C) +a1: A +a2: A +b1: B +b2: B +c1: C +c2: C if int(): a1, (b1, c1) = a2, (b2, c2) @@ -673,9 +818,12 @@ class C: pass [builtins fixtures/tuple.pyi] [case testNestedTupleAssignment2] - -a1, b1, c1 = None, None, None # type: (A, B, C) -a2, b2, c2 = None, None, None # type: (A, B, C) +a1: A +a2: A +b1: B +b2: B +c1: C +c2: C t = a1, b1 if int(): @@ -714,7 +862,7 @@ class A: def __add__(self, x: 'A') -> 'A': pass def f(x: 'A') -> None: pass -a = None # type: A +a: A (a, a) + a # E: Unsupported operand types for + ("Tuple[A, A]" and "A") a + (a, a) # E: Unsupported operand types for + ("A" and "Tuple[A, A]") @@ -724,7 +872,7 @@ f((a, a)) # E: Argument 1 to "f" has incompatible type "Tuple[A, A]"; expected [case testLargeTuplesInErrorMessages] -a = None # type: LongTypeName +a: LongTypeName a + (a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a) # Fail class LongTypeName: @@ -740,7 +888,7 @@ main:3: error: Unsupported operand types for + ("LongTypeName" and "Tuple[LongTy [case testTupleMethods] from typing import Tuple -t = None # type: Tuple[int, str] +t: Tuple[int, str] i = 0 s = '' b = bool() @@ -811,6 +959,12 @@ for x in B(), A(): [builtins fixtures/for.pyi] [case testTupleIterable] +from typing import Iterable, Optional, TypeVar + +T = TypeVar("T") + +def sum(iterable: Iterable[T], start: Optional[T] = None) -> T: pass + y = 'a' x = sum((1,2)) if int(): @@ -869,7 +1023,7 @@ class A(tuple): pass import m [file m.pyi] from typing import Tuple -a = None # type: A +a: A class A(Tuple[int, str]): pass x, y = a x() # E: "int" not callable @@ -932,14 +1086,14 @@ fb(aa) # E: Argument 1 to "fb" has incompatible type "Tuple[A, A]"; expected "Tu [case testSubtypingTupleIsContainer] from typing import Container -a = None # type: Container[str] +a: Container[str] a = () [typing fixtures/typing-full.pyi] [builtins fixtures/tuple.pyi] [case testSubtypingTupleIsSized] from typing import Sized -a = None # type: Sized +a: Sized a = () [typing fixtures/typing-medium.pyi] [builtins fixtures/tuple.pyi] @@ -957,6 +1111,13 @@ b = (0, *a) reveal_type(b) # N: Revealed type is "builtins.tuple[builtins.int, ...]" [builtins fixtures/tuple.pyi] +[case testTupleWithStarExpr2Precise] +# flags: --enable-incomplete-feature=PreciseTupleTypes +a = [1] +b = (0, *a) +reveal_type(b) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.int, ...]]]" +[builtins fixtures/tuple.pyi] + [case testTupleWithStarExpr3] a = [''] b = (0, *a) @@ -965,6 +1126,15 @@ c = (*a, '') reveal_type(c) # N: Revealed type is "builtins.tuple[builtins.str, ...]" [builtins fixtures/tuple.pyi] +[case testTupleWithStarExpr3Precise] +# flags: --enable-incomplete-feature=PreciseTupleTypes +a = [''] +b = (0, *a) +reveal_type(b) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.str, ...]]]" +c = (*a, '') +reveal_type(c) # N: Revealed type is "Tuple[Unpack[builtins.tuple[builtins.str, ...]], builtins.str]" +[builtins fixtures/tuple.pyi] + [case testTupleWithStarExpr4] a = (1, 1, 'x', 'x') b = (1, 'x') @@ -972,7 +1142,7 @@ a = (0, *b, '') [builtins fixtures/tuple.pyi] [case testUnpackSyntaxError] -*foo # E: Can use starred expression only as assignment target +*foo # E: can't use starred expression here [builtins fixtures/tuple.pyi] [case testUnpackBases] @@ -1046,8 +1216,8 @@ class B1(A): pass class B2(A): pass class C: pass -x = None # type: Tuple[A, ...] -y = None # type: Tuple[Union[B1, C], Union[B2, C]] +x: Tuple[A, ...] +y: Tuple[Union[B1, C], Union[B2, C]] def g(x: T) -> Tuple[T, T]: return (x, x) @@ -1063,13 +1233,13 @@ from typing import Tuple class A: pass class B(A): pass -fixtup = None # type: Tuple[B, B] +fixtup: Tuple[B, B] -vartup_b = None # type: Tuple[B, ...] +vartup_b: Tuple[B, ...] reveal_type(fixtup if int() else vartup_b) # N: Revealed type is "builtins.tuple[__main__.B, ...]" reveal_type(vartup_b if int() else fixtup) # N: Revealed type is "builtins.tuple[__main__.B, ...]" -vartup_a = None # type: Tuple[A, ...] +vartup_a: Tuple[A, ...] reveal_type(fixtup if int() else vartup_a) # N: Revealed type is "builtins.tuple[__main__.A, ...]" reveal_type(vartup_a if int() else fixtup) # N: Revealed type is "builtins.tuple[__main__.A, ...]" @@ -1083,13 +1253,13 @@ from typing import Tuple, List class A: pass class B(A): pass -fixtup = None # type: Tuple[B, B] +fixtup: Tuple[B, B] -lst_b = None # type: List[B] +lst_b: List[B] reveal_type(fixtup if int() else lst_b) # N: Revealed type is "typing.Sequence[__main__.B]" reveal_type(lst_b if int() else fixtup) # N: Revealed type is "typing.Sequence[__main__.B]" -lst_a = None # type: List[A] +lst_a: List[A] reveal_type(fixtup if int() else lst_a) # N: Revealed type is "typing.Sequence[__main__.A]" reveal_type(lst_a if int() else fixtup) # N: Revealed type is "typing.Sequence[__main__.A]" @@ -1103,15 +1273,15 @@ class A: pass empty = () -fixtup = None # type: Tuple[A] +fixtup: Tuple[A] reveal_type(fixtup if int() else empty) # N: Revealed type is "builtins.tuple[__main__.A, ...]" reveal_type(empty if int() else fixtup) # N: Revealed type is "builtins.tuple[__main__.A, ...]" -vartup = None # type: Tuple[A, ...] +vartup: Tuple[A, ...] reveal_type(empty if int() else vartup) # N: Revealed type is "builtins.tuple[__main__.A, ...]" reveal_type(vartup if int() else empty) # N: Revealed type is "builtins.tuple[__main__.A, ...]" -lst = None # type: List[A] +lst: List[A] reveal_type(empty if int() else lst) # N: Revealed type is "typing.Sequence[__main__.A]" reveal_type(lst if int() else empty) # N: Revealed type is "typing.Sequence[__main__.A]" @@ -1128,9 +1298,9 @@ class NTup(NamedTuple): class SubTuple(Tuple[bool]): ... class SubVarTuple(Tuple[int, ...]): ... -ntup = None # type: NTup -subtup = None # type: SubTuple -vartup = None # type: SubVarTuple +ntup: NTup +subtup: SubTuple +vartup: SubVarTuple reveal_type(ntup if int() else vartup) # N: Revealed type is "builtins.tuple[builtins.int, ...]" reveal_type(subtup if int() else vartup) # N: Revealed type is "builtins.tuple[builtins.int, ...]" @@ -1141,8 +1311,8 @@ reveal_type(subtup if int() else vartup) # N: Revealed type is "builtins.tuple[ [case testTupleJoinIrregular] from typing import Tuple -tup1 = None # type: Tuple[bool, int] -tup2 = None # type: Tuple[bool] +tup1: Tuple[bool, int] +tup2: Tuple[bool] reveal_type(tup1 if int() else tup2) # N: Revealed type is "builtins.tuple[builtins.int, ...]" reveal_type(tup2 if int() else tup1) # N: Revealed type is "builtins.tuple[builtins.int, ...]" @@ -1168,9 +1338,9 @@ class NTup2(NamedTuple): class SubTuple(Tuple[bool, int, int]): ... -tup1 = None # type: NTup1 -tup2 = None # type: NTup2 -subtup = None # type: SubTuple +tup1: NTup1 +tup2: NTup2 +subtup: SubTuple reveal_type(tup1 if int() else tup2) # N: Revealed type is "builtins.tuple[builtins.bool, ...]" reveal_type(tup2 if int() else tup1) # N: Revealed type is "builtins.tuple[builtins.bool, ...]" @@ -1187,7 +1357,7 @@ reveal_type(subtup if int() else tup2) # N: Revealed type is "builtins.tuple[bu [case testTupleWithUndersizedContext] a = ([1], 'x') if int(): - a = ([], 'x', 1) # E: Incompatible types in assignment (expression has type "Tuple[List[int], str, int]", variable has type "Tuple[List[int], str]") + a = ([], 'x', 1) # E: Incompatible types in assignment (expression has type "Tuple[List[Never], str, int]", variable has type "Tuple[List[int], str]") [builtins fixtures/tuple.pyi] [case testTupleWithOversizedContext] @@ -1226,9 +1396,9 @@ f(0) # E: Argument 1 to "f" has incompatible type "int"; expected "Tuple[Any, . from typing import Tuple def f(a: Tuple[()]) -> None: pass f(()) -f((1,)) # E: Argument 1 to "f" has incompatible type "Tuple[int]"; expected "Tuple[]" -f(('', '')) # E: Argument 1 to "f" has incompatible type "Tuple[str, str]"; expected "Tuple[]" -f(0) # E: Argument 1 to "f" has incompatible type "int"; expected "Tuple[]" +f((1,)) # E: Argument 1 to "f" has incompatible type "Tuple[int]"; expected "Tuple[()]" +f(('', '')) # E: Argument 1 to "f" has incompatible type "Tuple[str, str]"; expected "Tuple[()]" +f(0) # E: Argument 1 to "f" has incompatible type "int"; expected "Tuple[()]" [builtins fixtures/tuple.pyi] [case testNonliteralTupleIndex] @@ -1248,7 +1418,7 @@ t = (0, "") x = 0 y = "" reveal_type(t[x:]) # N: Revealed type is "builtins.tuple[Union[builtins.int, builtins.str], ...]" -t[y:] # E: Slice index must be an integer or None +t[y:] # E: Slice index must be an integer, SupportsIndex or None [builtins fixtures/tuple.pyi] [case testInferTupleTypeFallbackAgainstInstance] @@ -1288,7 +1458,21 @@ def foo(o: CallableTuple) -> int: class CallableTuple(Tuple[str, int]): def __call__(self, n: int, m: int) -> int: return n +[builtins fixtures/tuple.pyi] + +[case testTypeTupleGenericCall] +from typing import Generic, Tuple, TypeVar + +T = TypeVar('T') +def foo(o: CallableTuple[int]) -> int: + reveal_type(o) # N: Revealed type is "Tuple[builtins.str, builtins.int, fallback=__main__.CallableTuple[builtins.int]]" + reveal_type(o.count(3)) # N: Revealed type is "builtins.int" + return o(1, 2) + +class CallableTuple(Tuple[str, T]): + def __call__(self, n: int, m: int) -> int: + return n [builtins fixtures/tuple.pyi] [case testTupleCompatibleWithSequence] @@ -1367,42 +1551,36 @@ reveal_type(a + b) # N: Revealed type is "Tuple[builtins.int, builtins.str, bui from typing import Tuple # long initializer assignment with few mismatches -t: Tuple[int, ...] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", 11) \ - # E: Incompatible types in assignment (3 tuple items are incompatible) \ - # N: Expression tuple item 8 has type "str"; "int" expected; \ - # N: Expression tuple item 9 has type "str"; "int" expected; \ - # N: Expression tuple item 10 has type "str"; "int" expected; +t: Tuple[int, ...] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", 11) # E: Incompatible types in assignment (3 tuple items are incompatible) \ + # N: Expression tuple item 8 has type "str"; "int" expected; \ + # N: Expression tuple item 9 has type "str"; "int" expected; \ + # N: Expression tuple item 10 has type "str"; "int" expected; # long initializer assignment with more mismatches -t1: Tuple[int, ...] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", "str") \ - # E: Incompatible types in assignment (4 tuple items are incompatible; 1 items are omitted) \ - # N: Expression tuple item 8 has type "str"; "int" expected; \ - # N: Expression tuple item 9 has type "str"; "int" expected; \ - # N: Expression tuple item 10 has type "str"; "int" expected; +t1: Tuple[int, ...] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", "str") # E: Incompatible types in assignment (4 tuple items are incompatible; 1 items are omitted) \ + # N: Expression tuple item 8 has type "str"; "int" expected; \ + # N: Expression tuple item 9 has type "str"; "int" expected; \ + # N: Expression tuple item 10 has type "str"; "int" expected; # short tuple initializer assignment -t2: Tuple[int, ...] = (1, 2, "s", 4) \ - # E: Incompatible types in assignment (expression has type "Tuple[int, int, str, int]", variable has type "Tuple[int, ...]") +t2: Tuple[int, ...] = (1, 2, "s", 4) # E: Incompatible types in assignment (expression has type "Tuple[int, int, str, int]", variable has type "Tuple[int, ...]") # long initializer assignment with few mismatches, no ellipsis -t3: Tuple[int, int, int, int, int, int, int, int, int, int, int, int] = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, "str", "str") \ - # E: Incompatible types in assignment (2 tuple items are incompatible) \ - # N: Expression tuple item 10 has type "str"; "int" expected; \ - # N: Expression tuple item 11 has type "str"; "int" expected; +t3: Tuple[int, int, int, int, int, int, int, int, int, int, int, int] = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, "str", "str") # E: Incompatible types in assignment (2 tuple items are incompatible) \ + # N: Expression tuple item 10 has type "str"; "int" expected; \ + # N: Expression tuple item 11 has type "str"; "int" expected; # long initializer assignment with more mismatches, no ellipsis -t4: Tuple[int, int, int, int, int, int, int, int, int, int, int, int] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", "str") \ - # E: Incompatible types in assignment (4 tuple items are incompatible; 1 items are omitted) \ - # N: Expression tuple item 8 has type "str"; "int" expected; \ - # N: Expression tuple item 9 has type "str"; "int" expected; \ - # N: Expression tuple item 10 has type "str"; "int" expected; +t4: Tuple[int, int, int, int, int, int, int, int, int, int, int, int] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", "str") # E: Incompatible types in assignment (4 tuple items are incompatible; 1 items are omitted) \ + # N: Expression tuple item 8 has type "str"; "int" expected; \ + # N: Expression tuple item 9 has type "str"; "int" expected; \ + # N: Expression tuple item 10 has type "str"; "int" expected; # short tuple initializer assignment, no ellipsis t5: Tuple[int, int] = (1, 2, "s", 4) # E: Incompatible types in assignment (expression has type "Tuple[int, int, str, int]", variable has type "Tuple[int, int]") # long initializer assignment with mismatched pairs -t6: Tuple[int, int, int, int, int, int, int, int, int, int, int, int] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", "str", 1, 1, 1, 1, 1) \ - # E: Incompatible types in assignment (expression has type Tuple[int, int, ... <15 more items>], variable has type Tuple[int, int, ... <10 more items>]) +t6: Tuple[int, int, int, int, int, int, int, int, int, int, int, int] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", "str", 1, 1, 1, 1, 1) # E: Incompatible types in assignment (expression has type Tuple[int, int, ... <15 more items>], variable has type Tuple[int, int, ... <10 more items>]) [builtins fixtures/tuple.pyi] @@ -1443,8 +1621,7 @@ x7, x8, y7, y8 = *points2, *points3 # E: Contiguous iterable with same type expe x9, y9, x10, y10, z5 = *points2, 1, *points2 # E: Contiguous iterable with same type expected [builtins fixtures/tuple.pyi] -[case testAssignEmptyPy36] -# flags: --python-version 3.6 +[case testAssignEmpty] () = [] [case testAssignEmptyBogus] @@ -1456,7 +1633,7 @@ from typing import Tuple t = ('',) * 2 reveal_type(t) # N: Revealed type is "Tuple[builtins.str, builtins.str]" t2 = ('',) * -1 -reveal_type(t2) # N: Revealed type is "Tuple[]" +reveal_type(t2) # N: Revealed type is "Tuple[()]" t3 = ('', 1) * 2 reveal_type(t3) # N: Revealed type is "Tuple[builtins.str, builtins.int, builtins.str, builtins.int]" def f() -> Tuple[str, ...]: @@ -1464,12 +1641,21 @@ def f() -> Tuple[str, ...]: reveal_type(f() * 2) # N: Revealed type is "builtins.tuple[builtins.str, ...]" [builtins fixtures/tuple.pyi] +[case testEmptyTupleTypeRepr] +from typing import Tuple + +def f() -> Tuple[()]: ... + +reveal_type(f) # N: Revealed type is "def () -> Tuple[()]" +reveal_type(f()) # N: Revealed type is "Tuple[()]" +[builtins fixtures/tuple.pyi] + [case testMultiplyTupleByIntegerLiteralReverse] from typing import Tuple t = 2 * ('',) reveal_type(t) # N: Revealed type is "Tuple[builtins.str, builtins.str]" t2 = -1 * ('',) -reveal_type(t2) # N: Revealed type is "Tuple[]" +reveal_type(t2) # N: Revealed type is "Tuple[()]" t3 = 2 * ('', 1) reveal_type(t3) # N: Revealed type is "Tuple[builtins.str, builtins.int, builtins.str, builtins.int]" def f() -> Tuple[str, ...]: @@ -1502,3 +1688,30 @@ class Bar(aaaaaaaaaa): # E: Name "aaaaaaaaaa" is not defined class FooBarTuple(Tuple[Foo, Bar]): ... [builtins fixtures/tuple.pyi] + + +[case testTupleOverloadZipAny] +from typing import Any, Iterable, Iterator, Tuple, TypeVar, overload + +T = TypeVar("T") + +@overload +def zip(__i: Iterable[T]) -> Iterator[Tuple[T]]: ... +@overload +def zip(*i: Iterable[Any]) -> Iterator[Tuple[Any, ...]]: ... +def zip(i): ... + +def g(t: Tuple): + reveal_type(zip(*t)) # N: Revealed type is "typing.Iterator[builtins.tuple[Any, ...]]" + reveal_type(zip(t)) # N: Revealed type is "typing.Iterator[Tuple[Any]]" +[builtins fixtures/tuple.pyi] + +[case testTupleSubclassSlice] +from typing import Tuple + +class A: ... + +class tuple_aa_subclass(Tuple[A, A]): ... + +inst_tuple_aa_subclass: tuple_aa_subclass = tuple_aa_subclass((A(), A()))[:] # E: Incompatible types in assignment (expression has type "Tuple[A, A]", variable has type "tuple_aa_subclass") +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index d7cccd2d6ba6..aebb0381d962 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -12,7 +12,7 @@ U = Union[int, str] def f(x: U) -> None: pass f(1) f('') -f(()) # E: Argument 1 to "f" has incompatible type "Tuple[]"; expected "Union[int, str]" +f(()) # E: Argument 1 to "f" has incompatible type "Tuple[()]"; expected "Union[int, str]" [targets __main__, __main__.f] [builtins fixtures/tuple.pyi] @@ -28,7 +28,7 @@ f(1) # E: Argument 1 to "f" has incompatible type "int"; expected "Tuple[int, st [case testCallableTypeAlias] from typing import Callable A = Callable[[int], None] -f = None # type: A +f: A f(1) f('') # E: Argument 1 has incompatible type "str"; expected "int" [targets __main__] @@ -64,7 +64,7 @@ from _m import U def f(x: U) -> None: pass f(1) f('x') -f(()) # E: Argument 1 to "f" has incompatible type "Tuple[]"; expected "Union[int, str]" +f(()) # E: Argument 1 to "f" has incompatible type "Tuple[()]"; expected "Union[int, str]" [file _m.py] from typing import Union U = Union[int, str] @@ -169,12 +169,12 @@ f(1) # E: Argument 1 to "f" has incompatible type "int"; expected "str" [case testEmptyTupleTypeAlias] from typing import Tuple, Callable EmptyTuple = Tuple[()] -x = None # type: EmptyTuple -reveal_type(x) # N: Revealed type is "Tuple[]" +x: EmptyTuple +reveal_type(x) # N: Revealed type is "Tuple[()]" EmptyTupleCallable = Callable[[Tuple[()]], None] -f = None # type: EmptyTupleCallable -reveal_type(f) # N: Revealed type is "def (Tuple[])" +f: EmptyTupleCallable +reveal_type(f) # N: Revealed type is "def (Tuple[()])" [builtins fixtures/list.pyi] [case testForwardTypeAlias] @@ -197,30 +197,35 @@ Alias = Tuple[int, T] [out] [case testRecursiveAliasesErrors1] -# flags: --disable-recursive-aliases -# Recursive aliases are not supported yet. from typing import Type, Callable, Union -A = Union[A, int] # E: Cannot resolve name "A" (possible cyclic definition) -B = Callable[[B], int] # E: Cannot resolve name "B" (possible cyclic definition) -C = Type[C] # E: Cannot resolve name "C" (possible cyclic definition) +def test() -> None: + A = Union[A, int] # E: Cannot resolve name "A" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope + B = Callable[[B], int] # E: Cannot resolve name "B" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope + C = Type[C] # E: Cannot resolve name "C" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope [case testRecursiveAliasesErrors2] -# flags: --disable-recursive-aliases --disable-error-code=used-before-def -# Recursive aliases are not supported yet. +# flags: --disable-error-code=used-before-def from typing import Type, Callable, Union -A = Union[B, int] -B = Callable[[C], int] -C = Type[A] -x: A -reveal_type(x) +def test() -> None: + A = Union[B, int] + B = Callable[[C], int] + C = Type[A] + x: A + reveal_type(x) [out] main:5: error: Cannot resolve name "A" (possible cyclic definition) +main:5: note: Recursive types are not allowed at function scope main:5: error: Cannot resolve name "B" (possible cyclic definition) main:6: error: Cannot resolve name "B" (possible cyclic definition) +main:6: note: Recursive types are not allowed at function scope main:6: error: Cannot resolve name "C" (possible cyclic definition) main:7: error: Cannot resolve name "C" (possible cyclic definition) +main:7: note: Recursive types are not allowed at function scope main:9: note: Revealed type is "Union[Any, builtins.int]" [case testDoubleForwardAlias] @@ -245,13 +250,16 @@ reveal_type(x[0].x) # N: Revealed type is "builtins.str" [out] [case testJSONAliasApproximation] -# flags: --disable-recursive-aliases from typing import List, Union, Dict -x: JSON # E: Cannot resolve name "JSON" (possible cyclic definition) -JSON = Union[int, str, List[JSON], Dict[str, JSON]] # E: Cannot resolve name "JSON" (possible cyclic definition) -reveal_type(x) # N: Revealed type is "Any" -if isinstance(x, list): - reveal_type(x) # N: Revealed type is "builtins.list[Any]" + +def test() -> None: + x: JSON # E: Cannot resolve name "JSON" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope + JSON = Union[int, str, List[JSON], Dict[str, JSON]] # E: Cannot resolve name "JSON" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope + reveal_type(x) # N: Revealed type is "Any" + if isinstance(x, list): + reveal_type(x) # N: Revealed type is "builtins.list[Any]" [builtins fixtures/isinstancelist.pyi] [out] @@ -305,7 +313,6 @@ reveal_type(y) # N: Revealed type is "Union[builtins.int, None]" [builtins fixtures/bool.pyi] [case testNoneAliasStrict] -# flags: --strict-optional from typing import Optional, Union void = type(None) x: int @@ -323,10 +330,9 @@ c: C t: T reveal_type(c) # N: Revealed type is "def (*Any, **Any) -> Any" reveal_type(t) # N: Revealed type is "builtins.tuple[Any, ...]" -bad: C[int] # E: Bad number of arguments for type alias, expected: 0, given: 1 -also_bad: T[int] # E: Bad number of arguments for type alias, expected: 0, given: 1 +bad: C[int] # E: Bad number of arguments for type alias, expected 0, given 1 +also_bad: T[int] # E: Bad number of arguments for type alias, expected 0, given 1 [builtins fixtures/tuple.pyi] -[out] [case testAliasRefOnClass] from typing import Generic, TypeVar, Type @@ -1028,3 +1034,182 @@ RHSAlias3: type = tuple[int, ...] WrongTypeElement = str | tuple[float, 1] # E: Invalid type: try using Literal[1] instead? WrongEllipsis = str | tuple[float, float, ...] # E: Unexpected "..." [builtins fixtures/tuple.pyi] + +[case testCompiledNoCrashOnSingleItemUnion] +# flags: --no-strict-optional +from typing import Callable, Union, Generic, TypeVar + +Alias = Callable[[], int] + +T = TypeVar("T") +class C(Generic[T]): + attr: Union[Alias, None] = None + + @classmethod + def test(cls) -> None: + cls.attr +[builtins fixtures/classmethod.pyi] + +[case testRecursiveAliasTuple] +from typing_extensions import Literal, TypeAlias +from typing import Tuple, Union + +Expr: TypeAlias = Union[ + Tuple[Literal[123], int], + Tuple[Literal[456], "Expr"], +] + +def eval(e: Expr) -> int: + if e[0] == 123: + return e[1] + elif e[0] == 456: + return -eval(e[1]) +[builtins fixtures/dict-full.pyi] + +[case testTypeAliasType] +from typing import Union +from typing_extensions import TypeAliasType + +TestType = TypeAliasType("TestType", Union[int, str]) +x: TestType = 42 +y: TestType = 'a' +z: TestType = object() # E: Incompatible types in assignment (expression has type "object", variable has type "Union[int, str]") + +class A: + ClassAlias = TypeAliasType("ClassAlias", int) +xc: A.ClassAlias = 1 +yc: A.ClassAlias = "" # E: Incompatible types in assignment (expression has type "str", variable has type "int") +[builtins fixtures/tuple.pyi] + +[case testTypeAliasTypeInvalid] +from typing_extensions import TypeAliasType + +TestType = TypeAliasType("T", int) # E: String argument 1 "T" to TypeAliasType(...) does not match variable name "TestType" + +T1 = T2 = TypeAliasType("T", int) +t1: T1 # E: Variable "__main__.T1" is not valid as a type \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases + +T3 = TypeAliasType("T3", -1) # E: Invalid type: try using Literal[-1] instead? +t3: T3 +reveal_type(t3) # N: Revealed type is "Any" +[builtins fixtures/tuple.pyi] + +[case testTypeAliasTypeGeneric] +from typing import Callable, Dict, Generic, TypeVar, Tuple +from typing_extensions import TypeAliasType, TypeVarTuple, ParamSpec, Unpack + +K = TypeVar('K') +V = TypeVar('V') +T = TypeVar('T') +Ts = TypeVarTuple("Ts") +Ts1 = TypeVarTuple("Ts1") +P = ParamSpec("P") + +TestType = TypeAliasType("TestType", Dict[K, V], type_params=(K, V)) +x: TestType[int, str] = {1: 'a'} +y: TestType[str, int] = {'a': 1} +z: TestType[str, int] = {1: 'a'} # E: Dict entry 0 has incompatible type "int": "str"; expected "str": "int" +w: TestType[int] # E: Bad number of arguments for type alias, expected 2, given 1 + +InvertedDict = TypeAliasType("InvertedDict", Dict[K, V], type_params=(V, K)) +xi: InvertedDict[str, int] = {1: 'a'} +yi: InvertedDict[str, int] = {'a': 1} # E: Dict entry 0 has incompatible type "str": "int"; expected "int": "str" +zi: InvertedDict[int, str] = {1: 'a'} # E: Dict entry 0 has incompatible type "int": "str"; expected "str": "int" +reveal_type(xi) # N: Revealed type is "builtins.dict[builtins.int, builtins.str]" + +VariadicAlias1 = TypeAliasType("VariadicAlias1", Tuple[Unpack[Ts]], type_params=(Ts,)) +VariadicAlias2 = TypeAliasType("VariadicAlias2", Tuple[Unpack[Ts], K], type_params=(Ts, K)) +VariadicAlias3 = TypeAliasType("VariadicAlias3", Callable[[Unpack[Ts]], int], type_params=(Ts,)) +xv: VariadicAlias1[int, str] = (1, 'a') +yv: VariadicAlias1[str, int] = (1, 'a') # E: Incompatible types in assignment (expression has type "Tuple[int, str]", variable has type "Tuple[str, int]") +zv: VariadicAlias2[int, str] = (1, 'a') +def int_in_int_out(x: int) -> int: return x +wv: VariadicAlias3[int] = int_in_int_out +reveal_type(wv) # N: Revealed type is "def (builtins.int) -> builtins.int" + +ParamAlias = TypeAliasType("ParamAlias", Callable[P, int], type_params=(P,)) +def f(x: str, y: float) -> int: return 1 +def g(x: int, y: float) -> int: return 1 +xp1: ParamAlias[str, float] = f +xp2: ParamAlias[str, float] = g # E: Incompatible types in assignment (expression has type "Callable[[int, float], int]", variable has type "Callable[[str, float], int]") +xp3: ParamAlias[str, float] = lambda x, y: 1 + +class G(Generic[P, T]): ... +ParamAlias2 = TypeAliasType("ParamAlias2", G[P, T], type_params=(P, T)) +xp: ParamAlias2[[int], str] +reveal_type(xp) # N: Revealed type is "__main__.G[[builtins.int], builtins.str]" +[builtins fixtures/dict.pyi] + +[case testTypeAliasTypeInvalidGeneric] +from typing_extensions import TypeAliasType, TypeVarTuple, ParamSpec +from typing import Callable, Dict, Generic, TypeVar, Tuple, Unpack + +K = TypeVar('K') +V = TypeVar('V') +T = TypeVar('T') +Ts = TypeVarTuple("Ts") +Ts1 = TypeVarTuple("Ts1") +P = ParamSpec("P") + +Ta0 = TypeAliasType("Ta0", int, type_params=(T, T)) # E: Duplicate type variable "T" in type_params argument to TypeAliasType + +Ta1 = TypeAliasType("Ta1", int, type_params=K) # E: Tuple literal expected as the type_params argument to TypeAliasType + +Ta2 = TypeAliasType("Ta2", int, type_params=(None,)) # E: Free type variable expected in type_params argument to TypeAliasType + +Ta3 = TypeAliasType("Ta3", Dict[K, V], type_params=(V,)) # E: Type variable "K" is not included in type_params +partially_generic1: Ta3[int] = {"a": 1} +reveal_type(partially_generic1) # N: Revealed type is "builtins.dict[Any, builtins.int]" +partially_generic2: Ta3[int] = {1: "a"} # E: Dict entry 0 has incompatible type "int": "str"; expected "Any": "int" + +Ta4 = TypeAliasType("Ta4", Tuple[Unpack[Ts]], type_params=(Ts, Ts1)) # E: Can only use one TypeVarTuple in type_params argument to TypeAliasType + +Ta5 = TypeAliasType("Ta5", Dict) # Unlike old style aliases, this is not generic +non_generic_dict: Ta5[int, str] # E: Bad number of arguments for type alias, expected 0, given 2 +reveal_type(non_generic_dict) # N: Revealed type is "builtins.dict[Any, Any]" + +Ta6 = TypeAliasType("Ta6", Tuple[Unpack[Ts]]) # E: TypeVarTuple "Ts" is not included in type_params +unbound_tvt_alias: Ta6[int] # E: Bad number of arguments for type alias, expected 0, given 1 +reveal_type(unbound_tvt_alias) # N: Revealed type is "builtins.tuple[Any, ...]" + +class G(Generic[P, T]): ... +Ta7 = TypeAliasType("Ta7", G[P, T]) # E: ParamSpec "P" is not included in type_params \ + # E: Type variable "T" is not included in type_params +unbound_ps_alias: Ta7[[int], str] # E: Bracketed expression "[...]" is not valid as a type \ + # N: Did you mean "List[...]"? \ + # E: Bad number of arguments for type alias, expected 0, given 2 +reveal_type(unbound_ps_alias) # N: Revealed type is "__main__.G[Any, Any]" + +Ta8 = TypeAliasType("Ta8", Callable[P, int]) # E: ParamSpec "P" is not included in type_params +unbound_ps_alias2: Ta8[int] # E: Bad number of arguments for type alias, expected 0, given 1 +reveal_type(unbound_ps_alias2) # N: Revealed type is "def [P] (*Any, **Any) -> builtins.int" + +Ta9 = TypeAliasType("Ta9", Callable[P, T]) # E: ParamSpec "P" is not included in type_params \ + # E: Type variable "T" is not included in type_params +unbound_ps_alias3: Ta9[int, str] # E: Bad number of arguments for type alias, expected 0, given 2 +reveal_type(unbound_ps_alias3) # N: Revealed type is "def [P] (*Any, **Any) -> Any" + +Ta10 = TypeAliasType("Ta10", Callable[[Unpack[Ts]], str]) # E: TypeVarTuple "Ts" is not included in type_params +unbound_tvt_alias2: Ta10[int] # E: Bad number of arguments for type alias, expected 0, given 1 +reveal_type(unbound_tvt_alias2) # N: Revealed type is "def (*Any) -> builtins.str" + +class A(Generic[T]): + Ta11 = TypeAliasType("Ta11", Dict[str, T], type_params=(T,)) # E: Can't use bound type variable "T" to define generic alias \ + # E: "T" is a type variable and only valid in type context +x: A.Ta11 = {"a": 1} +reveal_type(x) # N: Revealed type is "builtins.dict[builtins.str, Any]" +[builtins fixtures/dict.pyi] + +[case testTypeAliasTypeNoUnpackInTypeParams311] +# flags: --python-version 3.11 +from typing_extensions import TypeAliasType, TypeVar, TypeVarTuple, Unpack + +T = TypeVar("T") +Ts = TypeVarTuple("Ts") + +Ta1 = TypeAliasType("Ta1", None, type_params=(*Ts,)) # E: can't use starred expression here +Ta2 = TypeAliasType("Ta2", None, type_params=(Unpack[Ts],)) # E: Free type variable expected in type_params argument to TypeAliasType \ + # N: Don't Unpack type variables in type_params + +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-type-checks.test b/test-data/unit/check-type-checks.test index 106f2d680ba4..03c8de4177f3 100644 --- a/test-data/unit/check-type-checks.test +++ b/test-data/unit/check-type-checks.test @@ -2,9 +2,9 @@ [case testSimpleIsinstance] -x = None # type: object -n = None # type: int -s = None # type: str +x: object +n: int +s: str if int(): n = x # E: Incompatible types in assignment (expression has type "object", variable has type "int") if isinstance(x, int): diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index e3d6188b643b..bd1fbe3f2667 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -9,7 +9,7 @@ reveal_type(p) # N: Revealed type is "TypedDict('__main__.Point', {'x': builtin reveal_type(p.values()) # N: Revealed type is "typing.Iterable[builtins.object]" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] -[targets sys, __main__] +[targets __main__] [case testCanCreateTypedDictInstanceWithDictCall] from mypy_extensions import TypedDict @@ -87,7 +87,6 @@ D = TypedDict('D', { -- Define TypedDict (Class syntax) [case testCanCreateTypedDictWithClass] -# flags: --python-version 3.6 from mypy_extensions import TypedDict class Point(TypedDict): @@ -99,7 +98,6 @@ reveal_type(p) # N: Revealed type is "TypedDict('__main__.Point', {'x': builtin [builtins fixtures/dict.pyi] [case testCanCreateTypedDictWithSubclass] -# flags: --python-version 3.6 from mypy_extensions import TypedDict class Point1D(TypedDict): @@ -113,7 +111,6 @@ reveal_type(p) # N: Revealed type is "TypedDict('__main__.Point2D', {'x': built [builtins fixtures/dict.pyi] [case testCanCreateTypedDictWithSubclass2] -# flags: --python-version 3.6 from mypy_extensions import TypedDict class Point1D(TypedDict): @@ -126,7 +123,6 @@ reveal_type(p) # N: Revealed type is "TypedDict('__main__.Point2D', {'x': built [builtins fixtures/dict.pyi] [case testCanCreateTypedDictClassEmpty] -# flags: --python-version 3.6 from mypy_extensions import TypedDict class EmptyDict(TypedDict): @@ -138,10 +134,7 @@ reveal_type(p) # N: Revealed type is "TypedDict('__main__.EmptyDict', {})" [case testCanCreateTypedDictWithClassOldVersion] -# flags: --python-version 3.5 - -# Test that we can use class-syntax to merge TypedDicts even in -# versions without type annotations +# Test that we can use class-syntax to merge function-based TypedDicts from mypy_extensions import TypedDict @@ -165,7 +158,6 @@ foo({'name': 'lol', 'year': 2009, 'based_on': 0}) # E: Incompatible types (expr -- Define TypedDict (Class syntax errors) [case testCannotCreateTypedDictWithClassOtherBases] -# flags: --python-version 3.6 from mypy_extensions import TypedDict class A: pass @@ -195,7 +187,6 @@ class C(TypedDict, TypedDict): # E: Duplicate base class "TypedDict" [typing fixtures/typing-typeddict.pyi] [case testCannotCreateTypedDictWithClassWithOtherStuff] -# flags: --python-version 3.6 from mypy_extensions import TypedDict class Point(TypedDict): @@ -251,7 +242,6 @@ Point = TypedDict('Point', {'x': int, 'y': int, '_fallback': object}) [builtins fixtures/dict.pyi] [case testCanCreateTypedDictWithClassUnderscores] -# flags: --python-version 3.6 from mypy_extensions import TypedDict class Point(TypedDict): @@ -263,7 +253,6 @@ reveal_type(p) # N: Revealed type is "TypedDict('__main__.Point', {'x': builtins [builtins fixtures/dict.pyi] [case testCannotCreateTypedDictWithDuplicateKey1] -# flags: --python-version 3.6 from mypy_extensions import TypedDict class Bad(TypedDict): @@ -291,7 +280,6 @@ reveal_type(d2) # N: Revealed type is "TypedDict('__main__.D2', {'x': builtins.s [typing fixtures/typing-typeddict.pyi] [case testCanCreateTypedDictWithClassOverwriting] -# flags: --python-version 3.6 from mypy_extensions import TypedDict class Point1(TypedDict): @@ -306,7 +294,6 @@ reveal_type(b) # N: Revealed type is "TypedDict('__main__.Bad', {'x': builtins.i [builtins fixtures/dict.pyi] [case testCanCreateTypedDictWithClassOverwriting2] -# flags: --python-version 3.6 from mypy_extensions import TypedDict class Point1(TypedDict): @@ -608,7 +595,6 @@ reveal_type(f(g)) # N: Revealed type is "TypedDict({'x': builtins.int, 'y': bui [builtins fixtures/dict.pyi] [case testMeetOfTypedDictsWithIncompatibleCommonKeysIsUninhabited] -# flags: --strict-optional from mypy_extensions import TypedDict from typing import TypeVar, Callable XYa = TypedDict('XYa', {'x': int, 'y': int}) @@ -616,7 +602,7 @@ YbZ = TypedDict('YbZ', {'y': object, 'z': int}) T = TypeVar('T') def f(x: Callable[[T, T], None]) -> T: pass def g(x: XYa, y: YbZ) -> None: pass -reveal_type(f(g)) # N: Revealed type is "" +reveal_type(f(g)) # N: Revealed type is "Never" [builtins fixtures/dict.pyi] [case testMeetOfTypedDictsWithNoCommonKeysHasAllKeysAndNewFallback] @@ -632,7 +618,6 @@ reveal_type(f(g)) # N: Revealed type is "TypedDict({'x': builtins.int, 'z': bui # TODO: It would be more accurate for the meet to be TypedDict instead. [case testMeetOfTypedDictWithCompatibleMappingIsUninhabitedForNow] -# flags: --strict-optional from mypy_extensions import TypedDict from typing import TypeVar, Callable, Mapping X = TypedDict('X', {'x': int}) @@ -640,11 +625,10 @@ M = Mapping[str, int] T = TypeVar('T') def f(x: Callable[[T, T], None]) -> T: pass def g(x: X, y: M) -> None: pass -reveal_type(f(g)) # N: Revealed type is "" +reveal_type(f(g)) # N: Revealed type is "Never" [builtins fixtures/dict.pyi] [case testMeetOfTypedDictWithIncompatibleMappingIsUninhabited] -# flags: --strict-optional from mypy_extensions import TypedDict from typing import TypeVar, Callable, Mapping X = TypedDict('X', {'x': int}) @@ -652,11 +636,10 @@ M = Mapping[str, str] T = TypeVar('T') def f(x: Callable[[T, T], None]) -> T: pass def g(x: X, y: M) -> None: pass -reveal_type(f(g)) # N: Revealed type is "" +reveal_type(f(g)) # N: Revealed type is "Never" [builtins fixtures/dict.pyi] [case testMeetOfTypedDictWithCompatibleMappingSuperclassIsUninhabitedForNow] -# flags: --strict-optional from mypy_extensions import TypedDict from typing import TypeVar, Callable, Iterable X = TypedDict('X', {'x': int}) @@ -690,7 +673,6 @@ reveal_type(f(g)) # N: Revealed type is "TypedDict({'x'?: builtins.int, 'y': bu [builtins fixtures/dict.pyi] [case testMeetOfTypedDictsWithIncompatibleNonTotalAndTotal] -# flags: --strict-optional from mypy_extensions import TypedDict from typing import TypeVar, Callable XY = TypedDict('XY', {'x': int, 'y': int}, total=False) @@ -698,7 +680,7 @@ YZ = TypedDict('YZ', {'y': int, 'z': int}) T = TypeVar('T') def f(x: Callable[[T, T], None]) -> T: pass def g(x: XY, y: YZ) -> None: pass -reveal_type(f(g)) # N: Revealed type is "" +reveal_type(f(g)) # N: Revealed type is "Never" [builtins fixtures/dict.pyi] @@ -985,7 +967,6 @@ if int(): -- Other TypedDict methods [case testTypedDictGetMethod] -# flags: --strict-optional from mypy_extensions import TypedDict class A: pass D = TypedDict('D', {'x': int, 'y': str}) @@ -999,7 +980,6 @@ reveal_type(d.get('y', None)) # N: Revealed type is "Union[builtins.str, None]" [typing fixtures/typing-typeddict.pyi] [case testTypedDictGetMethodTypeContext] -# flags: --strict-optional from typing import List from mypy_extensions import TypedDict class A: pass @@ -1057,7 +1037,6 @@ p.get('x', 1 + 'y') # E: Unsupported operand types for + ("int" and "str") [typing fixtures/typing-typeddict.pyi] [case testTypedDictChainedGetWithEmptyDictDefault] -# flags: --strict-optional from mypy_extensions import TypedDict C = TypedDict('C', {'a': int}) D = TypedDict('D', {'x': C, 'y': str}) @@ -1121,8 +1100,8 @@ D = TypedDict('D', {'x': int, 'y': str}, total=False) d: D reveal_type(d['x']) # N: Revealed type is "builtins.int" reveal_type(d['y']) # N: Revealed type is "builtins.str" -reveal_type(d.get('x')) # N: Revealed type is "builtins.int" -reveal_type(d.get('y')) # N: Revealed type is "builtins.str" +reveal_type(d.get('x')) # N: Revealed type is "Union[builtins.int, None]" +reveal_type(d.get('y')) # N: Revealed type is "Union[builtins.str, None]" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] @@ -1464,34 +1443,34 @@ reveal_type(x['a']['b']) # N: Revealed type is "builtins.int" [builtins fixtures/dict.pyi] [case testSelfRecursiveTypedDictInheriting] - from mypy_extensions import TypedDict -# flags: --disable-recursive-aliases -class MovieBase(TypedDict): - name: str - year: int -class Movie(MovieBase): - director: 'Movie' # E: Cannot resolve name "Movie" (possible cyclic definition) +def test() -> None: + class MovieBase(TypedDict): + name: str + year: int -m: Movie -reveal_type(m['director']['name']) # N: Revealed type is "Any" + class Movie(MovieBase): + director: 'Movie' # E: Cannot resolve name "Movie" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope + m: Movie + reveal_type(m['director']['name']) # N: Revealed type is "Any" [builtins fixtures/dict.pyi] -[out] [case testSubclassOfRecursiveTypedDict] -# flags: --disable-recursive-aliases from typing import List from mypy_extensions import TypedDict -class Command(TypedDict): - subcommands: List['Command'] # E: Cannot resolve name "Command" (possible cyclic definition) +def test() -> None: + class Command(TypedDict): + subcommands: List['Command'] # E: Cannot resolve name "Command" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope -class HelpCommand(Command): - pass + class HelpCommand(Command): + pass -hc = HelpCommand(subcommands=[]) -reveal_type(hc) # N: Revealed type is "TypedDict('__main__.HelpCommand', {'subcommands': builtins.list[Any]})" + hc = HelpCommand(subcommands=[]) + reveal_type(hc) # N: Revealed type is "TypedDict('__main__.HelpCommand@8', {'subcommands': builtins.list[Any]})" [builtins fixtures/list.pyi] [out] @@ -1642,7 +1621,7 @@ def f1(x: int, y: str, z: bytes) -> None: ... def f2(x: int, y: str) -> None: ... td: TD -d = None # type: Dict[Any, Any] +d: Dict[Any, Any] f1(**td, **d) f1(**d, **td) @@ -1740,8 +1719,8 @@ class TDB(TypedDict): td: Union[TDA, TDB] -reveal_type(td.get('a')) # N: Revealed type is "builtins.int" -reveal_type(td.get('b')) # N: Revealed type is "Union[builtins.str, builtins.int]" +reveal_type(td.get('a')) # N: Revealed type is "Union[builtins.int, None]" +reveal_type(td.get('b')) # N: Revealed type is "Union[builtins.str, None, builtins.int]" reveal_type(td.get('c')) # N: Revealed type is "builtins.object" reveal_type(td['a']) # N: Revealed type is "builtins.int" @@ -1774,7 +1753,6 @@ reveal_type(td.pop('c')) # E: TypedDict "TDA" has no key "c" \ [typing fixtures/typing-typeddict.pyi] [case testCanCreateTypedDictWithTypingExtensions] -# flags: --python-version 3.6 from typing_extensions import TypedDict class Point(TypedDict): @@ -1786,7 +1764,6 @@ reveal_type(p) # N: Revealed type is "TypedDict('__main__.Point', {'x': builtin [builtins fixtures/dict.pyi] [case testCanCreateTypedDictWithTypingProper] -# flags: --python-version 3.8 from typing import TypedDict class Point(TypedDict): @@ -1805,7 +1782,7 @@ from mypy_extensions import TypedDict class A(TypedDict): x: int -d: Union[A, None] +d: A d.update({'x': 1}) [builtins fixtures/dict.pyi] @@ -1878,7 +1855,7 @@ class Config(TypedDict): b: str x: Config -x == {} # E: Non-overlapping equality check (left operand type: "Config", right operand type: "Dict[, ]") +x == {} # E: Non-overlapping equality check (left operand type: "Config", right operand type: "Dict[Never, Never]") [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] @@ -2730,7 +2707,7 @@ class TD(TypedDict): reveal_type(TD.__iter__) # N: Revealed type is "def (typing._TypedDict) -> typing.Iterator[builtins.str]" reveal_type(TD.__annotations__) # N: Revealed type is "typing.Mapping[builtins.str, builtins.object]" reveal_type(TD.values) # N: Revealed type is "def (self: typing.Mapping[T`1, T_co`2]) -> typing.Iterable[T_co`2]" -[builtins fixtures/dict.pyi] +[builtins fixtures/dict-full.pyi] [typing fixtures/typing-typeddict.pyi] [case testGenericTypedDictAlias] @@ -2873,3 +2850,640 @@ foo({"foo": {"e": "foo"}}) # E: Type of TypedDict is ambiguous, none of ("A", " # E: Argument 1 to "foo" has incompatible type "Dict[str, Dict[str, str]]"; expected "Union[A, B]" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] + +[case testTypedDictMissingEmptyKey] +from typing_extensions import TypedDict + +class A(TypedDict): + my_attr_1: str + my_attr_2: int + +d: A +d[''] # E: TypedDict "A" has no key "" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictFlexibleUpdate] +from mypy_extensions import TypedDict + +A = TypedDict("A", {"foo": int, "bar": int}) +B = TypedDict("B", {"foo": int}) + +a = A({"foo": 1, "bar": 2}) +b = B({"foo": 2}) +a.update({"foo": 2}) +a.update(b) +a.update(a) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictStrictUpdate] +# flags: --extra-checks +from mypy_extensions import TypedDict + +A = TypedDict("A", {"foo": int, "bar": int}) +B = TypedDict("B", {"foo": int}) + +a = A({"foo": 1, "bar": 2}) +b = B({"foo": 2}) +a.update({"foo": 2}) # OK +a.update(b) # E: Argument 1 to "update" of "TypedDict" has incompatible type "B"; expected "TypedDict({'foo': int, 'bar'?: int})" +a.update(a) # OK +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictFlexibleUpdateUnion] +from typing import Union +from mypy_extensions import TypedDict + +A = TypedDict("A", {"foo": int, "bar": int}) +B = TypedDict("B", {"foo": int}) +C = TypedDict("C", {"bar": int}) + +a = A({"foo": 1, "bar": 2}) +u: Union[B, C] +a.update(u) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictFlexibleUpdateUnionExtra] +from typing import Union +from mypy_extensions import TypedDict + +A = TypedDict("A", {"foo": int, "bar": int}) +B = TypedDict("B", {"foo": int, "extra": int}) +C = TypedDict("C", {"bar": int, "extra": int}) + +a = A({"foo": 1, "bar": 2}) +u: Union[B, C] +a.update(u) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictFlexibleUpdateUnionStrict] +# flags: --extra-checks +from typing import Union, NotRequired +from mypy_extensions import TypedDict + +A = TypedDict("A", {"foo": int, "bar": int}) +A1 = TypedDict("A1", {"foo": int, "bar": NotRequired[int]}) +A2 = TypedDict("A2", {"foo": NotRequired[int], "bar": int}) +B = TypedDict("B", {"foo": int}) +C = TypedDict("C", {"bar": int}) + +a = A({"foo": 1, "bar": 2}) +u: Union[B, C] +a.update(u) # E: Argument 1 to "update" of "TypedDict" has incompatible type "Union[B, C]"; expected "Union[TypedDict({'foo': int, 'bar'?: int}), TypedDict({'foo'?: int, 'bar': int})]" +u2: Union[A1, A2] +a.update(u2) # OK +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackSame] +# flags: --extra-checks +from typing import TypedDict + +class Foo(TypedDict): + a: int + b: int + +foo1: Foo = {"a": 1, "b": 1} +foo2: Foo = {**foo1, "b": 2} +foo3 = Foo(**foo1, b=2) +foo4 = Foo({**foo1, "b": 2}) +foo5 = Foo(dict(**foo1, b=2)) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackCompatible] +# flags: --extra-checks +from typing import TypedDict + +class Foo(TypedDict): + a: int + +class Bar(TypedDict): + a: int + b: int + +foo: Foo = {"a": 1} +bar: Bar = {**foo, "b": 2} +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackIncompatible] +from typing import TypedDict + +class Foo(TypedDict): + a: int + b: str + +class Bar(TypedDict): + a: int + b: int + +foo: Foo = {"a": 1, "b": "a"} +bar1: Bar = {**foo, "b": 2} # Incompatible item is overriden +bar2: Bar = {**foo, "a": 2} # E: Incompatible types (expression has type "str", TypedDict item "b" has type "int") +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackNotRequiredKeyIncompatible] +from typing import TypedDict, NotRequired + +class Foo(TypedDict): + a: NotRequired[str] + +class Bar(TypedDict): + a: NotRequired[int] + +foo: Foo = {} +bar: Bar = {**foo} # E: Incompatible types (expression has type "str", TypedDict item "a" has type "int") +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + + +[case testTypedDictUnpackMissingOrExtraKey] +from typing import TypedDict + +class Foo(TypedDict): + a: int + +class Bar(TypedDict): + a: int + b: int + +foo1: Foo = {"a": 1} +bar1: Bar = {"a": 1, "b": 1} +foo2: Foo = {**bar1} # E: Extra key "b" for TypedDict "Foo" +bar2: Bar = {**foo1} # E: Missing key "b" for TypedDict "Bar" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackNotRequiredKeyExtra] +from typing import TypedDict, NotRequired + +class Foo(TypedDict): + a: int + +class Bar(TypedDict): + a: int + b: NotRequired[int] + +foo1: Foo = {"a": 1} +bar1: Bar = {"a": 1} +foo2: Foo = {**bar1} # E: Extra key "b" for TypedDict "Foo" +bar2: Bar = {**foo1} +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackRequiredKeyMissing] +from typing import TypedDict, NotRequired + +class Foo(TypedDict): + a: NotRequired[int] + +class Bar(TypedDict): + a: int + +foo: Foo = {"a": 1} +bar: Bar = {**foo} # E: Missing key "a" for TypedDict "Bar" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackMultiple] +# flags: --extra-checks +from typing import TypedDict + +class Foo(TypedDict): + a: int + +class Bar(TypedDict): + b: int + +class Baz(TypedDict): + a: int + b: int + c: int + +foo: Foo = {"a": 1} +bar: Bar = {"b": 1} +baz: Baz = {**foo, **bar, "c": 1} +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackNested] +from typing import TypedDict + +class Foo(TypedDict): + a: int + b: int + +class Bar(TypedDict): + c: Foo + d: int + +foo: Foo = {"a": 1, "b": 1} +bar: Bar = {"c": foo, "d": 1} +bar2: Bar = {**bar, "c": {**bar["c"], "b": 2}, "d": 2} +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackNestedError] +from typing import TypedDict + +class Foo(TypedDict): + a: int + b: int + +class Bar(TypedDict): + c: Foo + d: int + +foo: Foo = {"a": 1, "b": 1} +bar: Bar = {"c": foo, "d": 1} +bar2: Bar = {**bar, "c": {**bar["c"], "b": "wrong"}, "d": 2} # E: Incompatible types (expression has type "str", TypedDict item "b" has type "int") +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackOverrideRequired] +from mypy_extensions import TypedDict + +Details = TypedDict('Details', {'first_name': str, 'last_name': str}) +DetailsSubset = TypedDict('DetailsSubset', {'first_name': str, 'last_name': str}, total=False) +defaults: Details = {'first_name': 'John', 'last_name': 'Luther'} + +def generate(data: DetailsSubset) -> Details: + return {**defaults, **data} # OK +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackUntypedDict] +from typing import Any, Dict, TypedDict + +class Bar(TypedDict): + pass + +foo: Dict[str, Any] = {} +bar: Bar = {**foo} # E: Unsupported type "Dict[str, Any]" for ** expansion in TypedDict +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackIntoUnion] +from typing import TypedDict, Union + +class Foo(TypedDict): + a: int + +class Bar(TypedDict): + b: int + +foo: Foo = {'a': 1} +foo_or_bar: Union[Foo, Bar] = {**foo} +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackFromUnion] +from typing import TypedDict, Union + +class Foo(TypedDict): + a: int + b: int + +class Bar(TypedDict): + b: int + +foo_or_bar: Union[Foo, Bar] = {'b': 1} +foo: Bar = {**foo_or_bar} # E: Extra key "a" for TypedDict "Bar" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackUnionRequiredMissing] +from typing import TypedDict, NotRequired, Union + +class Foo(TypedDict): + a: int + b: int + +class Bar(TypedDict): + a: int + b: NotRequired[int] + +foo_or_bar: Union[Foo, Bar] = {"a": 1} +foo: Foo = {**foo_or_bar} # E: Missing key "b" for TypedDict "Foo" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackInference] +from typing import TypedDict, Generic, TypeVar + +class Foo(TypedDict): + a: int + b: str + +T = TypeVar("T") +class TD(TypedDict, Generic[T]): + a: T + b: str + +foo: Foo +bar = TD(**foo) +reveal_type(bar) # N: Revealed type is "TypedDict('__main__.TD', {'a': builtins.int, 'b': builtins.str})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackStrictMode] +# flags: --extra-checks +from typing import TypedDict, NotRequired + +class Foo(TypedDict): + a: int + +class Bar(TypedDict): + a: int + b: NotRequired[int] + +foo: Foo +bar: Bar = {**foo} # E: Non-required key "b" not explicitly found in any ** item +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackAny] +from typing import Any, TypedDict, NotRequired, Dict, Union + +class Foo(TypedDict): + a: int + b: NotRequired[int] + +x: Any +y: Dict[Any, Any] +z: Union[Any, Dict[Any, Any]] +t1: Foo = {**x} # E: Missing key "a" for TypedDict "Foo" +t2: Foo = {**y} # E: Missing key "a" for TypedDict "Foo" +t3: Foo = {**z} # E: Missing key "a" for TypedDict "Foo" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackError] +from typing import TypedDict + +class Foo(TypedDict): + a: int + +def foo(x: int) -> Foo: ... + +f: Foo = {**foo("no")} # E: Argument 1 to "foo" has incompatible type "str"; expected "int" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + + +[case testTypedDictWith__or__method] +from typing import Dict +from mypy_extensions import TypedDict + +class Foo(TypedDict): + key: int + +foo1: Foo = {'key': 1} +foo2: Foo = {'key': 2} + +reveal_type(foo1 | foo2) # N: Revealed type is "TypedDict('__main__.Foo', {'key': builtins.int})" +reveal_type(foo1 | {'key': 1}) # N: Revealed type is "TypedDict('__main__.Foo', {'key': builtins.int})" +reveal_type(foo1 | {'key': 'a'}) # N: Revealed type is "builtins.dict[builtins.str, builtins.object]" +reveal_type(foo1 | {}) # N: Revealed type is "TypedDict('__main__.Foo', {'key': builtins.int})" + +d1: Dict[str, int] +d2: Dict[int, str] + +reveal_type(foo1 | d1) # N: Revealed type is "builtins.dict[builtins.str, builtins.object]" +foo1 | d2 # E: Unsupported operand types for | ("Foo" and "Dict[int, str]") + + +class Bar(TypedDict): + key: int + value: str + +bar: Bar +reveal_type(bar | {}) # N: Revealed type is "TypedDict('__main__.Bar', {'key': builtins.int, 'value': builtins.str})" +reveal_type(bar | {'key': 1, 'value': 'v'}) # N: Revealed type is "TypedDict('__main__.Bar', {'key': builtins.int, 'value': builtins.str})" +reveal_type(bar | {'key': 1}) # N: Revealed type is "TypedDict('__main__.Bar', {'key': builtins.int, 'value': builtins.str})" +reveal_type(bar | {'value': 'v'}) # N: Revealed type is "TypedDict('__main__.Bar', {'key': builtins.int, 'value': builtins.str})" +reveal_type(bar | {'key': 'a'}) # N: Revealed type is "builtins.dict[builtins.str, builtins.object]" +reveal_type(bar | {'value': 1}) # N: Revealed type is "builtins.dict[builtins.str, builtins.object]" +reveal_type(bar | {'key': 'a', 'value': 1}) # N: Revealed type is "builtins.dict[builtins.str, builtins.object]" + +reveal_type(bar | foo1) # N: Revealed type is "TypedDict('__main__.Bar', {'key': builtins.int, 'value': builtins.str})" +reveal_type(bar | d1) # N: Revealed type is "builtins.dict[builtins.str, builtins.object]" +bar | d2 # E: Unsupported operand types for | ("Bar" and "Dict[int, str]") +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict-iror.pyi] + +[case testTypedDictWith__or__method_error] +from mypy_extensions import TypedDict + +class Foo(TypedDict): + key: int + +foo: Foo = {'key': 1} +foo | 1 + +class SubDict(dict): ... +foo | SubDict() +[out] +main:7: error: No overload variant of "__or__" of "TypedDict" matches argument type "int" +main:7: note: Possible overload variants: +main:7: note: def __or__(self, TypedDict({'key'?: int}), /) -> Foo +main:7: note: def __or__(self, Dict[str, Any], /) -> Dict[str, object] +main:10: error: No overload variant of "__ror__" of "dict" matches argument type "Foo" +main:10: note: Possible overload variants: +main:10: note: def __ror__(self, Dict[Any, Any], /) -> Dict[Any, Any] +main:10: note: def [T, T2] __ror__(self, Dict[T, T2], /) -> Dict[Union[Any, T], Union[Any, T2]] +[builtins fixtures/dict-full.pyi] +[typing fixtures/typing-typeddict-iror.pyi] + +[case testTypedDictWith__ror__method] +from typing import Dict +from mypy_extensions import TypedDict + +class Foo(TypedDict): + key: int + +foo: Foo = {'key': 1} + +reveal_type({'key': 1} | foo) # N: Revealed type is "TypedDict('__main__.Foo', {'key': builtins.int})" +reveal_type({'key': 'a'} | foo) # N: Revealed type is "builtins.dict[builtins.str, builtins.object]" +reveal_type({} | foo) # N: Revealed type is "TypedDict('__main__.Foo', {'key': builtins.int})" +{1: 'a'} | foo # E: Dict entry 0 has incompatible type "int": "str"; expected "str": "Any" + +d1: Dict[str, int] +d2: Dict[int, str] + +reveal_type(d1 | foo) # N: Revealed type is "builtins.dict[builtins.str, builtins.object]" +d2 | foo # E: Unsupported operand types for | ("Dict[int, str]" and "Foo") +1 | foo # E: Unsupported left operand type for | ("int") + + +class Bar(TypedDict): + key: int + value: str + +bar: Bar +reveal_type({} | bar) # N: Revealed type is "TypedDict('__main__.Bar', {'key': builtins.int, 'value': builtins.str})" +reveal_type({'key': 1, 'value': 'v'} | bar) # N: Revealed type is "TypedDict('__main__.Bar', {'key': builtins.int, 'value': builtins.str})" +reveal_type({'key': 1} | bar) # N: Revealed type is "TypedDict('__main__.Bar', {'key': builtins.int, 'value': builtins.str})" +reveal_type({'value': 'v'} | bar) # N: Revealed type is "TypedDict('__main__.Bar', {'key': builtins.int, 'value': builtins.str})" +reveal_type({'key': 'a'} | bar) # N: Revealed type is "builtins.dict[builtins.str, builtins.object]" +reveal_type({'value': 1} | bar) # N: Revealed type is "builtins.dict[builtins.str, builtins.object]" +reveal_type({'key': 'a', 'value': 1} | bar) # N: Revealed type is "builtins.dict[builtins.str, builtins.object]" + +reveal_type(d1 | bar) # N: Revealed type is "builtins.dict[builtins.str, builtins.object]" +d2 | bar # E: Unsupported operand types for | ("Dict[int, str]" and "Bar") +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict-iror.pyi] + +[case testTypedDictWith__ior__method] +from typing import Dict +from mypy_extensions import TypedDict + +class Foo(TypedDict): + key: int + +foo: Foo = {'key': 1} +foo |= {'key': 2} + +foo |= {} +foo |= {'key': 'a', 'b': 'a'} # E: Expected TypedDict key "key" but found keys ("key", "b") \ + # E: Incompatible types (expression has type "str", TypedDict item "key" has type "int") +foo |= {'b': 2} # E: Unexpected TypedDict key "b" + +d1: Dict[str, int] +d2: Dict[int, str] + +foo |= d1 # E: Argument 1 to "__ior__" of "TypedDict" has incompatible type "Dict[str, int]"; expected "TypedDict({'key'?: int})" +foo |= d2 # E: Argument 1 to "__ior__" of "TypedDict" has incompatible type "Dict[int, str]"; expected "TypedDict({'key'?: int})" + + +class Bar(TypedDict): + key: int + value: str + +bar: Bar +bar |= {} +bar |= {'key': 1, 'value': 'a'} +bar |= {'key': 'a', 'value': 'a', 'b': 'a'} # E: Expected TypedDict keys ("key", "value") but found keys ("key", "value", "b") \ + # E: Incompatible types (expression has type "str", TypedDict item "key" has type "int") + +bar |= foo +bar |= d1 # E: Argument 1 to "__ior__" of "TypedDict" has incompatible type "Dict[str, int]"; expected "TypedDict({'key'?: int, 'value'?: str})" +bar |= d2 # E: Argument 1 to "__ior__" of "TypedDict" has incompatible type "Dict[int, str]"; expected "TypedDict({'key'?: int, 'value'?: str})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict-iror.pyi] + +[case testGenericTypedDictStrictOptionalExtending] +from typing import Generic, TypeVar, TypedDict, Optional + +T = TypeVar("T") +class Foo(TypedDict, Generic[T], total=False): + a: Optional[str] + g: Optional[T] + +class Bar(Foo[T], total=False): + other: str + +b: Bar[int] +reveal_type(b["a"]) # N: Revealed type is "Union[builtins.str, None]" +reveal_type(b["g"]) # N: Revealed type is "Union[builtins.int, None]" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testNoCrashOnUnImportedAnyNotRequired] +# flags: --disallow-any-unimported +from typing import NotRequired, Required, TypedDict +from thismoduledoesntexist import T # type: ignore[import] + +B = TypedDict("B", { # E: Type of a TypedDict key becomes "Any" due to an unfollowed import + "T1": NotRequired[T], + "T2": Required[T], +}) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictWithClassLevelKeywords] +from typing import TypedDict, Generic, TypeVar + +T = TypeVar('T') + +class Meta(type): ... + +class WithMetaKeyword(TypedDict, metaclass=Meta): # E: Unexpected keyword argument "metaclass" for "__init_subclass__" of "TypedDict" + ... + +class GenericWithMetaKeyword(TypedDict, Generic[T], metaclass=Meta): # E: Unexpected keyword argument "metaclass" for "__init_subclass__" of "TypedDict" + ... + +# We still don't allow this, because the implementation is much easier +# and it does not make any practical sense to do it: +class WithTypeMeta(TypedDict, metaclass=type): # E: Unexpected keyword argument "metaclass" for "__init_subclass__" of "TypedDict" + ... + +class OtherKeywords(TypedDict, a=1, b=2, c=3, total=True): # E: Unexpected keyword argument "a" for "__init_subclass__" of "TypedDict" \ + # E: Unexpected keyword argument "b" for "__init_subclass__" of "TypedDict" \ + # E: Unexpected keyword argument "c" for "__init_subclass__" of "TypedDict" + ... + +class TotalInTheMiddle(TypedDict, a=1, total=True, b=2, c=3): # E: Unexpected keyword argument "a" for "__init_subclass__" of "TypedDict" \ + # E: Unexpected keyword argument "b" for "__init_subclass__" of "TypedDict" \ + # E: Unexpected keyword argument "c" for "__init_subclass__" of "TypedDict" + ... +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testCanCreateClassWithFunctionBasedTypedDictBase] +from mypy_extensions import TypedDict + +class Params(TypedDict("Params", {'x': int})): + pass + +p: Params = {'x': 2} +reveal_type(p) # N: Revealed type is "TypedDict('__main__.Params', {'x': builtins.int})" +[builtins fixtures/dict.pyi] + +[case testInitTypedDictFromType] +from typing import TypedDict, Type +from typing_extensions import Required + +class Point(TypedDict, total=False): + x: Required[int] + y: int + +def func(cls: Type[Point]) -> None: + reveal_type(cls) # N: Revealed type is "Type[TypedDict('__main__.Point', {'x': builtins.int, 'y'?: builtins.int})]" + cls(x=1, y=2) + cls(1, 2) # E: Too many positional arguments + cls(x=1) + cls(y=2) # E: Missing named argument "x" + cls(x=1, y=2, error="") # E: Unexpected keyword argument "error" +[typing fixtures/typing-full.pyi] +[builtins fixtures/tuple.pyi] + +[case testInitTypedDictFromTypeGeneric] +from typing import Generic, TypedDict, Type, TypeVar +from typing_extensions import Required + +class Point(TypedDict, total=False): + x: Required[int] + y: int + +T = TypeVar("T", bound=Point) + +class A(Generic[T]): + def __init__(self, a: Type[T]) -> None: + self.a = a + + def func(self) -> T: + reveal_type(self.a) # N: Revealed type is "Type[T`1]" + self.a(x=1, y=2) + self.a(y=2) # E: Missing named argument "x" + return self.a(x=1) +[typing fixtures/typing-full.pyi] +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-typeguard.test b/test-data/unit/check-typeguard.test index 39bcb091f09e..e1b7a86aba63 100644 --- a/test-data/unit/check-typeguard.test +++ b/test-data/unit/check-typeguard.test @@ -9,6 +9,17 @@ def main(a: object) -> None: reveal_type(a) # N: Revealed type is "builtins.object" [builtins fixtures/tuple.pyi] +[case testTypeGuardStringified] +from typing_extensions import TypeGuard +class Point: pass +def is_point(a: object) -> "TypeGuard[Point]": pass +def main(a: object) -> None: + if is_point(a): + reveal_type(a) # N: Revealed type is "__main__.Point" + else: + reveal_type(a) # N: Revealed type is "builtins.object" +[builtins fixtures/tuple.pyi] + [case testTypeGuardTypeArgsNone] from typing_extensions import TypeGuard def foo(a: object) -> TypeGuard: # E: TypeGuard must have exactly one type argument @@ -54,6 +65,18 @@ def main(a: object, b: object) -> None: reveal_type(b) # N: Revealed type is "builtins.object" [builtins fixtures/tuple.pyi] +[case testTypeGuardTypeVarReturn] +from typing import Callable, Optional, TypeVar +from typing_extensions import TypeGuard +T = TypeVar('T') +def is_str(x: object) -> TypeGuard[str]: pass +def main(x: object, type_check_func: Callable[[object], TypeGuard[T]]) -> T: + if not type_check_func(x): + raise Exception() + return x +reveal_type(main("a", is_str)) # N: Revealed type is "builtins.str" +[builtins fixtures/exception.pyi] + [case testTypeGuardIsBool] from typing_extensions import TypeGuard def f(a: TypeGuard[int]) -> None: pass @@ -248,7 +271,6 @@ def main1(a: object) -> None: [builtins fixtures/tuple.pyi] [case testTypeGuardOverload] -# flags: --strict-optional from typing import overload, Any, Callable, Iterable, Iterator, List, Optional, TypeVar from typing_extensions import TypeGuard @@ -505,7 +527,7 @@ def with_bool(o: object) -> bool: pass accepts_typeguard(with_bool_typeguard) accepts_typeguard(with_str_typeguard) -accepts_typeguard(with_bool) # E: Argument 1 to "accepts_typeguard" has incompatible type "Callable[[object], bool]"; expected "Callable[[object], TypeGuard[bool]]" +accepts_typeguard(with_bool) # E: Argument 1 to "accepts_typeguard" has incompatible type "Callable[[object], bool]"; expected "Callable[[object], TypeGuard[Never]]" [builtins fixtures/tuple.pyi] [case testTypeGuardAsOverloadedFunctionArg] @@ -602,12 +624,26 @@ def func(names: Tuple[str, ...]): from typing_extensions import TypeGuard class Z: - def typeguard(self, *, x: object) -> TypeGuard[int]: # E: TypeGuard functions must have a positional argument + def typeguard1(self, *, x: object) -> TypeGuard[int]: # line 4 + ... + + @staticmethod + def typeguard2(x: object) -> TypeGuard[int]: + ... + + @staticmethod # line 11 + def typeguard3(*, x: object) -> TypeGuard[int]: ... -def bad_typeguard(*, x: object) -> TypeGuard[int]: # E: TypeGuard functions must have a positional argument +def bad_typeguard(*, x: object) -> TypeGuard[int]: # line 15 ... -[builtins fixtures/tuple.pyi] + +# In Python 3.8 the line number associated with FunctionDef nodes changed +[builtins fixtures/classmethod.pyi] +[out] +main:4: error: TypeGuard functions must have a positional argument +main:12: error: TypeGuard functions must have a positional argument +main:15: error: TypeGuard functions must have a positional argument [case testTypeGuardWithKeywordArg] from typing_extensions import TypeGuard @@ -640,7 +676,6 @@ if Y().typeguard(x): reveal_type(x) # N: Revealed type is "builtins.int" if Y.typeguard(x): reveal_type(x) # N: Revealed type is "builtins.int" -[builtins fixtures/tuple.pyi] [builtins fixtures/classmethod.pyi] [case testTypeGuardKwargFollowingThroughOverloaded] @@ -671,3 +706,29 @@ if typeguard(x=x, y="42"): if typeguard(y="42", x=x): reveal_type(x) # N: Revealed type is "builtins.str" [builtins fixtures/tuple.pyi] + +[case testGenericAliasWithTypeGuard] +from typing import Callable, List, TypeVar +from typing_extensions import TypeGuard, TypeAlias + +A = Callable[[object], TypeGuard[List[T]]] +def foo(x: object) -> TypeGuard[List[str]]: ... + +def test(f: A[T]) -> T: ... +reveal_type(test(foo)) # N: Revealed type is "builtins.str" +[builtins fixtures/list.pyi] + +[case testNoCrashOnDunderCallTypeGuard] +from typing_extensions import TypeGuard + +class A: + def __call__(self, x) -> TypeGuard[int]: + return True + +a: A +assert a(x=1) + +x: object +assert a(x=x) +reveal_type(x) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-typeis.test b/test-data/unit/check-typeis.test new file mode 100644 index 000000000000..83467d5e3683 --- /dev/null +++ b/test-data/unit/check-typeis.test @@ -0,0 +1,821 @@ +[case testTypeIsBasic] +from typing_extensions import TypeIs +class Point: pass +def is_point(a: object) -> TypeIs[Point]: pass +def main(a: object) -> None: + if is_point(a): + reveal_type(a) # N: Revealed type is "__main__.Point" + else: + reveal_type(a) # N: Revealed type is "builtins.object" +[builtins fixtures/tuple.pyi] + +[case testTypeIsStringified] +from typing_extensions import TypeIs +class Point: pass +def is_point(a: object) -> "TypeIs[Point]": pass +def main(a: object) -> None: + if is_point(a): + reveal_type(a) # N: Revealed type is "__main__.Point" + else: + reveal_type(a) # N: Revealed type is "builtins.object" +[builtins fixtures/tuple.pyi] + +[case testTypeIsElif] +from typing_extensions import TypeIs +from typing import Union +class Point: pass +def is_point(a: object) -> TypeIs[Point]: pass +class Line: pass +def is_line(a: object) -> TypeIs[Line]: pass +def main(a: Union[Point, Line, int]) -> None: + if is_point(a): + reveal_type(a) # N: Revealed type is "__main__.Point" + elif is_line(a): + reveal_type(a) # N: Revealed type is "__main__.Line" + else: + reveal_type(a) # N: Revealed type is "builtins.int" + +[builtins fixtures/tuple.pyi] + +[case testTypeIsTypeArgsNone] +from typing_extensions import TypeIs +def foo(a: object) -> TypeIs: # E: TypeIs must have exactly one type argument + pass +[builtins fixtures/tuple.pyi] + +[case testTypeIsTypeArgsTooMany] +from typing_extensions import TypeIs +def foo(a: object) -> TypeIs[int, int]: # E: TypeIs must have exactly one type argument + pass +[builtins fixtures/tuple.pyi] + +[case testTypeIsTypeArgType] +from typing_extensions import TypeIs +def foo(a: object) -> TypeIs[42]: # E: Invalid type: try using Literal[42] instead? + pass +[builtins fixtures/tuple.pyi] + +[case testTypeIsRepr] +from typing_extensions import TypeIs +def foo(a: object) -> TypeIs[int]: + pass +reveal_type(foo) # N: Revealed type is "def (a: builtins.object) -> TypeIs[builtins.int]" +[builtins fixtures/tuple.pyi] + +[case testTypeIsCallArgsNone] +from typing_extensions import TypeIs +class Point: pass + +def is_point() -> TypeIs[Point]: pass # E: "TypeIs" functions must have a positional argument +def main(a: object) -> None: + if is_point(): + reveal_type(a) # N: Revealed type is "builtins.object" +[builtins fixtures/tuple.pyi] + +[case testTypeIsCallArgsMultiple] +from typing_extensions import TypeIs +class Point: pass +def is_point(a: object, b: object) -> TypeIs[Point]: pass +def main(a: object, b: object) -> None: + if is_point(a, b): + reveal_type(a) # N: Revealed type is "__main__.Point" + reveal_type(b) # N: Revealed type is "builtins.object" +[builtins fixtures/tuple.pyi] + +[case testTypeIsIsBool] +from typing_extensions import TypeIs +def f(a: TypeIs[int]) -> None: pass +reveal_type(f) # N: Revealed type is "def (a: builtins.bool)" +a: TypeIs[int] +reveal_type(a) # N: Revealed type is "builtins.bool" +class C: + a: TypeIs[int] +reveal_type(C().a) # N: Revealed type is "builtins.bool" +[builtins fixtures/tuple.pyi] + +[case testTypeIsWithTypeVar] +from typing import TypeVar, Tuple, Type +from typing_extensions import TypeIs +T = TypeVar('T') +def is_tuple_of_type(a: Tuple[object, ...], typ: Type[T]) -> TypeIs[Tuple[T, ...]]: pass +def main(a: Tuple[object, ...]): + if is_tuple_of_type(a, int): + reveal_type(a) # N: Revealed type is "builtins.tuple[builtins.int, ...]" +[builtins fixtures/tuple.pyi] + +[case testTypeIsTypeVarReturn] +from typing import Callable, Optional, TypeVar +from typing_extensions import TypeIs +T = TypeVar('T') +def is_str(x: object) -> TypeIs[str]: pass +def main(x: object, type_check_func: Callable[[object], TypeIs[T]]) -> T: + if not type_check_func(x): + raise Exception() + return x +reveal_type(main("a", is_str)) # N: Revealed type is "builtins.str" +[builtins fixtures/exception.pyi] + +[case testTypeIsUnionIn] +from typing import Union +from typing_extensions import TypeIs +def is_foo(a: Union[int, str]) -> TypeIs[str]: pass +def main(a: Union[str, int]) -> None: + if is_foo(a): + reveal_type(a) # N: Revealed type is "builtins.str" + else: + reveal_type(a) # N: Revealed type is "builtins.int" + reveal_type(a) # N: Revealed type is "Union[builtins.str, builtins.int]" +[builtins fixtures/tuple.pyi] + +[case testTypeIsUnionOut] +from typing import Union +from typing_extensions import TypeIs +def is_foo(a: object) -> TypeIs[Union[int, str]]: pass +def main(a: object) -> None: + if is_foo(a): + reveal_type(a) # N: Revealed type is "Union[builtins.int, builtins.str]" +[builtins fixtures/tuple.pyi] + +[case testTypeIsNonzeroFloat] +from typing_extensions import TypeIs +def is_nonzero(a: object) -> TypeIs[float]: pass +def main(a: int): + if is_nonzero(a): + reveal_type(a) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] + +[case testTypeIsHigherOrder] +from typing import Callable, TypeVar, Iterable, List +from typing_extensions import TypeIs +T = TypeVar('T') +R = TypeVar('R') +def filter(f: Callable[[T], TypeIs[R]], it: Iterable[T]) -> Iterable[R]: pass +def is_float(a: object) -> TypeIs[float]: pass +a: List[object] = ["a", 0, 0.0] +b = filter(is_float, a) +reveal_type(b) # N: Revealed type is "typing.Iterable[builtins.float]" +[builtins fixtures/tuple.pyi] + +[case testTypeIsMethod] +from typing_extensions import TypeIs +class C: + def main(self, a: object) -> None: + if self.is_float(a): + reveal_type(self) # N: Revealed type is "__main__.C" + reveal_type(a) # N: Revealed type is "builtins.float" + def is_float(self, a: object) -> TypeIs[float]: pass +[builtins fixtures/tuple.pyi] + +[case testTypeIsCrossModule] +import guard +from points import Point +def main(a: object) -> None: + if guard.is_point(a): + reveal_type(a) # N: Revealed type is "points.Point" +[file guard.py] +from typing_extensions import TypeIs +import points +def is_point(a: object) -> TypeIs[points.Point]: pass +[file points.py] +class Point: pass +[builtins fixtures/tuple.pyi] + +[case testTypeIsBodyRequiresBool] +from typing_extensions import TypeIs +def is_float(a: object) -> TypeIs[float]: + return "not a bool" # E: Incompatible return value type (got "str", expected "bool") +[builtins fixtures/tuple.pyi] + +[case testTypeIsNarrowToTypedDict] +from typing import Mapping, TypedDict +from typing_extensions import TypeIs +class User(TypedDict): + name: str + id: int +def is_user(a: Mapping[str, object]) -> TypeIs[User]: + return isinstance(a.get("name"), str) and isinstance(a.get("id"), int) +def main(a: Mapping[str, object]) -> None: + if is_user(a): + reveal_type(a) # N: Revealed type is "TypedDict('__main__.User', {'name': builtins.str, 'id': builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypeIsInAssert] +from typing_extensions import TypeIs +def is_float(a: object) -> TypeIs[float]: pass +def main(a: object) -> None: + assert is_float(a) + reveal_type(a) # N: Revealed type is "builtins.float" +[builtins fixtures/tuple.pyi] + +[case testTypeIsFromAny] +from typing import Any +from typing_extensions import TypeIs +def is_objfloat(a: object) -> TypeIs[float]: pass +def is_anyfloat(a: Any) -> TypeIs[float]: pass +def objmain(a: object) -> None: + if is_objfloat(a): + reveal_type(a) # N: Revealed type is "builtins.float" + if is_anyfloat(a): + reveal_type(a) # N: Revealed type is "builtins.float" +def anymain(a: Any) -> None: + if is_objfloat(a): + reveal_type(a) # N: Revealed type is "builtins.float" + if is_anyfloat(a): + reveal_type(a) # N: Revealed type is "builtins.float" +[builtins fixtures/tuple.pyi] + +[case testTypeIsNegatedAndElse] +from typing import Union +from typing_extensions import TypeIs +def is_int(a: object) -> TypeIs[int]: pass +def is_str(a: object) -> TypeIs[str]: pass +def intmain(a: Union[int, str]) -> None: + if not is_int(a): + reveal_type(a) # N: Revealed type is "builtins.str" + else: + reveal_type(a) # N: Revealed type is "builtins.int" +def strmain(a: Union[int, str]) -> None: + if is_str(a): + reveal_type(a) # N: Revealed type is "builtins.str" + else: + reveal_type(a) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] + +[case testTypeIsClassMethod] +from typing_extensions import TypeIs +class C: + @classmethod + def is_float(cls, a: object) -> TypeIs[float]: pass + def method(self, a: object) -> None: + if self.is_float(a): + reveal_type(a) # N: Revealed type is "builtins.float" +def main(a: object) -> None: + if C.is_float(a): + reveal_type(a) # N: Revealed type is "builtins.float" +[builtins fixtures/classmethod.pyi] + +[case testTypeIsRequiresPositionalArgs] +from typing_extensions import TypeIs +def is_float(a: object, b: object = 0) -> TypeIs[float]: pass +def main1(a: object) -> None: + if is_float(a=a, b=1): + reveal_type(a) # N: Revealed type is "builtins.float" + + if is_float(b=1, a=a): + reveal_type(a) # N: Revealed type is "builtins.float" + +[builtins fixtures/tuple.pyi] + +[case testTypeIsOverload] +from typing import overload, Any, Callable, Iterable, Iterator, List, Optional, TypeVar +from typing_extensions import TypeIs + +T = TypeVar("T") +R = TypeVar("R") + +@overload +def filter(f: Callable[[T], TypeIs[R]], it: Iterable[T]) -> Iterator[R]: ... +@overload +def filter(f: Callable[[T], bool], it: Iterable[T]) -> Iterator[T]: ... +def filter(*args): pass + +def is_int_typeis(a: object) -> TypeIs[int]: pass +def is_int_bool(a: object) -> bool: pass + +def main(a: List[Optional[int]]) -> None: + bb = filter(lambda x: x is not None, a) + reveal_type(bb) # N: Revealed type is "typing.Iterator[Union[builtins.int, None]]" + # Also, if you replace 'bool' with 'Any' in the second overload, bb is Iterator[Any] + cc = filter(is_int_typeis, a) + reveal_type(cc) # N: Revealed type is "typing.Iterator[builtins.int]" + dd = filter(is_int_bool, a) + reveal_type(dd) # N: Revealed type is "typing.Iterator[Union[builtins.int, None]]" + +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] + +[case testTypeIsDecorated] +from typing import TypeVar +from typing_extensions import TypeIs +T = TypeVar("T") +def decorator(f: T) -> T: pass +@decorator +def is_float(a: object) -> TypeIs[float]: + pass +def main(a: object) -> None: + if is_float(a): + reveal_type(a) # N: Revealed type is "builtins.float" +[builtins fixtures/tuple.pyi] + +[case testTypeIsMethodOverride] +from typing_extensions import TypeIs +class C: + def is_float(self, a: object) -> TypeIs[float]: pass +class D(C): + def is_float(self, a: object) -> bool: pass # Fail +[builtins fixtures/tuple.pyi] +[out] +main:5: error: Signature of "is_float" incompatible with supertype "C" +main:5: note: Superclass: +main:5: note: def is_float(self, a: object) -> TypeIs[float] +main:5: note: Subclass: +main:5: note: def is_float(self, a: object) -> bool + +[case testTypeIsInAnd] +from typing import Any +from typing_extensions import TypeIs +def isclass(a: object) -> bool: + pass +def isfloat(a: object) -> TypeIs[float]: + pass +def isstr(a: object) -> TypeIs[str]: + pass + +def coverage1(obj: Any) -> bool: + if isfloat(obj) and obj.__self__ is not None and isclass(obj.__self__): # E: "float" has no attribute "__self__" + reveal_type(obj) # N: Revealed type is "builtins.float" + return True + reveal_type(obj) # N: Revealed type is "Any" + return False + +def coverage2(obj: Any) -> bool: + if not (isfloat(obj) or isstr(obj)): + reveal_type(obj) # N: Revealed type is "Any" + return True + reveal_type(obj) # N: Revealed type is "Union[builtins.float, builtins.str]" + return False +[builtins fixtures/classmethod.pyi] + +[case testAssignToTypeIsedVariable1] +from typing_extensions import TypeIs + +class A: pass +class B(A): pass + +def guard(a: A) -> TypeIs[B]: + pass + +a = A() +if not guard(a): + a = A() +[builtins fixtures/tuple.pyi] + +[case testAssignToTypeIsedVariable2] +from typing_extensions import TypeIs + +class A: pass +class B: pass + +def guard(a: object) -> TypeIs[B]: + pass + +a = A() +if not guard(a): + a = A() +[builtins fixtures/tuple.pyi] + +[case testAssignToTypeIsedVariable3] +from typing_extensions import TypeIs + +class A: pass +class B: pass + +def guard(a: object) -> TypeIs[B]: + pass + +a = A() +if guard(a): + reveal_type(a) # N: Revealed type is "__main__." + a = B() # E: Incompatible types in assignment (expression has type "B", variable has type "A") + reveal_type(a) # N: Revealed type is "__main__." + a = A() + reveal_type(a) # N: Revealed type is "__main__.A" +reveal_type(a) # N: Revealed type is "__main__.A" +[builtins fixtures/tuple.pyi] + +[case testTypeIsNestedRestrictionAny] +from typing_extensions import TypeIs +from typing import Any + +class A: ... +def f(x: object) -> TypeIs[A]: ... +def g(x: object) -> None: ... + +def test(x: Any) -> None: + if not(f(x) or x): + return + g(reveal_type(x)) # N: Revealed type is "Union[__main__.A, Any]" +[builtins fixtures/tuple.pyi] + +[case testTypeIsNestedRestrictionUnionOther] +from typing_extensions import TypeIs +from typing import Any + +class A: ... +class B: ... +def f(x: object) -> TypeIs[A]: ... +def f2(x: object) -> TypeIs[B]: ... +def g(x: object) -> None: ... + +def test(x: object) -> None: + if not(f(x) or f2(x)): + return + g(reveal_type(x)) # N: Revealed type is "Union[__main__.A, __main__.B]" +[builtins fixtures/tuple.pyi] + +[case testTypeIsComprehensionSubtype] +from typing import List +from typing_extensions import TypeIs + +class Base: ... +class Foo(Base): ... +class Bar(Base): ... + +def is_foo(item: object) -> TypeIs[Foo]: + return isinstance(item, Foo) + +def is_bar(item: object) -> TypeIs[Bar]: + return isinstance(item, Bar) + +def foobar(items: List[object]): + a: List[Base] = [x for x in items if is_foo(x) or is_bar(x)] + b: List[Base] = [x for x in items if is_foo(x)] + c: List[Foo] = [x for x in items if is_foo(x)] + d: List[Bar] = [x for x in items if is_foo(x)] # E: List comprehension has incompatible type List[Foo]; expected List[Bar] +[builtins fixtures/tuple.pyi] + +[case testTypeIsNestedRestrictionUnionIsInstance] +from typing_extensions import TypeIs +from typing import Any, List + +class A: ... +def f(x: List[Any]) -> TypeIs[List[str]]: ... +def g(x: object) -> None: ... + +def test(x: List[Any]) -> None: + if not(f(x) or isinstance(x, A)): + return + g(reveal_type(x)) # N: Revealed type is "Union[builtins.list[builtins.str], __main__.]" +[builtins fixtures/tuple.pyi] + +[case testTypeIsMultipleCondition] +from typing_extensions import TypeIs +from typing import Any, List + +class Foo: ... +class Bar: ... + +def is_foo(item: object) -> TypeIs[Foo]: + return isinstance(item, Foo) + +def is_bar(item: object) -> TypeIs[Bar]: + return isinstance(item, Bar) + +def foobar(x: object): + if not isinstance(x, Foo) or not isinstance(x, Bar): + return + reveal_type(x) # N: Revealed type is "__main__." + +def foobar_typeis(x: object): + if not is_foo(x) or not is_bar(x): + return + # Looks like a typo but this is what our unique name generation produces + reveal_type(x) # N: Revealed type is "__main__.1" +[builtins fixtures/tuple.pyi] + +[case testTypeIsAsFunctionArgAsBoolSubtype] +from typing import Callable +from typing_extensions import TypeIs + +def accepts_bool(f: Callable[[object], bool]): pass + +def with_bool_typeis(o: object) -> TypeIs[bool]: pass +def with_str_typeis(o: object) -> TypeIs[str]: pass +def with_bool(o: object) -> bool: pass + +accepts_bool(with_bool_typeis) +accepts_bool(with_str_typeis) +accepts_bool(with_bool) +[builtins fixtures/tuple.pyi] + +[case testTypeIsAsFunctionArg] +from typing import Callable +from typing_extensions import TypeIs + +def accepts_typeis(f: Callable[[object], TypeIs[bool]]): pass +def different_typeis(f: Callable[[object], TypeIs[str]]): pass + +def with_typeis(o: object) -> TypeIs[bool]: pass +def with_bool(o: object) -> bool: pass + +accepts_typeis(with_typeis) +accepts_typeis(with_bool) # E: Argument 1 to "accepts_typeis" has incompatible type "Callable[[object], bool]"; expected "Callable[[object], TypeIs[bool]]" + +different_typeis(with_typeis) # E: Argument 1 to "different_typeis" has incompatible type "Callable[[object], TypeIs[bool]]"; expected "Callable[[object], TypeIs[str]]" +different_typeis(with_bool) # E: Argument 1 to "different_typeis" has incompatible type "Callable[[object], bool]"; expected "Callable[[object], TypeIs[str]]" +[builtins fixtures/tuple.pyi] + +[case testTypeIsAsGenericFunctionArg] +from typing import Callable, TypeVar +from typing_extensions import TypeIs + +T = TypeVar('T') + +def accepts_typeis(f: Callable[[object], TypeIs[T]]): pass + +def with_bool_typeis(o: object) -> TypeIs[bool]: pass +def with_str_typeis(o: object) -> TypeIs[str]: pass +def with_bool(o: object) -> bool: pass + +accepts_typeis(with_bool_typeis) +accepts_typeis(with_str_typeis) +accepts_typeis(with_bool) # E: Argument 1 to "accepts_typeis" has incompatible type "Callable[[object], bool]"; expected "Callable[[object], TypeIs[Never]]" +[builtins fixtures/tuple.pyi] + +[case testTypeIsAsOverloadedFunctionArg] +# https://github.com/python/mypy/issues/11307 +from typing import Callable, TypeVar, Generic, Any, overload +from typing_extensions import TypeIs + +_T = TypeVar('_T') + +class filter(Generic[_T]): + @overload + def __init__(self, function: Callable[[object], TypeIs[_T]]) -> None: pass + @overload + def __init__(self, function: Callable[[_T], Any]) -> None: pass + def __init__(self, function): pass + +def is_int_typeis(a: object) -> TypeIs[int]: pass +def returns_bool(a: object) -> bool: pass + +reveal_type(filter(is_int_typeis)) # N: Revealed type is "__main__.filter[builtins.int]" +reveal_type(filter(returns_bool)) # N: Revealed type is "__main__.filter[builtins.object]" +[builtins fixtures/tuple.pyi] + +[case testTypeIsSubtypingVariance] +from typing import Callable +from typing_extensions import TypeIs + +class A: pass +class B(A): pass +class C(B): pass + +def accepts_typeis(f: Callable[[object], TypeIs[B]]): pass + +def with_typeis_a(o: object) -> TypeIs[A]: pass +def with_typeis_b(o: object) -> TypeIs[B]: pass +def with_typeis_c(o: object) -> TypeIs[C]: pass + +accepts_typeis(with_typeis_a) # E: Argument 1 to "accepts_typeis" has incompatible type "Callable[[object], TypeIs[A]]"; expected "Callable[[object], TypeIs[B]]" +accepts_typeis(with_typeis_b) +accepts_typeis(with_typeis_c) # E: Argument 1 to "accepts_typeis" has incompatible type "Callable[[object], TypeIs[C]]"; expected "Callable[[object], TypeIs[B]]" +[builtins fixtures/tuple.pyi] + +[case testTypeIsWithIdentityGeneric] +from typing import TypeVar +from typing_extensions import TypeIs + +_T = TypeVar("_T") + +def identity(val: _T) -> TypeIs[_T]: + pass + +def func1(name: _T): + reveal_type(name) # N: Revealed type is "_T`-1" + if identity(name): + reveal_type(name) # N: Revealed type is "_T`-1" + +def func2(name: str): + reveal_type(name) # N: Revealed type is "builtins.str" + if identity(name): + reveal_type(name) # N: Revealed type is "builtins.str" +[builtins fixtures/tuple.pyi] + +[case testTypeIsWithGenericOnSecondParam] +from typing import TypeVar +from typing_extensions import TypeIs + +_R = TypeVar("_R") + +def guard(val: object, param: _R) -> TypeIs[_R]: + pass + +def func1(name: object): + reveal_type(name) # N: Revealed type is "builtins.object" + if guard(name, name): + reveal_type(name) # N: Revealed type is "builtins.object" + if guard(name, 1): + reveal_type(name) # N: Revealed type is "builtins.int" + +def func2(name: int): + reveal_type(name) # N: Revealed type is "builtins.int" + if guard(name, True): + reveal_type(name) # N: Revealed type is "builtins.bool" +[builtins fixtures/tuple.pyi] + +[case testTypeIsWithGenericInstance] +from typing import TypeVar, List, Iterable +from typing_extensions import TypeIs + +_T = TypeVar("_T") + +def is_list_of_str(val: Iterable[_T]) -> TypeIs[List[_T]]: + pass + +def func(name: Iterable[str]): + reveal_type(name) # N: Revealed type is "typing.Iterable[builtins.str]" + if is_list_of_str(name): + reveal_type(name) # N: Revealed type is "builtins.list[builtins.str]" +[builtins fixtures/tuple.pyi] + +[case testTypeIsWithTupleGeneric] +from typing import TypeVar, Tuple +from typing_extensions import TypeIs + +_T = TypeVar("_T") + +def is_two_element_tuple(val: Tuple[_T, ...]) -> TypeIs[Tuple[_T, _T]]: + pass + +def func(names: Tuple[str, ...]): + reveal_type(names) # N: Revealed type is "builtins.tuple[builtins.str, ...]" + if is_two_element_tuple(names): + reveal_type(names) # N: Revealed type is "Tuple[builtins.str, builtins.str]" +[builtins fixtures/tuple.pyi] + +[case testTypeIsErroneousDefinitionFails] +from typing_extensions import TypeIs + +class Z: + def typeis1(self, *, x: object) -> TypeIs[int]: # E: "TypeIs" functions must have a positional argument + ... + + @staticmethod + def typeis2(x: object) -> TypeIs[int]: + ... + + @staticmethod + def typeis3(*, x: object) -> TypeIs[int]: # E: "TypeIs" functions must have a positional argument + ... + +def bad_typeis(*, x: object) -> TypeIs[int]: # E: "TypeIs" functions must have a positional argument + ... + +[builtins fixtures/classmethod.pyi] + +[case testTypeIsWithKeywordArg] +from typing_extensions import TypeIs + +class Z: + def typeis(self, x: object) -> TypeIs[int]: + ... + +def typeis(x: object) -> TypeIs[int]: + ... + +n: object +if typeis(x=n): + reveal_type(n) # N: Revealed type is "builtins.int" + +if Z().typeis(x=n): + reveal_type(n) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] + +[case testStaticMethodTypeIs] +from typing_extensions import TypeIs + +class Y: + @staticmethod + def typeis(h: object) -> TypeIs[int]: + ... + +x: object +if Y().typeis(x): + reveal_type(x) # N: Revealed type is "builtins.int" +if Y.typeis(x): + reveal_type(x) # N: Revealed type is "builtins.int" +[builtins fixtures/classmethod.pyi] + +[case testTypeIsKwargFollowingThroughOverloaded] +from typing import overload, Union +from typing_extensions import TypeIs + +@overload +def typeis(x: object, y: str) -> TypeIs[str]: + ... + +@overload +def typeis(x: object, y: int) -> TypeIs[int]: + ... + +def typeis(x: object, y: Union[int, str]) -> Union[TypeIs[int], TypeIs[str]]: + ... + +x: object +if typeis(x=x, y=42): + reveal_type(x) # N: Revealed type is "builtins.int" + +if typeis(y=42, x=x): + reveal_type(x) # N: Revealed type is "builtins.int" + +if typeis(x=x, y="42"): + reveal_type(x) # N: Revealed type is "builtins.str" + +if typeis(y="42", x=x): + reveal_type(x) # N: Revealed type is "builtins.str" +[builtins fixtures/tuple.pyi] + +[case testGenericAliasWithTypeIs] +from typing import Callable, List, TypeVar +from typing_extensions import TypeIs + +T = TypeVar('T') +A = Callable[[object], TypeIs[List[T]]] +def foo(x: object) -> TypeIs[List[str]]: ... + +def test(f: A[T]) -> T: ... +reveal_type(test(foo)) # N: Revealed type is "builtins.str" +[builtins fixtures/list.pyi] + +[case testNoCrashOnDunderCallTypeIs] +from typing_extensions import TypeIs + +class A: + def __call__(self, x) -> TypeIs[int]: + return True + +a: A +assert a(x=1) + +x: object +assert a(x=x) +reveal_type(x) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] + +[case testTypeIsMustBeSubtypeFunctions] +from typing_extensions import TypeIs +from typing import List, Sequence, TypeVar + +def f(x: str) -> TypeIs[int]: # E: Narrowed type "int" is not a subtype of input type "str" + pass + +T = TypeVar('T') + +def g(x: List[T]) -> TypeIs[Sequence[T]]: # E: Narrowed type "Sequence[T]" is not a subtype of input type "List[T]" + pass + +[builtins fixtures/tuple.pyi] + +[case testTypeIsMustBeSubtypeMethods] +from typing_extensions import TypeIs + +class NarrowHolder: + @classmethod + def cls_narrower_good(cls, x: object) -> TypeIs[int]: + pass + + @classmethod + def cls_narrower_bad(cls, x: str) -> TypeIs[int]: # E: Narrowed type "int" is not a subtype of input type "str" + pass + + @staticmethod + def static_narrower_good(x: object) -> TypeIs[int]: + pass + + @staticmethod + def static_narrower_bad(x: str) -> TypeIs[int]: # E: Narrowed type "int" is not a subtype of input type "str" + pass + + def inst_narrower_good(self, x: object) -> TypeIs[int]: + pass + + def inst_narrower_bad(self, x: str) -> TypeIs[int]: # E: Narrowed type "int" is not a subtype of input type "str" + pass + + +[builtins fixtures/classmethod.pyi] + +[case testTypeIsTypeGuardNoSubtyping] +from typing_extensions import TypeGuard, TypeIs +from typing import Callable + +def accept_typeis(x: Callable[[object], TypeIs[str]]): + pass + +def accept_typeguard(x: Callable[[object], TypeGuard[str]]): + pass + +def typeis(x: object) -> TypeIs[str]: + pass + +def typeguard(x: object) -> TypeGuard[str]: + pass + +accept_typeis(typeis) +accept_typeis(typeguard) # E: Argument 1 to "accept_typeis" has incompatible type "Callable[[object], TypeGuard[str]]"; expected "Callable[[object], TypeIs[str]]" +accept_typeguard(typeis) # E: Argument 1 to "accept_typeguard" has incompatible type "Callable[[object], TypeIs[str]]"; expected "Callable[[object], TypeGuard[str]]" +accept_typeguard(typeguard) + +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-typevar-defaults.test b/test-data/unit/check-typevar-defaults.test new file mode 100644 index 000000000000..9ca67376da26 --- /dev/null +++ b/test-data/unit/check-typevar-defaults.test @@ -0,0 +1,719 @@ +[case testTypeVarDefaultsBasic] +import builtins +from typing import Generic, TypeVar, ParamSpec, Callable, Tuple, List +from typing_extensions import TypeVarTuple, Unpack + +T1 = TypeVar("T1", default=int) +P1 = ParamSpec("P1", default=[int, str]) +Ts1 = TypeVarTuple("Ts1", default=Unpack[Tuple[int, str]]) + +def f1(a: T1) -> List[T1]: ... +reveal_type(f1) # N: Revealed type is "def [T1 = builtins.int] (a: T1`-1 = builtins.int) -> builtins.list[T1`-1 = builtins.int]" + +def f2(a: Callable[P1, None] ) -> Callable[P1, None]: ... +reveal_type(f2) # N: Revealed type is "def [P1 = [builtins.int, builtins.str]] (a: def (*P1.args, **P1.kwargs)) -> def (*P1.args, **P1.kwargs)" + +def f3(a: Tuple[Unpack[Ts1]]) -> Tuple[Unpack[Ts1]]: ... +reveal_type(f3) # N: Revealed type is "def [Ts1 = Unpack[Tuple[builtins.int, builtins.str]]] (a: Tuple[Unpack[Ts1`-1 = Unpack[Tuple[builtins.int, builtins.str]]]]) -> Tuple[Unpack[Ts1`-1 = Unpack[Tuple[builtins.int, builtins.str]]]]" + + +class ClassA1(Generic[T1]): ... +class ClassA2(Generic[P1]): ... +class ClassA3(Generic[Unpack[Ts1]]): ... + +reveal_type(ClassA1) # N: Revealed type is "def [T1 = builtins.int] () -> __main__.ClassA1[T1`1 = builtins.int]" +reveal_type(ClassA2) # N: Revealed type is "def [P1 = [builtins.int, builtins.str]] () -> __main__.ClassA2[P1`1 = [builtins.int, builtins.str]]" +reveal_type(ClassA3) # N: Revealed type is "def [Ts1 = Unpack[Tuple[builtins.int, builtins.str]]] () -> __main__.ClassA3[Unpack[Ts1`1 = Unpack[Tuple[builtins.int, builtins.str]]]]" +[builtins fixtures/tuple.pyi] + +[case testTypeVarDefaultsValid] +from typing import TypeVar, ParamSpec, Any, List, Tuple +from typing_extensions import TypeVarTuple, Unpack + +S0 = TypeVar("S0") +S1 = TypeVar("S1", bound=int) + +P0 = ParamSpec("P0") +Ts0 = TypeVarTuple("Ts0") + +T1 = TypeVar("T1", default=int) +T2 = TypeVar("T2", bound=float, default=int) +T3 = TypeVar("T3", bound=List[Any], default=List[int]) +T4 = TypeVar("T4", int, str, default=int) +T5 = TypeVar("T5", default=S0) +T6 = TypeVar("T6", bound=float, default=S1) +# T7 = TypeVar("T7", bound=List[Any], default=List[S0]) # TODO + +P1 = ParamSpec("P1", default=[]) +P2 = ParamSpec("P2", default=...) +P3 = ParamSpec("P3", default=[int, str]) +P4 = ParamSpec("P4", default=P0) + +Ts1 = TypeVarTuple("Ts1", default=Unpack[Tuple[int]]) +Ts2 = TypeVarTuple("Ts2", default=Unpack[Tuple[int, ...]]) +# Ts3 = TypeVarTuple("Ts3", default=Unpack[Ts0]) # TODO +[builtins fixtures/tuple.pyi] + +[case testTypeVarDefaultsInvalid] +from typing import TypeVar, ParamSpec, Tuple +from typing_extensions import TypeVarTuple, Unpack + +T1 = TypeVar("T1", default=2) # E: TypeVar "default" must be a type +T2 = TypeVar("T2", default=[int]) # E: Bracketed expression "[...]" is not valid as a type \ + # N: Did you mean "List[...]"? \ + # E: TypeVar "default" must be a type + +P1 = ParamSpec("P1", default=int) # E: The default argument to ParamSpec must be a list expression, ellipsis, or a ParamSpec +P2 = ParamSpec("P2", default=2) # E: The default argument to ParamSpec must be a list expression, ellipsis, or a ParamSpec +P3 = ParamSpec("P3", default=(2, int)) # E: The default argument to ParamSpec must be a list expression, ellipsis, or a ParamSpec +P4 = ParamSpec("P4", default=[2, int]) # E: Argument 0 of ParamSpec default must be a type + +Ts1 = TypeVarTuple("Ts1", default=2) # E: The default argument to TypeVarTuple must be an Unpacked tuple +Ts2 = TypeVarTuple("Ts2", default=int) # E: The default argument to TypeVarTuple must be an Unpacked tuple +Ts3 = TypeVarTuple("Ts3", default=Tuple[int]) # E: The default argument to TypeVarTuple must be an Unpacked tuple +[builtins fixtures/tuple.pyi] + +[case testTypeVarDefaultsInvalid2] +from typing import TypeVar, List, Union + +T1 = TypeVar("T1", bound=str, default=int) # E: TypeVar default must be a subtype of the bound type +T2 = TypeVar("T2", bound=List[str], default=List[int]) # E: TypeVar default must be a subtype of the bound type +T3 = TypeVar("T3", int, str, default=bytes) # E: TypeVar default must be one of the constraint types +T4 = TypeVar("T4", int, str, default=Union[int, str]) # E: TypeVar default must be one of the constraint types +T5 = TypeVar("T5", float, str, default=int) # E: TypeVar default must be one of the constraint types + +[case testTypeVarDefaultsInvalid3] +from typing import Dict, Generic, TypeVar + +T1 = TypeVar("T1") +T2 = TypeVar("T2", default=T3) # E: Name "T3" is used before definition +T3 = TypeVar("T3", default=str) +T4 = TypeVar("T4", default=T3) + +class ClassError1(Generic[T3, T1]): ... # E: "T1" cannot appear after "T3" in type parameter list because it has no default type + +def func_error1( + a: ClassError1, + b: ClassError1[int], + c: ClassError1[int, float], +) -> None: + reveal_type(a) # N: Revealed type is "__main__.ClassError1[builtins.str, Any]" + reveal_type(b) # N: Revealed type is "__main__.ClassError1[builtins.int, Any]" + reveal_type(c) # N: Revealed type is "__main__.ClassError1[builtins.int, builtins.float]" + + k = ClassError1() + reveal_type(k) # N: Revealed type is "__main__.ClassError1[builtins.str, Any]" + l = ClassError1[int]() + reveal_type(l) # N: Revealed type is "__main__.ClassError1[builtins.int, Any]" + m = ClassError1[int, float]() + reveal_type(m) # N: Revealed type is "__main__.ClassError1[builtins.int, builtins.float]" + +class ClassError2(Generic[T4, T3]): ... # E: Type parameter "T4" has a default type that refers to one or more type variables that are out of scope + +def func_error2( + a: ClassError2, + b: ClassError2[int], + c: ClassError2[int, float], +) -> None: + reveal_type(a) # N: Revealed type is "__main__.ClassError2[Any, builtins.str]" + reveal_type(b) # N: Revealed type is "__main__.ClassError2[builtins.int, builtins.str]" + reveal_type(c) # N: Revealed type is "__main__.ClassError2[builtins.int, builtins.float]" + + k = ClassError2() + reveal_type(k) # N: Revealed type is "__main__.ClassError2[Any, builtins.str]" + l = ClassError2[int]() + reveal_type(l) # N: Revealed type is "__main__.ClassError2[builtins.int, builtins.str]" + m = ClassError2[int, float]() + reveal_type(m) # N: Revealed type is "__main__.ClassError2[builtins.int, builtins.float]" + +TERR1 = Dict[T3, T1] # E: "T1" cannot appear after "T3" in type parameter list because it has no default type + +def func_error_alias1( + a: TERR1, + b: TERR1[int], + c: TERR1[int, float], +) -> None: + reveal_type(a) # N: Revealed type is "builtins.dict[builtins.str, Any]" + reveal_type(b) # N: Revealed type is "builtins.dict[builtins.int, Any]" + reveal_type(c) # N: Revealed type is "builtins.dict[builtins.int, builtins.float]" + +TERR2 = Dict[T4, T3] # TODO should be an error \ + # Type parameter "T4" has a default type that refers to one or more type variables that are out of scope + +def func_error_alias2( + a: TERR2, + b: TERR2[int], + c: TERR2[int, float], +) -> None: + reveal_type(a) # N: Revealed type is "builtins.dict[Any, builtins.str]" + reveal_type(b) # N: Revealed type is "builtins.dict[builtins.int, builtins.str]" + reveal_type(c) # N: Revealed type is "builtins.dict[builtins.int, builtins.float]" +[builtins fixtures/dict.pyi] + +[case testTypeVarDefaultsFunctions] +from typing import TypeVar, ParamSpec, List, Union, Callable, Tuple +from typing_extensions import TypeVarTuple, Unpack + +T1 = TypeVar("T1", default=str) +T2 = TypeVar("T2", bound=str, default=str) +T3 = TypeVar("T3", bytes, str, default=str) +P1 = ParamSpec("P1", default=[int, str]) +Ts1 = TypeVarTuple("Ts1", default=Unpack[Tuple[int, str]]) + +def callback1(x: str) -> None: ... + +def func_a1(x: Union[int, T1]) -> T1: ... +reveal_type(func_a1(2)) # N: Revealed type is "builtins.str" +reveal_type(func_a1(2.1)) # N: Revealed type is "builtins.float" + +def func_a2(x: Union[int, T1]) -> List[T1]: ... +reveal_type(func_a2(2)) # N: Revealed type is "builtins.list[builtins.str]" +reveal_type(func_a2(2.1)) # N: Revealed type is "builtins.list[builtins.float]" + +def func_a3(x: Union[int, T2]) -> T2: ... +reveal_type(func_a3(2)) # N: Revealed type is "builtins.str" + +def func_a4(x: Union[int, T3]) -> T3: ... +reveal_type(func_a4(2)) # N: Revealed type is "builtins.str" + +def func_b1(x: Union[int, Callable[P1, None]]) -> Callable[P1, None]: ... +reveal_type(func_b1(callback1)) # N: Revealed type is "def (x: builtins.str)" +reveal_type(func_b1(2)) # N: Revealed type is "def (builtins.int, builtins.str)" + +def func_c1(x: Union[int, Callable[[Unpack[Ts1]], None]]) -> Tuple[Unpack[Ts1]]: ... +# reveal_type(func_c1(callback1)) # Revealed type is "builtins.tuple[str]" # TODO +# reveal_type(func_c1(2)) # Revealed type is "builtins.tuple[builtins.int, builtins.str]" # TODO +[builtins fixtures/tuple.pyi] + +[case testTypeVarDefaultsClass1] +# flags: --disallow-any-generics +from typing import Generic, TypeVar, Union, overload + +T1 = TypeVar("T1") +T2 = TypeVar("T2", default=int) +T3 = TypeVar("T3", default=str) +T4 = TypeVar("T4", default=Union[int, None]) + +class ClassA1(Generic[T2, T3]): ... + +def func_a1( + a: ClassA1, + b: ClassA1[float], + c: ClassA1[float, float], + d: ClassA1[float, float, float], # E: "ClassA1" expects between 0 and 2 type arguments, but 3 given +) -> None: + reveal_type(a) # N: Revealed type is "__main__.ClassA1[builtins.int, builtins.str]" + reveal_type(b) # N: Revealed type is "__main__.ClassA1[builtins.float, builtins.str]" + reveal_type(c) # N: Revealed type is "__main__.ClassA1[builtins.float, builtins.float]" + reveal_type(d) # N: Revealed type is "__main__.ClassA1[builtins.int, builtins.str]" + + k = ClassA1() + reveal_type(k) # N: Revealed type is "__main__.ClassA1[builtins.int, builtins.str]" + l = ClassA1[float]() + reveal_type(l) # N: Revealed type is "__main__.ClassA1[builtins.float, builtins.str]" + m = ClassA1[float, float]() + reveal_type(m) # N: Revealed type is "__main__.ClassA1[builtins.float, builtins.float]" + n = ClassA1[float, float, float]() # E: Type application has too many types (expected between 0 and 2) + reveal_type(n) # N: Revealed type is "Any" + +class ClassA2(Generic[T1, T2, T3]): ... + +def func_a2( + a: ClassA2, # E: Missing type parameters for generic type "ClassA2" + b: ClassA2[float], + c: ClassA2[float, float], + d: ClassA2[float, float, float], + e: ClassA2[float, float, float, float], # E: "ClassA2" expects between 1 and 3 type arguments, but 4 given +) -> None: + reveal_type(a) # N: Revealed type is "__main__.ClassA2[Any, builtins.int, builtins.str]" + reveal_type(b) # N: Revealed type is "__main__.ClassA2[builtins.float, builtins.int, builtins.str]" + reveal_type(c) # N: Revealed type is "__main__.ClassA2[builtins.float, builtins.float, builtins.str]" + reveal_type(d) # N: Revealed type is "__main__.ClassA2[builtins.float, builtins.float, builtins.float]" + reveal_type(e) # N: Revealed type is "__main__.ClassA2[Any, builtins.int, builtins.str]" + + k = ClassA2() # E: Need type annotation for "k" + reveal_type(k) # N: Revealed type is "__main__.ClassA2[Any, builtins.int, builtins.str]" + l = ClassA2[float]() + reveal_type(l) # N: Revealed type is "__main__.ClassA2[builtins.float, builtins.int, builtins.str]" + m = ClassA2[float, float]() + reveal_type(m) # N: Revealed type is "__main__.ClassA2[builtins.float, builtins.float, builtins.str]" + n = ClassA2[float, float, float]() + reveal_type(n) # N: Revealed type is "__main__.ClassA2[builtins.float, builtins.float, builtins.float]" + o = ClassA2[float, float, float, float]() # E: Type application has too many types (expected between 1 and 3) + reveal_type(o) # N: Revealed type is "Any" + +class ClassA3(Generic[T1, T2]): + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, var: int) -> None: ... + def __init__(self, var: Union[int, None] = None) -> None: ... + +def func_a3( + a: ClassA3, # E: Missing type parameters for generic type "ClassA3" + b: ClassA3[float], + c: ClassA3[float, float], + d: ClassA3[float, float, float], # E: "ClassA3" expects between 1 and 2 type arguments, but 3 given +) -> None: + reveal_type(a) # N: Revealed type is "__main__.ClassA3[Any, builtins.int]" + reveal_type(b) # N: Revealed type is "__main__.ClassA3[builtins.float, builtins.int]" + reveal_type(c) # N: Revealed type is "__main__.ClassA3[builtins.float, builtins.float]" + reveal_type(d) # N: Revealed type is "__main__.ClassA3[Any, builtins.int]" + + k = ClassA3() # E: Need type annotation for "k" + reveal_type(k) # N: Revealed type is "__main__.ClassA3[Any, builtins.int]" + l = ClassA3[float]() + reveal_type(l) # N: Revealed type is "__main__.ClassA3[builtins.float, builtins.int]" + m = ClassA3[float, float]() + reveal_type(m) # N: Revealed type is "__main__.ClassA3[builtins.float, builtins.float]" + n = ClassA3[float, float, float]() # E: Type application has too many types (expected between 1 and 2) + reveal_type(n) # N: Revealed type is "Any" + +class ClassA4(Generic[T4]): ... + +def func_a4( + a: ClassA4, + b: ClassA4[float], +) -> None: + reveal_type(a) # N: Revealed type is "__main__.ClassA4[Union[builtins.int, None]]" + reveal_type(b) # N: Revealed type is "__main__.ClassA4[builtins.float]" + + k = ClassA4() + reveal_type(k) # N: Revealed type is "__main__.ClassA4[Union[builtins.int, None]]" + l = ClassA4[float]() + reveal_type(l) # N: Revealed type is "__main__.ClassA4[builtins.float]" + +[case testTypeVarDefaultsClass2] +# flags: --disallow-any-generics +from typing import Generic, ParamSpec + +P1 = ParamSpec("P1") +P2 = ParamSpec("P2", default=[int, str]) +P3 = ParamSpec("P3", default=...) + +class ClassB1(Generic[P2, P3]): ... + +def func_b1( + a: ClassB1, + b: ClassB1[[float]], + c: ClassB1[[float], [float]], + d: ClassB1[[float], [float], [float]], # E: "ClassB1" expects between 0 and 2 type arguments, but 3 given +) -> None: + reveal_type(a) # N: Revealed type is "__main__.ClassB1[[builtins.int, builtins.str], ...]" + reveal_type(b) # N: Revealed type is "__main__.ClassB1[[builtins.float], ...]" + reveal_type(c) # N: Revealed type is "__main__.ClassB1[[builtins.float], [builtins.float]]" + reveal_type(d) # N: Revealed type is "__main__.ClassB1[[builtins.int, builtins.str], ...]" + + k = ClassB1() + reveal_type(k) # N: Revealed type is "__main__.ClassB1[[builtins.int, builtins.str], [*Any, **Any]]" + l = ClassB1[[float]]() + reveal_type(l) # N: Revealed type is "__main__.ClassB1[[builtins.float], [*Any, **Any]]" + m = ClassB1[[float], [float]]() + reveal_type(m) # N: Revealed type is "__main__.ClassB1[[builtins.float], [builtins.float]]" + n = ClassB1[[float], [float], [float]]() # E: Type application has too many types (expected between 0 and 2) + reveal_type(n) # N: Revealed type is "Any" + +class ClassB2(Generic[P1, P2]): ... + +def func_b2( + a: ClassB2, # E: Missing type parameters for generic type "ClassB2" + b: ClassB2[[float]], + c: ClassB2[[float], [float]], + d: ClassB2[[float], [float], [float]], # E: "ClassB2" expects between 1 and 2 type arguments, but 3 given +) -> None: + reveal_type(a) # N: Revealed type is "__main__.ClassB2[Any, [builtins.int, builtins.str]]" + reveal_type(b) # N: Revealed type is "__main__.ClassB2[[builtins.float], [builtins.int, builtins.str]]" + reveal_type(c) # N: Revealed type is "__main__.ClassB2[[builtins.float], [builtins.float]]" + reveal_type(d) # N: Revealed type is "__main__.ClassB2[Any, [builtins.int, builtins.str]]" + + k = ClassB2() # E: Need type annotation for "k" + reveal_type(k) # N: Revealed type is "__main__.ClassB2[Any, [builtins.int, builtins.str]]" + l = ClassB2[[float]]() + reveal_type(l) # N: Revealed type is "__main__.ClassB2[[builtins.float], [builtins.int, builtins.str]]" + m = ClassB2[[float], [float]]() + reveal_type(m) # N: Revealed type is "__main__.ClassB2[[builtins.float], [builtins.float]]" + n = ClassB2[[float], [float], [float]]() # E: Type application has too many types (expected between 1 and 2) + reveal_type(n) # N: Revealed type is "Any" + +[case testTypeVarDefaultsClass3] +# flags: --disallow-any-generics +from typing import Generic, Tuple, TypeVar +from typing_extensions import TypeVarTuple, Unpack + +T1 = TypeVar("T1") +T3 = TypeVar("T3", default=str) + +Ts1 = TypeVarTuple("Ts1") +Ts2 = TypeVarTuple("Ts2", default=Unpack[Tuple[int, str]]) +Ts3 = TypeVarTuple("Ts3", default=Unpack[Tuple[float, ...]]) +Ts4 = TypeVarTuple("Ts4", default=Unpack[Tuple[()]]) + +class ClassC1(Generic[Unpack[Ts2]]): ... + +def func_c1( + a: ClassC1, + b: ClassC1[float], +) -> None: + # reveal_type(a) # Revealed type is "__main__.ClassC1[builtins.int, builtins.str]" # TODO + reveal_type(b) # N: Revealed type is "__main__.ClassC1[builtins.float]" + + k = ClassC1() + reveal_type(k) # N: Revealed type is "__main__.ClassC1[builtins.int, builtins.str]" + l = ClassC1[float]() + reveal_type(l) # N: Revealed type is "__main__.ClassC1[builtins.float]" + +class ClassC2(Generic[T3, Unpack[Ts3]]): ... + +def func_c2( + a: ClassC2, + b: ClassC2[int], + c: ClassC2[int, Unpack[Tuple[()]]], +) -> None: + reveal_type(a) # N: Revealed type is "__main__.ClassC2[builtins.str, Unpack[builtins.tuple[builtins.float, ...]]]" + # reveal_type(b) # Revealed type is "__main__.ClassC2[builtins.int, Unpack[builtins.tuple[builtins.float, ...]]]" # TODO + reveal_type(c) # N: Revealed type is "__main__.ClassC2[builtins.int]" + + k = ClassC2() + reveal_type(k) # N: Revealed type is "__main__.ClassC2[builtins.str, Unpack[builtins.tuple[builtins.float, ...]]]" + l = ClassC2[int]() + # reveal_type(l) # Revealed type is "__main__.ClassC2[builtins.int, Unpack[builtins.tuple[builtins.float, ...]]]" # TODO + m = ClassC2[int, Unpack[Tuple[()]]]() + reveal_type(m) # N: Revealed type is "__main__.ClassC2[builtins.int]" + +class ClassC3(Generic[T3, Unpack[Ts4]]): ... + +def func_c3( + a: ClassC3, + b: ClassC3[int], + c: ClassC3[int, Unpack[Tuple[float]]] +) -> None: + # reveal_type(a) # Revealed type is "__main__.ClassC3[builtins.str]" # TODO + reveal_type(b) # N: Revealed type is "__main__.ClassC3[builtins.int]" + reveal_type(c) # N: Revealed type is "__main__.ClassC3[builtins.int, builtins.float]" + + k = ClassC3() + reveal_type(k) # N: Revealed type is "__main__.ClassC3[builtins.str]" + l = ClassC3[int]() + reveal_type(l) # N: Revealed type is "__main__.ClassC3[builtins.int]" + m = ClassC3[int, Unpack[Tuple[float]]]() + reveal_type(m) # N: Revealed type is "__main__.ClassC3[builtins.int, builtins.float]" + +class ClassC4(Generic[T1, Unpack[Ts1], T3]): ... + +def func_c4( + a: ClassC4, # E: Missing type parameters for generic type "ClassC4" + b: ClassC4[int], + c: ClassC4[int, float], +) -> None: + reveal_type(a) # N: Revealed type is "__main__.ClassC4[Any, Unpack[builtins.tuple[Any, ...]], builtins.str]" + # reveal_type(b) # Revealed type is "__main__.ClassC4[builtins.int, builtins.str]" # TODO + reveal_type(c) # N: Revealed type is "__main__.ClassC4[builtins.int, builtins.float]" + + k = ClassC4() # E: Need type annotation for "k" + reveal_type(k) # N: Revealed type is "__main__.ClassC4[Any, Unpack[builtins.tuple[Any, ...]], builtins.str]" + l = ClassC4[int]() + # reveal_type(l) # Revealed type is "__main__.ClassC4[builtins.int, builtins.str]" # TODO + m = ClassC4[int, float]() + reveal_type(m) # N: Revealed type is "__main__.ClassC4[builtins.int, builtins.float]" +[builtins fixtures/tuple.pyi] + +[case testTypeVarDefaultsClassRecursive1] +# flags: --disallow-any-generics +from typing import Generic, TypeVar, List + +T1 = TypeVar("T1", default=str) +T2 = TypeVar("T2", default=T1) +T3 = TypeVar("T3", default=T2) +T4 = TypeVar("T4", default=List[T1]) + +class ClassD1(Generic[T1, T2]): ... + +def func_d1( + a: ClassD1, + b: ClassD1[int], + c: ClassD1[int, float] +) -> None: + reveal_type(a) # N: Revealed type is "__main__.ClassD1[builtins.str, builtins.str]" + reveal_type(b) # N: Revealed type is "__main__.ClassD1[builtins.int, builtins.int]" + reveal_type(c) # N: Revealed type is "__main__.ClassD1[builtins.int, builtins.float]" + + k = ClassD1() + reveal_type(k) # N: Revealed type is "__main__.ClassD1[builtins.str, builtins.str]" + l = ClassD1[int]() + reveal_type(l) # N: Revealed type is "__main__.ClassD1[builtins.int, builtins.int]" + m = ClassD1[int, float]() + reveal_type(m) # N: Revealed type is "__main__.ClassD1[builtins.int, builtins.float]" + +class ClassD2(Generic[T1, T2, T3]): ... + +def func_d2( + a: ClassD2, + b: ClassD2[int], + c: ClassD2[int, float], + d: ClassD2[int, float, str], +) -> None: + reveal_type(a) # N: Revealed type is "__main__.ClassD2[builtins.str, builtins.str, builtins.str]" + reveal_type(b) # N: Revealed type is "__main__.ClassD2[builtins.int, builtins.int, builtins.int]" + reveal_type(c) # N: Revealed type is "__main__.ClassD2[builtins.int, builtins.float, builtins.float]" + reveal_type(d) # N: Revealed type is "__main__.ClassD2[builtins.int, builtins.float, builtins.str]" + + k = ClassD2() + reveal_type(k) # N: Revealed type is "__main__.ClassD2[builtins.str, builtins.str, builtins.str]" + l = ClassD2[int]() + reveal_type(l) # N: Revealed type is "__main__.ClassD2[builtins.int, builtins.int, builtins.int]" + m = ClassD2[int, float]() + reveal_type(m) # N: Revealed type is "__main__.ClassD2[builtins.int, builtins.float, builtins.float]" + n = ClassD2[int, float, str]() + reveal_type(n) # N: Revealed type is "__main__.ClassD2[builtins.int, builtins.float, builtins.str]" + +class ClassD3(Generic[T1, T4]): ... + +def func_d3( + a: ClassD3, + b: ClassD3[int], + c: ClassD3[int, float], +) -> None: + reveal_type(a) # N: Revealed type is "__main__.ClassD3[builtins.str, builtins.list[builtins.str]]" + reveal_type(b) # N: Revealed type is "__main__.ClassD3[builtins.int, builtins.list[builtins.int]]" + reveal_type(c) # N: Revealed type is "__main__.ClassD3[builtins.int, builtins.float]" + + # k = ClassD3() + # reveal_type(k) # Revealed type is "__main__.ClassD3[builtins.str, builtins.list[builtins.str]]" # TODO + l = ClassD3[int]() + reveal_type(l) # N: Revealed type is "__main__.ClassD3[builtins.int, builtins.list[builtins.int]]" + m = ClassD3[int, float]() + reveal_type(m) # N: Revealed type is "__main__.ClassD3[builtins.int, builtins.float]" + +[case testTypeVarDefaultsClassRecursiveMultipleFiles] +# flags: --disallow-any-generics +from typing import Generic, TypeVar +from file2 import T as T2 + +T = TypeVar("T", default=T2) + +class ClassG1(Generic[T2, T]): + pass + +def func( + a: ClassG1, + b: ClassG1[str], + c: ClassG1[str, float], +) -> None: + reveal_type(a) # N: Revealed type is "__main__.ClassG1[builtins.int, builtins.int]" + reveal_type(b) # N: Revealed type is "__main__.ClassG1[builtins.str, builtins.str]" + reveal_type(c) # N: Revealed type is "__main__.ClassG1[builtins.str, builtins.float]" + + k = ClassG1() + reveal_type(k) # N: Revealed type is "__main__.ClassG1[builtins.int, builtins.int]" + l = ClassG1[str]() + reveal_type(l) # N: Revealed type is "__main__.ClassG1[builtins.str, builtins.str]" + m = ClassG1[str, float]() + reveal_type(m) # N: Revealed type is "__main__.ClassG1[builtins.str, builtins.float]" + +[file file2.py] +from typing import TypeVar +T = TypeVar('T', default=int) + +[case testTypeVarDefaultsTypeAlias1] +# flags: --disallow-any-generics +from typing import Any, Dict, List, Tuple, TypeVar, Union + +T1 = TypeVar("T1") +T2 = TypeVar("T2", default=int) +T3 = TypeVar("T3", default=str) +T4 = TypeVar("T4") + +TA1 = Dict[T2, T3] + +def func_a1( + a: TA1, + b: TA1[float], + c: TA1[float, float], + d: TA1[float, float, float], # E: Bad number of arguments for type alias, expected between 0 and 2, given 3 +) -> None: + reveal_type(a) # N: Revealed type is "builtins.dict[builtins.int, builtins.str]" + reveal_type(b) # N: Revealed type is "builtins.dict[builtins.float, builtins.str]" + reveal_type(c) # N: Revealed type is "builtins.dict[builtins.float, builtins.float]" + reveal_type(d) # N: Revealed type is "builtins.dict[builtins.int, builtins.str]" + +TA2 = Tuple[T1, T2, T3] + +def func_a2( + a: TA2, # E: Missing type parameters for generic type "TA2" + b: TA2[float], + c: TA2[float, float], + d: TA2[float, float, float], + e: TA2[float, float, float, float], # E: Bad number of arguments for type alias, expected between 1 and 3, given 4 +) -> None: + reveal_type(a) # N: Revealed type is "Tuple[Any, builtins.int, builtins.str]" + reveal_type(b) # N: Revealed type is "Tuple[builtins.float, builtins.int, builtins.str]" + reveal_type(c) # N: Revealed type is "Tuple[builtins.float, builtins.float, builtins.str]" + reveal_type(d) # N: Revealed type is "Tuple[builtins.float, builtins.float, builtins.float]" + reveal_type(e) # N: Revealed type is "Tuple[Any, builtins.int, builtins.str]" + +TA3 = Union[Dict[T1, T2], List[T3]] + +def func_a3( + a: TA3, # E: Missing type parameters for generic type "TA3" + b: TA3[float], + c: TA3[float, float], + d: TA3[float, float, float], + e: TA3[float, float, float, float], # E: Bad number of arguments for type alias, expected between 1 and 3, given 4 +) -> None: + reveal_type(a) # N: Revealed type is "Union[builtins.dict[Any, builtins.int], builtins.list[builtins.str]]" + reveal_type(b) # N: Revealed type is "Union[builtins.dict[builtins.float, builtins.int], builtins.list[builtins.str]]" + reveal_type(c) # N: Revealed type is "Union[builtins.dict[builtins.float, builtins.float], builtins.list[builtins.str]]" + reveal_type(d) # N: Revealed type is "Union[builtins.dict[builtins.float, builtins.float], builtins.list[builtins.float]]" + reveal_type(e) # N: Revealed type is "Union[builtins.dict[Any, builtins.int], builtins.list[builtins.str]]" + +TA4 = Tuple[T1, T4, T2] + +def func_a4( + a: TA4, # E: Missing type parameters for generic type "TA4" + b: TA4[float], # E: Bad number of arguments for type alias, expected between 2 and 3, given 1 + c: TA4[float, float], + d: TA4[float, float, float], + e: TA4[float, float, float, float], # E: Bad number of arguments for type alias, expected between 2 and 3, given 4 +) -> None: + reveal_type(a) # N: Revealed type is "Tuple[Any, Any, builtins.int]" + reveal_type(b) # N: Revealed type is "Tuple[Any, Any, builtins.int]" + reveal_type(c) # N: Revealed type is "Tuple[builtins.float, builtins.float, builtins.int]" + reveal_type(d) # N: Revealed type is "Tuple[builtins.float, builtins.float, builtins.float]" + reveal_type(e) # N: Revealed type is "Tuple[Any, Any, builtins.int]" +[builtins fixtures/dict.pyi] + +[case testTypeVarDefaultsTypeAlias2] +# flags: --disallow-any-generics +from typing import Any, Generic, ParamSpec + +P1 = ParamSpec("P1") +P2 = ParamSpec("P2", default=[int, str]) +P3 = ParamSpec("P3", default=...) + +class ClassB1(Generic[P2, P3]): ... +TB1 = ClassB1[P2, P3] + +def func_b1( + a: TB1, + b: TB1[[float]], + c: TB1[[float], [float]], + d: TB1[[float], [float], [float]], # E: Bad number of arguments for type alias, expected between 0 and 2, given 3 +) -> None: + reveal_type(a) # N: Revealed type is "__main__.ClassB1[[builtins.int, builtins.str], [*Any, **Any]]" + reveal_type(b) # N: Revealed type is "__main__.ClassB1[[builtins.float], [*Any, **Any]]" + reveal_type(c) # N: Revealed type is "__main__.ClassB1[[builtins.float], [builtins.float]]" + reveal_type(d) # N: Revealed type is "__main__.ClassB1[[builtins.int, builtins.str], [*Any, **Any]]" + +class ClassB2(Generic[P1, P2]): ... +TB2 = ClassB2[P1, P2] + +def func_b2( + a: TB2, # E: Missing type parameters for generic type "TB2" + b: TB2[[float]], + c: TB2[[float], [float]], + d: TB2[[float], [float], [float]], # E: Bad number of arguments for type alias, expected between 1 and 2, given 3 +) -> None: + reveal_type(a) # N: Revealed type is "__main__.ClassB2[Any, [builtins.int, builtins.str]]" + reveal_type(b) # N: Revealed type is "__main__.ClassB2[[builtins.float], [builtins.int, builtins.str]]" + reveal_type(c) # N: Revealed type is "__main__.ClassB2[[builtins.float], [builtins.float]]" + reveal_type(d) # N: Revealed type is "__main__.ClassB2[Any, [builtins.int, builtins.str]]" +[builtins fixtures/tuple.pyi] + +[case testTypeVarDefaultsTypeAlias3] +# flags: --disallow-any-generics +from typing import Tuple, TypeVar +from typing_extensions import TypeVarTuple, Unpack + +T1 = TypeVar("T1") +T3 = TypeVar("T3", default=str) + +Ts1 = TypeVarTuple("Ts1") +Ts2 = TypeVarTuple("Ts2", default=Unpack[Tuple[int, str]]) +Ts3 = TypeVarTuple("Ts3", default=Unpack[Tuple[float, ...]]) +Ts4 = TypeVarTuple("Ts4", default=Unpack[Tuple[()]]) + +TC1 = Tuple[Unpack[Ts2]] + +def func_c1( + a: TC1, + b: TC1[float], +) -> None: + # reveal_type(a) # Revealed type is "Tuple[builtins.int, builtins.str]" # TODO + reveal_type(b) # N: Revealed type is "Tuple[builtins.float]" + +TC2 = Tuple[T3, Unpack[Ts3]] + +def func_c2( + a: TC2, + b: TC2[int], + c: TC2[int, Unpack[Tuple[()]]], +) -> None: + # reveal_type(a) # Revealed type is "Tuple[builtins.str, Unpack[builtins.tuple[builtins.float, ...]]]" # TODO + # reveal_type(b) # Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]]]" # TODO + reveal_type(c) # N: Revealed type is "Tuple[builtins.int]" + +TC3 = Tuple[T3, Unpack[Ts4]] + +def func_c3( + a: TC3, + b: TC3[int], + c: TC3[int, Unpack[Tuple[float]]], +) -> None: + # reveal_type(a) # Revealed type is "Tuple[builtins.str]" # TODO + reveal_type(b) # N: Revealed type is "Tuple[builtins.int]" + reveal_type(c) # N: Revealed type is "Tuple[builtins.int, builtins.float]" + +TC4 = Tuple[T1, Unpack[Ts1], T3] + +def func_c4( + a: TC4, # E: Missing type parameters for generic type "TC4" + b: TC4[int], + c: TC4[int, float], +) -> None: + reveal_type(a) # N: Revealed type is "Tuple[Any, Unpack[builtins.tuple[Any, ...]], builtins.str]" + # reveal_type(b) # Revealed type is "Tuple[builtins.int, builtins.str]" # TODO + reveal_type(c) # N: Revealed type is "Tuple[builtins.int, builtins.float]" +[builtins fixtures/tuple.pyi] + +[case testTypeVarDefaultsTypeAliasRecursive1] +# flags: --disallow-any-generics +from typing import Dict, List, TypeVar + +T1 = TypeVar("T1") +T2 = TypeVar("T2", default=T1) + +TD1 = Dict[T1, T2] + +def func_d1( + a: TD1, # E: Missing type parameters for generic type "TD1" + b: TD1[int], + c: TD1[int, float], +) -> None: + reveal_type(a) # N: Revealed type is "builtins.dict[Any, Any]" + reveal_type(b) # N: Revealed type is "builtins.dict[builtins.int, builtins.int]" + reveal_type(c) # N: Revealed type is "builtins.dict[builtins.int, builtins.float]" +[builtins fixtures/dict.pyi] + +[case testTypeVarDefaultsTypeAliasRecursive2] +from typing import Any, Dict, Generic, TypeVar + +T1 = TypeVar("T1", default=str) +T2 = TypeVar("T2", default=T1) +Alias1 = Dict[T1, T2] +T3 = TypeVar("T3") +class A(Generic[T3]): ... + +T4 = TypeVar("T4", default=A[Alias1]) +class B(Generic[T4]): ... + +def func_d3( + a: B, + b: B[A[Alias1[int]]], + c: B[A[Alias1[int, float]]], + d: B[int], +) -> None: + reveal_type(a) # N: Revealed type is "__main__.B[__main__.A[builtins.dict[builtins.str, builtins.str]]]" + reveal_type(b) # N: Revealed type is "__main__.B[__main__.A[builtins.dict[builtins.int, builtins.int]]]" + reveal_type(c) # N: Revealed type is "__main__.B[__main__.A[builtins.dict[builtins.int, builtins.float]]]" + reveal_type(d) # N: Revealed type is "__main__.B[builtins.int]" +[builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index 9afe709ed19b..f704e3c5c713 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -17,7 +17,7 @@ reveal_type(f(args)) # N: Revealed type is "Tuple[builtins.int, builtins.str]" reveal_type(f(varargs)) # N: Revealed type is "builtins.tuple[builtins.int, ...]" -f(0) # E: Argument 1 to "f" has incompatible type "int"; expected +f(0) # E: Argument 1 to "f" has incompatible type "int"; expected "Tuple[Never, ...]" def g(a: Tuple[Unpack[Ts]], b: Tuple[Unpack[Ts]]) -> Tuple[Unpack[Ts]]: return a @@ -25,7 +25,7 @@ def g(a: Tuple[Unpack[Ts]], b: Tuple[Unpack[Ts]]) -> Tuple[Unpack[Ts]]: reveal_type(g(args, args)) # N: Revealed type is "Tuple[builtins.int, builtins.str]" reveal_type(g(args, args2)) # N: Revealed type is "Tuple[builtins.int, builtins.str]" reveal_type(g(args, args3)) # N: Revealed type is "builtins.tuple[builtins.object, ...]" -reveal_type(g(any, any)) # N: Revealed type is "Any" +reveal_type(g(any, any)) # N: Revealed type is "builtins.tuple[Any, ...]" [builtins fixtures/tuple.pyi] [case testTypeVarTupleMixed] @@ -57,11 +57,12 @@ f_args3: Tuple[int, str, bool] reveal_type(f(f_args)) # N: Revealed type is "Tuple[builtins.str, builtins.str]" reveal_type(f(f_args2)) # N: Revealed type is "Tuple[builtins.str]" reveal_type(f(f_args3)) # N: Revealed type is "Tuple[builtins.str, builtins.str, builtins.bool]" -f(empty) # E: Argument 1 to "f" has incompatible type "Tuple[]"; expected "Tuple[int]" +f(empty) # E: Argument 1 to "f" has incompatible type "Tuple[()]"; expected "Tuple[int]" f(bad_args) # E: Argument 1 to "f" has incompatible type "Tuple[str, str]"; expected "Tuple[int, str]" -# TODO: This hits a crash where we assert len(templates.items) == 1. See visit_tuple_type -# in mypy/constraints.py. -#f(var_len_tuple) + +# The reason for error in subtle: actual can be empty, formal cannot. +reveal_type(f(var_len_tuple)) # N: Revealed type is "Tuple[builtins.str, Unpack[builtins.tuple[builtins.int, ...]]]" \ + # E: Argument 1 to "f" has incompatible type "Tuple[int, ...]"; expected "Tuple[int, Unpack[Tuple[int, ...]]]" g_args: Tuple[str, int] reveal_type(g(g_args)) # N: Revealed type is "Tuple[builtins.str, builtins.str]" @@ -117,19 +118,18 @@ variadic_single: Variadic[int] reveal_type(variadic_single) # N: Revealed type is "__main__.Variadic[builtins.int]" empty: Variadic[()] -# TODO: fix pretty printer to be better. -reveal_type(empty) # N: Revealed type is "__main__.Variadic" +reveal_type(empty) # N: Revealed type is "__main__.Variadic[()]" + +omitted: Variadic +reveal_type(omitted) # N: Revealed type is "__main__.Variadic[Unpack[builtins.tuple[Any, ...]]]" bad: Variadic[Unpack[Tuple[int, ...]], str, Unpack[Tuple[bool, ...]]] # E: More than one Unpack in a type is not allowed reveal_type(bad) # N: Revealed type is "__main__.Variadic[Unpack[builtins.tuple[builtins.int, ...]], builtins.str]" -# TODO: This is tricky to fix because we need typeanal to know whether the current -# location is valid for an Unpack or not. -# bad2: Unpack[Tuple[int, ...]] +bad2: Unpack[Tuple[int, ...]] # E: Unpack is only valid in a variadic position m1: Mixed1[int, str, bool] reveal_type(m1) # N: Revealed type is "__main__.Mixed1[builtins.int, builtins.str, builtins.bool]" - [builtins fixtures/tuple.pyi] [case testTypeVarTupleGenericClassWithFunctions] @@ -148,7 +148,6 @@ def foo(t: Variadic[int, Unpack[Ts], object]) -> Tuple[int, Unpack[Ts]]: v: Variadic[int, str, bool, object] reveal_type(foo(v)) # N: Revealed type is "Tuple[builtins.int, builtins.str, builtins.bool]" - [builtins fixtures/tuple.pyi] [case testTypeVarTupleGenericClassWithMethods] @@ -168,7 +167,6 @@ class Variadic(Generic[T, Unpack[Ts], S]): v: Variadic[float, str, bool, object] reveal_type(v.foo(0)) # N: Revealed type is "Tuple[builtins.int, builtins.str, builtins.bool]" - [builtins fixtures/tuple.pyi] [case testTypeVarTupleIsNotValidAliasTarget] @@ -202,7 +200,7 @@ class Array(Generic[Unpack[Shape]]): def get_shape(self) -> Tuple[Unpack[Shape]]: return self._shape - + def __abs__(self) -> Array[Unpack[Shape]]: ... def __add__(self, other: Array[Unpack[Shape]]) -> Array[Unpack[Shape]]: ... @@ -211,8 +209,8 @@ shape = (Height(480), Width(640)) x: Array[Height, Width] = Array(shape) reveal_type(abs(x)) # N: Revealed type is "__main__.Array[__main__.Height, __main__.Width]" reveal_type(x + x) # N: Revealed type is "__main__.Array[__main__.Height, __main__.Width]" - [builtins fixtures/tuple.pyi] + [case testTypeVarTuplePep646ArrayExampleWithDType] from typing import Generic, Tuple, TypeVar, Protocol, NewType from typing_extensions import TypeVarTuple, Unpack @@ -238,7 +236,7 @@ class Array(Generic[DType, Unpack[Shape]]): def get_shape(self) -> Tuple[Unpack[Shape]]: return self._shape - + def __abs__(self) -> Array[DType, Unpack[Shape]]: ... def __add__(self, other: Array[DType, Unpack[Shape]]) -> Array[DType, Unpack[Shape]]: ... @@ -247,7 +245,6 @@ shape = (Height(480), Width(640)) x: Array[float, Height, Width] = Array(shape) reveal_type(abs(x)) # N: Revealed type is "__main__.Array[builtins.float, __main__.Height, __main__.Width]" reveal_type(x + x) # N: Revealed type is "__main__.Array[builtins.float, __main__.Height, __main__.Width]" - [builtins fixtures/tuple.pyi] [case testTypeVarTuplePep646ArrayExampleInfer] @@ -293,8 +290,8 @@ c = del_batch_axis(b) reveal_type(c) # N: Revealed type is "__main__.Array[__main__.Height, __main__.Width]" d = add_batch_channels(a) reveal_type(d) # N: Revealed type is "__main__.Array[__main__.Batch, __main__.Height, __main__.Width, __main__.Channels]" - [builtins fixtures/tuple.pyi] + [case testTypeVarTuplePep646TypeVarConcatenation] from typing import Generic, TypeVar, NewType, Tuple from typing_extensions import TypeVarTuple, Unpack @@ -311,6 +308,7 @@ def prefix_tuple( z = prefix_tuple(x=0, y=(True, 'a')) reveal_type(z) # N: Revealed type is "Tuple[builtins.int, builtins.bool, builtins.str]" [builtins fixtures/tuple.pyi] + [case testTypeVarTuplePep646TypeVarTupleUnpacking] from typing import Generic, TypeVar, NewType, Any, Tuple from typing_extensions import TypeVarTuple, Unpack @@ -363,8 +361,6 @@ reveal_type(bad) # N: Revealed type is "def [Ts, Ts2] (x: Tuple[builtins.int, U def bad2(x: Tuple[int, Unpack[Tuple[int, ...]], str, Unpack[Tuple[str, ...]]]) -> None: # E: More than one Unpack in a type is not allowed ... reveal_type(bad2) # N: Revealed type is "def (x: Tuple[builtins.int, Unpack[builtins.tuple[builtins.int, ...]], builtins.str])" - - [builtins fixtures/tuple.pyi] [case testTypeVarTuplePep646TypeVarStarArgsBasic] @@ -373,23 +369,44 @@ from typing_extensions import TypeVarTuple, Unpack Ts = TypeVarTuple("Ts") -# TODO: add less trivial tests with prefix/suffix etc. -# TODO: add tests that call with a type var tuple instead of just args. def args_to_tuple(*args: Unpack[Ts]) -> Tuple[Unpack[Ts]]: reveal_type(args) # N: Revealed type is "Tuple[Unpack[Ts`-1]]" - return args + reveal_type(args_to_tuple(1, *args)) # N: Revealed type is "Tuple[Literal[1]?, Unpack[Ts`-1]]" + reveal_type(args_to_tuple(*args, 'a')) # N: Revealed type is "Tuple[Unpack[Ts`-1], Literal['a']?]" + reveal_type(args_to_tuple(1, *args, 'a')) # N: Revealed type is "Tuple[Literal[1]?, Unpack[Ts`-1], Literal['a']?]" + args_to_tuple(*args, *args) # E: Passing multiple variadic unpacks in a call is not supported + ok = (1, 'a') + reveal_type(args_to_tuple(*ok, *ok)) # N: Revealed type is "Tuple[builtins.int, builtins.str, builtins.int, builtins.str]" + if int(): + return args + else: + return args_to_tuple(*args) reveal_type(args_to_tuple(1, 'a')) # N: Revealed type is "Tuple[Literal[1]?, Literal['a']?]" - +vt: Tuple[int, ...] +reveal_type(args_to_tuple(1, *vt)) # N: Revealed type is "Tuple[Literal[1]?, Unpack[builtins.tuple[builtins.int, ...]]]" +reveal_type(args_to_tuple(*vt, 'a')) # N: Revealed type is "Tuple[Unpack[builtins.tuple[builtins.int, ...]], Literal['a']?]" +reveal_type(args_to_tuple(1, *vt, 'a')) # N: Revealed type is "Tuple[Literal[1]?, Unpack[builtins.tuple[builtins.int, ...]], Literal['a']?]" +args_to_tuple(*vt, *vt) # E: Passing multiple variadic unpacks in a call is not supported [builtins fixtures/tuple.pyi] + [case testTypeVarTuplePep646TypeVarStarArgs] from typing import Tuple from typing_extensions import TypeVarTuple, Unpack Ts = TypeVarTuple("Ts") +def args_to_tuple(*args: Unpack[Ts]) -> Tuple[Unpack[Ts]]: + with_prefix_suffix(*args) # E: Too few arguments for "with_prefix_suffix" \ + # E: Argument 1 to "with_prefix_suffix" has incompatible type "*Tuple[Unpack[Ts]]"; expected "bool" + new_args = (True, "foo", *args, 5) + with_prefix_suffix(*new_args) + return args + def with_prefix_suffix(*args: Unpack[Tuple[bool, str, Unpack[Ts], int]]) -> Tuple[bool, str, Unpack[Ts], int]: reveal_type(args) # N: Revealed type is "Tuple[builtins.bool, builtins.str, Unpack[Ts`-1], builtins.int]" + reveal_type(args_to_tuple(*args)) # N: Revealed type is "Tuple[builtins.bool, builtins.str, Unpack[Ts`-1], builtins.int]" + reveal_type(args_to_tuple(1, *args, 'a')) # N: Revealed type is "Tuple[Literal[1]?, builtins.bool, builtins.str, Unpack[Ts`-1], builtins.int, Literal['a']?]" return args reveal_type(with_prefix_suffix(True, "bar", "foo", 5)) # N: Revealed type is "Tuple[builtins.bool, builtins.str, Literal['foo']?, builtins.int]" @@ -402,16 +419,13 @@ t = (True, "bar", "foo", 5) reveal_type(with_prefix_suffix(*t)) # N: Revealed type is "Tuple[builtins.bool, builtins.str, builtins.str, builtins.int]" reveal_type(with_prefix_suffix(True, *("bar", "foo"), 5)) # N: Revealed type is "Tuple[builtins.bool, builtins.str, Literal['foo']?, builtins.int]" -# TODO: handle list case -#reveal_type(with_prefix_suffix(True, "bar", *["foo1", "foo2"], 5)) +reveal_type(with_prefix_suffix(True, "bar", *["foo1", "foo2"], 5)) # N: Revealed type is "Tuple[builtins.bool, builtins.str, Unpack[builtins.tuple[builtins.str, ...]], builtins.int]" bad_t = (True, "bar") with_prefix_suffix(*bad_t) # E: Too few arguments for "with_prefix_suffix" def foo(*args: Unpack[Ts]) -> None: reveal_type(with_prefix_suffix(True, "bar", *args, 5)) # N: Revealed type is "Tuple[builtins.bool, builtins.str, Unpack[Ts`-1], builtins.int]" - - [builtins fixtures/tuple.pyi] [case testTypeVarTuplePep646TypeVarStarArgsFixedLengthTuple] @@ -422,40 +436,62 @@ def foo(*args: Unpack[Tuple[int, str]]) -> None: reveal_type(args) # N: Revealed type is "Tuple[builtins.int, builtins.str]" foo(0, "foo") -foo(0, 1) # E: Argument 2 to "foo" has incompatible type "int"; expected "Unpack[Tuple[int, str]]" -foo("foo", "bar") # E: Argument 1 to "foo" has incompatible type "str"; expected "Unpack[Tuple[int, str]]" -foo(0, "foo", 1) # E: Invalid number of arguments -foo(0) # E: Invalid number of arguments -foo() # E: Invalid number of arguments +foo(0, 1) # E: Argument 2 to "foo" has incompatible type "int"; expected "str" +foo("foo", "bar") # E: Argument 1 to "foo" has incompatible type "str"; expected "int" +foo(0, "foo", 1) # E: Too many arguments for "foo" +foo(0) # E: Too few arguments for "foo" +foo() # E: Too few arguments for "foo" foo(*(0, "foo")) -# TODO: fix this case to do something sensible. -#def foo2(*args: Unpack[Tuple[bool, Unpack[Tuple[int, str]], bool]]) -> None: -# reveal_type(args) +def foo2(*args: Unpack[Tuple[bool, Unpack[Tuple[int, str]], bool]]) -> None: + reveal_type(args) # N: Revealed type is "Tuple[builtins.bool, builtins.int, builtins.str, builtins.bool]" + +# It is hard to normalize callable types in definition, because there is deep relation between `FuncDef.type` +# and `FuncDef.arguments`, therefore various typeops need to be sure to normalize Callable types before using them. +reveal_type(foo2) # N: Revealed type is "def (*args: Unpack[Tuple[builtins.bool, builtins.int, builtins.str, builtins.bool]])" +class C: + def foo2(self, *args: Unpack[Tuple[bool, Unpack[Tuple[int, str]], bool]]) -> None: ... +reveal_type(C().foo2) # N: Revealed type is "def (*args: Unpack[Tuple[builtins.bool, builtins.int, builtins.str, builtins.bool]])" [builtins fixtures/tuple.pyi] [case testTypeVarTuplePep646TypeVarStarArgsVariableLengthTuple] from typing import Tuple -from typing_extensions import Unpack +from typing_extensions import Unpack, TypeVarTuple def foo(*args: Unpack[Tuple[int, ...]]) -> None: reveal_type(args) # N: Revealed type is "builtins.tuple[builtins.int, ...]" foo(0, 1, 2) -# TODO: this should say 'expected "int"' rather than the unpack -foo(0, 1, "bar") # E: Argument 3 to "foo" has incompatible type "str"; expected "Unpack[Tuple[int, ...]]" - +foo(0, 1, "bar") # E: Argument 3 to "foo" has incompatible type "str"; expected "int" def foo2(*args: Unpack[Tuple[str, Unpack[Tuple[int, ...]], bool, bool]]) -> None: reveal_type(args) # N: Revealed type is "Tuple[builtins.str, Unpack[builtins.tuple[builtins.int, ...]], builtins.bool, builtins.bool]" - # TODO: generate an error - # reveal_type(args[1]) + reveal_type(args[1]) # N: Revealed type is "builtins.int" + +def foo3(*args: Unpack[Tuple[str, Unpack[Tuple[int, ...]], str, float]]) -> None: + reveal_type(args[0]) # N: Revealed type is "builtins.str" + reveal_type(args[1]) # N: Revealed type is "Union[builtins.int, builtins.str]" + reveal_type(args[2]) # N: Revealed type is "Union[builtins.int, builtins.str, builtins.float]" + args[3] # E: Tuple index out of range \ + # N: Variadic tuple can have length 3 + reveal_type(args[-1]) # N: Revealed type is "builtins.float" + reveal_type(args[-2]) # N: Revealed type is "builtins.str" + reveal_type(args[-3]) # N: Revealed type is "Union[builtins.str, builtins.int]" + args[-4] # E: Tuple index out of range \ + # N: Variadic tuple can have length 3 + reveal_type(args[::-1]) # N: Revealed type is "Tuple[builtins.float, builtins.str, Unpack[builtins.tuple[builtins.int, ...]], builtins.str]" + args[::2] # E: Ambiguous slice of a variadic tuple + args[:2] # E: Ambiguous slice of a variadic tuple + +Ts = TypeVarTuple("Ts") +def foo4(*args: Unpack[Tuple[str, Unpack[Ts], bool, bool]]) -> None: + reveal_type(args[1]) # N: Revealed type is "builtins.object" foo2("bar", 1, 2, 3, False, True) -foo2(0, 1, 2, 3, False, True) # E: Argument 1 to "foo2" has incompatible type "int"; expected "Unpack[Tuple[str, Unpack[Tuple[int, ...]], bool, bool]]" -foo2("bar", "bar", 2, 3, False, True) # E: Argument 2 to "foo2" has incompatible type "str"; expected "Unpack[Tuple[str, Unpack[Tuple[int, ...]], bool, bool]]" -foo2("bar", 1, 2, 3, 4, True) # E: Argument 5 to "foo2" has incompatible type "int"; expected "Unpack[Tuple[str, Unpack[Tuple[int, ...]], bool, bool]]" +foo2(0, 1, 2, 3, False, True) # E: Argument 1 to "foo2" has incompatible type "int"; expected "str" +foo2("bar", "bar", 2, 3, False, True) # E: Argument 2 to "foo2" has incompatible type "str"; expected "Unpack[Tuple[Unpack[Tuple[int, ...]], bool, bool]]" +foo2("bar", 1, 2, 3, 4, True) # E: Argument 5 to "foo2" has incompatible type "int"; expected "Unpack[Tuple[Unpack[Tuple[int, ...]], bool, bool]]" foo2(*("bar", 1, 2, 3, False, True)) [builtins fixtures/tuple.pyi] @@ -479,18 +515,18 @@ vargs: Tuple[int, ...] vargs_str: Tuple[str, ...] call(target=func, args=(0, 'foo')) -call(target=func, args=('bar', 'foo')) # E: Argument "target" to "call" has incompatible type "Callable[[int, str], None]"; expected "Callable[[object, str], None]" -call(target=func, args=(True, 'foo', 0)) # E: Argument "target" to "call" has incompatible type "Callable[[int, str], None]"; expected "Callable[[VarArg(object)], None]" -call(target=func, args=(0, 0, 'foo')) # E: Argument "target" to "call" has incompatible type "Callable[[int, str], None]"; expected "Callable[[VarArg(object)], None]" -call(target=func, args=vargs) # E: Argument "target" to "call" has incompatible type "Callable[[int, str], None]"; expected "Callable[[VarArg(object)], None]" +call(target=func, args=('bar', 'foo')) # E: Argument "target" to "call" has incompatible type "Callable[[int, str], None]"; expected "Callable[[str, str], None]" +call(target=func, args=(True, 'foo', 0)) # E: Argument "target" to "call" has incompatible type "Callable[[int, str], None]"; expected "Callable[[bool, str, int], None]" +call(target=func, args=(0, 0, 'foo')) # E: Argument "target" to "call" has incompatible type "Callable[[int, str], None]"; expected "Callable[[int, int, str], None]" +call(target=func, args=vargs) # E: Argument "target" to "call" has incompatible type "Callable[[int, str], None]"; expected "Callable[[VarArg(int)], None]" # NOTE: This behavior may be a bit contentious, it is maybe inconsistent with our handling of # PEP646 but consistent with our handling of callable constraints. call(target=func2, args=vargs) # E: Argument "target" to "call" has incompatible type "Callable[[int, int], None]"; expected "Callable[[VarArg(int)], None]" call(target=func3, args=vargs) call(target=func3, args=(0,1)) -call(target=func3, args=(0,'foo')) # E: Argument "target" to "call" has incompatible type "Callable[[VarArg(int)], None]"; expected "Callable[[VarArg(object)], None]" -call(target=func3, args=vargs_str) # E: Argument "target" to "call" has incompatible type "Callable[[VarArg(int)], None]"; expected "Callable[[VarArg(object)], None]" +call(target=func3, args=(0,'foo')) # E: Argument "target" to "call" has incompatible type "Callable[[VarArg(int)], None]"; expected "Callable[[int, str], None]" +call(target=func3, args=vargs_str) # E: Argument "target" to "call" has incompatible type "Callable[[VarArg(int)], None]"; expected "Callable[[VarArg(str)], None]" [builtins fixtures/tuple.pyi] [case testTypeVarTuplePep646CallableWithPrefixSuffix] @@ -512,3 +548,1775 @@ call_prefix(target=func_prefix, args=(0, 'foo')) call_prefix(target=func2_prefix, args=(0, 'foo')) # E: Argument "target" to "call_prefix" has incompatible type "Callable[[str, int, str], None]"; expected "Callable[[bytes, int, str], None]" [builtins fixtures/tuple.pyi] +[case testTypeVarTuplePep646CallableSuffixSyntax] +from typing import Callable, Tuple, TypeVar +from typing_extensions import Unpack, TypeVarTuple + +x: Callable[[str, Unpack[Tuple[int, ...]], bool], None] +reveal_type(x) # N: Revealed type is "def (builtins.str, *Unpack[Tuple[Unpack[builtins.tuple[builtins.int, ...]], builtins.bool]])" + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") +A = Callable[[T, Unpack[Ts], S], int] +y: A[int, str, bool] +reveal_type(y) # N: Revealed type is "def (builtins.int, builtins.str, builtins.bool) -> builtins.int" +z: A[Unpack[Tuple[int, ...]]] +reveal_type(z) # N: Revealed type is "def (builtins.int, *Unpack[Tuple[Unpack[builtins.tuple[builtins.int, ...]], builtins.int]]) -> builtins.int" +[builtins fixtures/tuple.pyi] + +[case testTypeVarTuplePep646CallableInvalidSyntax] +from typing import Callable, Tuple, TypeVar +from typing_extensions import Unpack, TypeVarTuple + +Ts = TypeVarTuple("Ts") +Us = TypeVarTuple("Us") +a: Callable[[Unpack[Ts], Unpack[Us]], int] # E: More than one Unpack in a type is not allowed +reveal_type(a) # N: Revealed type is "def [Ts, Us] (*Unpack[Ts`-1]) -> builtins.int" +b: Callable[[Unpack], int] # E: Unpack[...] requires exactly one type argument +reveal_type(b) # N: Revealed type is "def (*Any) -> builtins.int" +[builtins fixtures/tuple.pyi] + +[case testTypeVarTuplePep646CallableNewSyntax] +from typing import Callable, Generic, Tuple +from typing_extensions import ParamSpec + +x: Callable[[str, *Tuple[int, ...]], None] +reveal_type(x) # N: Revealed type is "def (builtins.str, *builtins.int)" +y: Callable[[str, *Tuple[int, ...], bool], None] +reveal_type(y) # N: Revealed type is "def (builtins.str, *Unpack[Tuple[Unpack[builtins.tuple[builtins.int, ...]], builtins.bool]])" + +P = ParamSpec("P") +class C(Generic[P]): ... +bad: C[[int, *Tuple[int, ...], int]] # E: Unpack is only valid in a variadic position +reveal_type(bad) # N: Revealed type is "__main__.C[[builtins.int, *Any]]" +[builtins fixtures/tuple.pyi] + +[case testTypeVarTuplePep646UnspecifiedParameters] +from typing import Tuple, Generic, TypeVar +from typing_extensions import Unpack, TypeVarTuple + +Ts = TypeVarTuple("Ts") + +class Array(Generic[Unpack[Ts]]): + ... + +def takes_any_array(arr: Array) -> None: + ... + +x: Array[int, bool] +takes_any_array(x) + +T = TypeVar("T") + +class Array2(Generic[T, Unpack[Ts]]): + ... + +def takes_empty_array2(arr: Array2[int]) -> None: + ... + +y: Array2[int] +takes_empty_array2(y) +[builtins fixtures/tuple.pyi] + +[case testTypeVarTuplePep646CallableStarArgs] +from typing import Tuple, Callable +from typing_extensions import Unpack, TypeVarTuple + +Ts = TypeVarTuple("Ts") + +def call( + target: Callable[[Unpack[Ts]], None], + *args: Unpack[Ts], +) -> None: + ... + target(*args) + +class A: + def func(self, arg1: int, arg2: str) -> None: ... + def func2(self, arg1: int, arg2: int) -> None: ... + def func3(self, *args: int) -> None: ... + +vargs: Tuple[int, ...] +vargs_str: Tuple[str, ...] + +call(A().func) # E: Argument 1 to "call" has incompatible type "Callable[[int, str], None]"; expected "Callable[[], None]" +call(A().func, 0, 'foo') +call(A().func, 0, 'foo', 0) # E: Argument 1 to "call" has incompatible type "Callable[[int, str], None]"; expected "Callable[[int, str, int], None]" +call(A().func, 0) # E: Argument 1 to "call" has incompatible type "Callable[[int, str], None]"; expected "Callable[[int], None]" +call(A().func, 0, 1) # E: Argument 1 to "call" has incompatible type "Callable[[int, str], None]"; expected "Callable[[int, int], None]" +call(A().func2, 0, 0) +call(A().func3, 0, 1, 2) +call(A().func3) +[builtins fixtures/tuple.pyi] + +[case testVariadicAliasBasicTuple] +from typing import Tuple, List, TypeVar +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +Ts = TypeVarTuple("Ts") + +A = List[Tuple[T, Unpack[Ts], T]] +x: A[int, str, str] +reveal_type(x) # N: Revealed type is "builtins.list[Tuple[builtins.int, builtins.str, builtins.str, builtins.int]]" +[builtins fixtures/tuple.pyi] + +[case testVariadicAliasBasicCallable] +from typing import TypeVar, Callable +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") + +A = Callable[[T, Unpack[Ts]], S] +x: A[int, str, int, str] +reveal_type(x) # N: Revealed type is "def (builtins.int, builtins.str, builtins.int) -> builtins.str" +[builtins fixtures/tuple.pyi] + +[case testVariadicAliasBasicInstance] +from typing import TypeVar, Generic +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +Ts = TypeVarTuple("Ts") + +class G(Generic[Unpack[Ts], T]): ... + +A = G[T, Unpack[Ts], T] +x: A[int, str, str] +reveal_type(x) # N: Revealed type is "__main__.G[builtins.int, builtins.str, builtins.str, builtins.int]" +[builtins fixtures/tuple.pyi] + +[case testVariadicAliasUnpackFixedTupleArgs] +from typing import Tuple, List, TypeVar +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") + +Start = Tuple[int, str] +A = List[Tuple[T, Unpack[Ts], S]] +x: A[Unpack[Start], int] +reveal_type(x) # N: Revealed type is "builtins.list[Tuple[builtins.int, builtins.str, builtins.int]]" +[builtins fixtures/tuple.pyi] + +[case testVariadicAliasUnpackFixedTupleTarget] +from typing import Tuple, TypeVar +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") + +Prefix = Tuple[int, int] +A = Tuple[Unpack[Prefix], Unpack[Ts]] +x: A[str, str] +reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.int, builtins.str, builtins.str]" +[builtins fixtures/tuple.pyi] + +[case testVariadicAliasMultipleUnpacks] +from typing import Tuple, Generic, Callable +from typing_extensions import Unpack, TypeVarTuple + +Ts = TypeVarTuple("Ts") +Us = TypeVarTuple("Us") +class G(Generic[Unpack[Ts]]): ... + +A = Tuple[Unpack[Ts], Unpack[Us]] # E: More than one Unpack in a type is not allowed +x: A[int, str] +reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.str]" + +B = Callable[[Unpack[Ts], Unpack[Us]], int] # E: More than one Unpack in a type is not allowed +y: B[int, str] +reveal_type(y) # N: Revealed type is "def (builtins.int, builtins.str) -> builtins.int" + +C = G[Unpack[Ts], Unpack[Us]] # E: More than one Unpack in a type is not allowed +z: C[int, str] +reveal_type(z) # N: Revealed type is "__main__.G[builtins.int, builtins.str]" +[builtins fixtures/tuple.pyi] + +[case testVariadicAliasNoArgs] +from typing import Tuple, TypeVar, Generic, Callable, List +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +Ts = TypeVarTuple("Ts") +class G(Generic[Unpack[Ts]]): ... + +A = List[Tuple[T, Unpack[Ts], T]] +x: A +reveal_type(x) # N: Revealed type is "builtins.list[Tuple[Any, Unpack[builtins.tuple[Any, ...]], Any]]" + +B = Callable[[T, Unpack[Ts]], int] +y: B +reveal_type(y) # N: Revealed type is "def (Any, *Any) -> builtins.int" + +C = G[T, Unpack[Ts], T] +z: C +reveal_type(z) # N: Revealed type is "__main__.G[Any, Unpack[builtins.tuple[Any, ...]], Any]" +[builtins fixtures/tuple.pyi] + +[case testVariadicAliasFewArgs] +from typing import Tuple, List, TypeVar, Generic, Callable +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") +class G(Generic[Unpack[Ts]]): ... + +A = List[Tuple[T, Unpack[Ts], S]] +x: A[int] # E: Bad number of arguments for type alias, expected at least 2, given 1 +reveal_type(x) # N: Revealed type is "builtins.list[Tuple[Any, Unpack[builtins.tuple[Any, ...]], Any]]" + +B = Callable[[T, S, Unpack[Ts]], int] +y: B[int] # E: Bad number of arguments for type alias, expected at least 2, given 1 +reveal_type(y) # N: Revealed type is "def (Any, Any, *Any) -> builtins.int" + +C = G[T, Unpack[Ts], S] +z: C[int] # E: Bad number of arguments for type alias, expected at least 2, given 1 +reveal_type(z) # N: Revealed type is "__main__.G[Any, Unpack[builtins.tuple[Any, ...]], Any]" +[builtins fixtures/tuple.pyi] + +[case testVariadicAliasRecursiveUnpack] +from typing import Tuple, Optional +from typing_extensions import Unpack, TypeVarTuple + +Ts = TypeVarTuple("Ts") + +A = Tuple[Unpack[Ts], Optional[A[Unpack[Ts]]]] +x: A[int, str] +reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.str, Union[..., None]]" + +*_, last = x +if last is not None: + reveal_type(last) # N: Revealed type is "Tuple[builtins.int, builtins.str, Union[Tuple[builtins.int, builtins.str, Union[..., None]], None]]" +[builtins fixtures/tuple.pyi] + +[case testVariadicAliasUpperBoundCheck] +from typing import Tuple, TypeVar +from typing_extensions import Unpack, TypeVarTuple + +class A: ... +class B: ... +class C: ... +class D: ... + +T = TypeVar("T", bound=int) +S = TypeVar("S", bound=str) +Ts = TypeVarTuple("Ts") + +Alias = Tuple[T, Unpack[Ts], S] +First = Tuple[A, B] +Second = Tuple[C, D] +x: Alias[Unpack[First], Unpack[Second]] # E: Type argument "A" of "Alias" must be a subtype of "int" \ + # E: Type argument "D" of "Alias" must be a subtype of "str" +[builtins fixtures/tuple.pyi] + +[case testVariadicAliasEmptyArg] +from typing import Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +A = Tuple[int, Unpack[Ts], str] +x: A[()] +reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.str]" +[builtins fixtures/tuple.pyi] + +[case testVariadicAliasVariadicTupleArg] +from typing import Tuple, TypeVar +from typing_extensions import Unpack, TypeVarTuple + +Ts = TypeVarTuple("Ts") + +A = Tuple[int, Unpack[Ts]] +B = A[str, Unpack[Ts]] +C = B[Unpack[Tuple[bool, ...]]] +x: C +reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.str, Unpack[builtins.tuple[builtins.bool, ...]]]" +[builtins fixtures/tuple.pyi] + +[case testVariadicAliasVariadicTupleArgGeneric] +from typing import Tuple, TypeVar +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +Ts = TypeVarTuple("Ts") + +A = Tuple[int, Unpack[Ts]] +B = A[Unpack[Tuple[T, ...]]] +x: B[str] +reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.str, ...]]]" +[builtins fixtures/tuple.pyi] + +[case testVariadicAliasVariadicTupleArgSplit] +from typing import Tuple, TypeVar +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") + +A = Tuple[T, Unpack[Ts], S, T] + +x: A[int, Unpack[Tuple[bool, ...]], str] +reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.bool, ...]], builtins.str, builtins.int]" + +y: A[Unpack[Tuple[bool, ...]]] +reveal_type(y) # N: Revealed type is "Tuple[builtins.bool, Unpack[builtins.tuple[builtins.bool, ...]], builtins.bool, builtins.bool]" +[builtins fixtures/tuple.pyi] + +[case testBanPathologicalRecursiveTuples] +from typing import Tuple +from typing_extensions import Unpack +A = Tuple[int, Unpack[A]] # E: Invalid recursive alias: a tuple item of itself +B = Tuple[int, Unpack[C]] # E: Invalid recursive alias: a tuple item of itself \ + # E: Name "C" is used before definition +C = Tuple[int, Unpack[B]] +x: A +y: B +z: C +reveal_type(x) # N: Revealed type is "Any" +reveal_type(y) # N: Revealed type is "Any" +reveal_type(z) # N: Revealed type is "Tuple[builtins.int, Unpack[Any]]" +[builtins fixtures/tuple.pyi] + +[case testInferenceAgainstGenericVariadicWithBadType] +# flags: --new-type-inference +from typing import TypeVar, Callable, Generic +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +Ts = TypeVarTuple("Ts") +Us = TypeVarTuple("Us") + +class Foo(Generic[Unpack[Ts]]): ... + +def dec(f: Callable[[Unpack[Ts]], T]) -> Callable[[Unpack[Ts]], T]: ... +def f(*args: Unpack[Us]) -> Foo[Us]: ... # E: TypeVarTuple "Us" is only valid with an unpack +dec(f) # No crash +[builtins fixtures/tuple.pyi] + +[case testHomogeneousGenericTupleUnpackInferenceNoCrash1] +from typing import Any, TypeVar, Tuple, Type, Optional +from typing_extensions import Unpack + +T = TypeVar("T") +def convert(obj: Any, *to_classes: Unpack[Tuple[Type[T], ...]]) -> Optional[T]: + ... + +x = convert(1, int, float) +reveal_type(x) # N: Revealed type is "Union[builtins.float, None]" +[builtins fixtures/tuple.pyi] + +[case testHomogeneousGenericTupleUnpackInferenceNoCrash2] +from typing import TypeVar, Tuple, Callable, Iterable +from typing_extensions import Unpack + +T = TypeVar("T") +def combine(x: T, y: T) -> T: ... +def reduce(fn: Callable[[T, T], T], xs: Iterable[T]) -> T: ... + +def pipeline(*xs: Unpack[Tuple[int, Unpack[Tuple[str, ...]], bool]]) -> None: + reduce(combine, xs) +[builtins fixtures/tuple.pyi] + +[case testVariadicStarArgsCallNoCrash] +from typing import TypeVar, Callable, Tuple +from typing_extensions import TypeVarTuple, Unpack + +X = TypeVar("X") +Y = TypeVar("Y") +Xs = TypeVarTuple("Xs") +Ys = TypeVarTuple("Ys") + +def nil() -> Tuple[()]: + return () + +def cons( + f: Callable[[X], Y], + g: Callable[[Unpack[Xs]], Tuple[Unpack[Ys]]], +) -> Callable[[X, Unpack[Xs]], Tuple[Y, Unpack[Ys]]]: + def wrapped(x: X, *xs: Unpack[Xs]) -> Tuple[Y, Unpack[Ys]]: + y, ys = f(x), g(*xs) + return y, *ys + return wrapped + +def star(f: Callable[[X], Y]) -> Callable[[Unpack[Tuple[X, ...]]], Tuple[Y, ...]]: + def wrapped(*xs: X) -> Tuple[Y, ...]: + if not xs: + return nil() + return cons(f, star(f))(*xs) + return wrapped +[builtins fixtures/tuple.pyi] + +[case testInvalidTypeVarTupleUseNoCrash] +from typing_extensions import TypeVarTuple + +Ts = TypeVarTuple("Ts") + +def f(x: Ts) -> Ts: # E: TypeVarTuple "Ts" is only valid with an unpack + return x + +v = f(1, 2, "A") # E: Too many arguments for "f" +reveal_type(v) # N: Revealed type is "Any" +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleSimpleDecoratorWorks] +from typing import TypeVar, Callable +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +T = TypeVar("T") + +def decorator(f: Callable[[Unpack[Ts]], T]) -> Callable[[Unpack[Ts]], T]: + def wrapper(*args: Unpack[Ts]) -> T: + return f(*args) + return wrapper + +@decorator +def f(a: int, b: int) -> int: ... +reveal_type(f) # N: Revealed type is "def (builtins.int, builtins.int) -> builtins.int" +[builtins fixtures/tuple.pyi] + +[case testTupleWithUnpackIterator] +from typing import Tuple +from typing_extensions import Unpack + +def pipeline(*xs: Unpack[Tuple[int, Unpack[Tuple[float, ...]], bool]]) -> None: + for x in xs: + reveal_type(x) # N: Revealed type is "builtins.float" +[builtins fixtures/tuple.pyi] + +[case testFixedUnpackItemInInstanceArguments] +from typing import TypeVar, Callable, Tuple, Generic +from typing_extensions import TypeVarTuple, Unpack + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") + +class C(Generic[T, Unpack[Ts], S]): + prefix: T + suffix: S + middle: Tuple[Unpack[Ts]] + +Ints = Tuple[int, int] +c: C[Unpack[Ints]] +reveal_type(c.prefix) # N: Revealed type is "builtins.int" +reveal_type(c.suffix) # N: Revealed type is "builtins.int" +reveal_type(c.middle) # N: Revealed type is "Tuple[()]" +[builtins fixtures/tuple.pyi] + +[case testVariadicUnpackItemInInstanceArguments] +from typing import TypeVar, Callable, Tuple, Generic +from typing_extensions import TypeVarTuple, Unpack + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") + +class Other(Generic[Unpack[Ts]]): ... +class C(Generic[T, Unpack[Ts], S]): + prefix: T + suffix: S + x: Tuple[Unpack[Ts]] + y: Callable[[Unpack[Ts]], None] + z: Other[Unpack[Ts]] + +Ints = Tuple[int, ...] +c: C[Unpack[Ints]] +reveal_type(c.prefix) # N: Revealed type is "builtins.int" +reveal_type(c.suffix) # N: Revealed type is "builtins.int" +reveal_type(c.x) # N: Revealed type is "builtins.tuple[builtins.int, ...]" +reveal_type(c.y) # N: Revealed type is "def (*builtins.int)" +reveal_type(c.z) # N: Revealed type is "__main__.Other[Unpack[builtins.tuple[builtins.int, ...]]]" +[builtins fixtures/tuple.pyi] + +[case testTooFewItemsInInstanceArguments] +from typing import Generic, TypeVar +from typing_extensions import TypeVarTuple, Unpack + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") +class C(Generic[T, Unpack[Ts], S]): ... + +c: C[int] # E: Bad number of arguments, expected: at least 2, given: 1 +reveal_type(c) # N: Revealed type is "__main__.C[Any, Unpack[builtins.tuple[Any, ...]], Any]" +[builtins fixtures/tuple.pyi] + +[case testVariadicClassUpperBoundCheck] +from typing import Tuple, TypeVar, Generic +from typing_extensions import Unpack, TypeVarTuple + +class A: ... +class B: ... +class C: ... +class D: ... + +T = TypeVar("T", bound=int) +S = TypeVar("S", bound=str) +Ts = TypeVarTuple("Ts") + +class G(Generic[T, Unpack[Ts], S]): ... +First = Tuple[A, B] +Second = Tuple[C, D] +x: G[Unpack[First], Unpack[Second]] # E: Type argument "A" of "G" must be a subtype of "int" \ + # E: Type argument "D" of "G" must be a subtype of "str" +[builtins fixtures/tuple.pyi] + +[case testVariadicTupleType] +from typing import Tuple, Callable +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class A(Tuple[Unpack[Ts]]): + fn: Callable[[Unpack[Ts]], None] + +x: A[int] +reveal_type(x) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.A[builtins.int]]" +reveal_type(x[0]) # N: Revealed type is "builtins.int" +reveal_type(x.fn) # N: Revealed type is "def (builtins.int)" + +y: A[int, str] +reveal_type(y) # N: Revealed type is "Tuple[builtins.int, builtins.str, fallback=__main__.A[builtins.int, builtins.str]]" +reveal_type(y[0]) # N: Revealed type is "builtins.int" +reveal_type(y.fn) # N: Revealed type is "def (builtins.int, builtins.str)" + +z: A[Unpack[Tuple[int, ...]]] +reveal_type(z) # N: Revealed type is "__main__.A[Unpack[builtins.tuple[builtins.int, ...]]]" +reveal_type(z[0]) # N: Revealed type is "builtins.int" +reveal_type(z.fn) # N: Revealed type is "def (*builtins.int)" + +t: A[int, Unpack[Tuple[int, str]], str] +reveal_type(t) # N: Revealed type is "Tuple[builtins.int, builtins.int, builtins.str, builtins.str, fallback=__main__.A[builtins.int, builtins.int, builtins.str, builtins.str]]" +reveal_type(t[0]) # N: Revealed type is "builtins.int" +reveal_type(t.fn) # N: Revealed type is "def (builtins.int, builtins.int, builtins.str, builtins.str)" +[builtins fixtures/tuple.pyi] + +[case testVariadicNamedTuple] +from typing import Tuple, Callable, NamedTuple, Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class A(NamedTuple, Generic[Unpack[Ts], T]): + fn: Callable[[Unpack[Ts]], None] + val: T + +y: A[int, str] +reveal_type(y) # N: Revealed type is "Tuple[def (builtins.int), builtins.str, fallback=__main__.A[builtins.int, builtins.str]]" +reveal_type(y[0]) # N: Revealed type is "def (builtins.int)" +reveal_type(y.fn) # N: Revealed type is "def (builtins.int)" + +z: A[Unpack[Tuple[int, ...]]] +reveal_type(z) # N: Revealed type is "Tuple[def (*builtins.int), builtins.int, fallback=__main__.A[Unpack[builtins.tuple[builtins.int, ...]], builtins.int]]" +reveal_type(z.fn) # N: Revealed type is "def (*builtins.int)" + +t: A[int, Unpack[Tuple[int, str]], str] +reveal_type(t) # N: Revealed type is "Tuple[def (builtins.int, builtins.int, builtins.str), builtins.str, fallback=__main__.A[builtins.int, builtins.int, builtins.str, builtins.str]]" + +def test(x: int, y: str) -> None: ... +nt = A(fn=test, val=42) +reveal_type(nt) # N: Revealed type is "Tuple[def (builtins.int, builtins.str), builtins.int, fallback=__main__.A[builtins.int, builtins.str, builtins.int]]" + +def bad() -> int: ... +nt2 = A(fn=bad, val=42) # E: Argument "fn" to "A" has incompatible type "Callable[[], int]"; expected "Callable[[], None]" +[builtins fixtures/tuple.pyi] + +[case testVariadicTypedDict] +from typing import Tuple, Callable, Generic +from typing_extensions import TypeVarTuple, Unpack, TypedDict + +Ts = TypeVarTuple("Ts") +class A(TypedDict, Generic[Unpack[Ts], T]): + fn: Callable[[Unpack[Ts]], None] + val: T + +y: A[int, str] +reveal_type(y) # N: Revealed type is "TypedDict('__main__.A', {'fn': def (builtins.int), 'val': builtins.str})" +reveal_type(y["fn"]) # N: Revealed type is "def (builtins.int)" + +z: A[Unpack[Tuple[int, ...]]] +reveal_type(z) # N: Revealed type is "TypedDict('__main__.A', {'fn': def (*builtins.int), 'val': builtins.int})" +reveal_type(z["fn"]) # N: Revealed type is "def (*builtins.int)" + +t: A[int, Unpack[Tuple[int, str]], str] +reveal_type(t) # N: Revealed type is "TypedDict('__main__.A', {'fn': def (builtins.int, builtins.int, builtins.str), 'val': builtins.str})" + +def test(x: int, y: str) -> None: ... +td = A({"fn": test, "val": 42}) +reveal_type(td) # N: Revealed type is "TypedDict('__main__.A', {'fn': def (builtins.int, builtins.str), 'val': builtins.int})" + +def bad() -> int: ... +td2 = A({"fn": bad, "val": 42}) # E: Incompatible types (expression has type "Callable[[], int]", TypedDict item "fn" has type "Callable[[], None]") +[builtins fixtures/tuple.pyi] + +[case testFixedUnpackWithRegularInstance] +from typing import Tuple, Generic, TypeVar +from typing_extensions import Unpack + +T1 = TypeVar("T1") +T2 = TypeVar("T2") +T3 = TypeVar("T3") +T4 = TypeVar("T4") + +class C(Generic[T1, T2, T3, T4]): ... +x: C[int, Unpack[Alias], str] +Alias = Tuple[int, str] +reveal_type(x) # N: Revealed type is "__main__.C[builtins.int, builtins.int, builtins.str, builtins.str]" +[builtins fixtures/tuple.pyi] + +[case testVariadicUnpackWithRegularInstance] +from typing import Tuple, Generic, TypeVar +from typing_extensions import Unpack + +T1 = TypeVar("T1") +T2 = TypeVar("T2") +T3 = TypeVar("T3") +T4 = TypeVar("T4") + +class C(Generic[T1, T2, T3, T4]): ... +x: C[int, Unpack[Alias], str, str] # E: Unpack is only valid in a variadic position +Alias = Tuple[int, ...] +reveal_type(x) # N: Revealed type is "__main__.C[Any, Any, Any, Any]" +y: C[int, Unpack[Undefined]] # E: Name "Undefined" is not defined +reveal_type(y) # N: Revealed type is "__main__.C[Any, Any, Any, Any]" +[builtins fixtures/tuple.pyi] + +[case testVariadicAliasInvalidUnpackNoCrash] +from typing import Tuple, Generic, Union, List +from typing_extensions import Unpack, TypeVarTuple + +Ts = TypeVarTuple("Ts") +Alias = Tuple[int, Unpack[Ts], str] + +A = Union[int, str] +x: List[Alias[int, Unpack[A], str]] # E: "Union[int, str]" cannot be unpacked (must be tuple or TypeVarTuple) +reveal_type(x) # N: Revealed type is "builtins.list[Tuple[builtins.int, builtins.int, Unpack[builtins.tuple[Any, ...]], builtins.str, builtins.str]]" +y: List[Alias[int, Unpack[Undefined], str]] # E: Name "Undefined" is not defined +reveal_type(y) # N: Revealed type is "builtins.list[Tuple[builtins.int, Unpack[builtins.tuple[Any, ...]], builtins.str]]" +[builtins fixtures/tuple.pyi] + +[case testVariadicAliasForwardRefToFixedUnpack] +from typing import Tuple, Generic, TypeVar +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") +Alias = Tuple[T, Unpack[Ts], S] +x: Alias[int, Unpack[Other]] +Other = Tuple[int, str] +reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.int, builtins.str]" +[builtins fixtures/tuple.pyi] + +[case testVariadicAliasForwardRefToVariadicUnpack] +from typing import Tuple, Generic, TypeVar +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") +Alias = Tuple[T, Unpack[Ts], S] +x: Alias[int, Unpack[Other]] +Other = Tuple[int, ...] +reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.int, ...]], builtins.int]" +[builtins fixtures/tuple.pyi] + +[case testVariadicInstanceStrictPrefixSuffixCheck] +from typing import Tuple, Generic, TypeVar +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") +class C(Generic[T, Unpack[Ts], S]): ... + +def foo(x: Tuple[Unpack[Ts]]) -> Tuple[Unpack[Ts]]: + y: C[int, Unpack[Ts]] # E: TypeVarTuple cannot be split + z: C[Unpack[Ts], int] # E: TypeVarTuple cannot be split + return x +[builtins fixtures/tuple.pyi] + +[case testVariadicAliasStrictPrefixSuffixCheck] +from typing import Tuple, TypeVar +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") +Alias = Tuple[T, Unpack[Ts], S] + +def foo(x: Tuple[Unpack[Ts]]) -> Tuple[Unpack[Ts]]: + y: Alias[int, Unpack[Ts]] # E: TypeVarTuple cannot be split + z: Alias[Unpack[Ts], int] # E: TypeVarTuple cannot be split + return x +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleWithIsInstance] +# flags: --warn-unreachable +from typing import Generic, Tuple +from typing_extensions import TypeVarTuple, Unpack + +TP = TypeVarTuple("TP") +class A(Tuple[Unpack[TP]]): ... + +def test(d: A[int, str]) -> None: + if isinstance(d, A): + reveal_type(d) # N: Revealed type is "Tuple[builtins.int, builtins.str, fallback=__main__.A[builtins.int, builtins.str]]" + else: + reveal_type(d) # E: Statement is unreachable + +class B(Generic[Unpack[TP]]): ... + +def test2(d: B[int, str]) -> None: + if isinstance(d, B): + reveal_type(d) # N: Revealed type is "__main__.B[builtins.int, builtins.str]" + else: + reveal_type(d) # E: Statement is unreachable +[builtins fixtures/isinstancelist.pyi] + +[case testVariadicTupleSubtyping] +from typing import Tuple +from typing_extensions import Unpack + +def f1(x: Tuple[float, ...]) -> None: ... +def f2(x: Tuple[float, Unpack[Tuple[float, ...]]]) -> None: ... +def f3(x: Tuple[Unpack[Tuple[float, ...]], float]) -> None: ... +def f4(x: Tuple[float, Unpack[Tuple[float, ...]], float]) -> None: ... + +t1: Tuple[int, int] +t2: Tuple[int, Unpack[Tuple[int, ...]]] +t3: Tuple[Unpack[Tuple[int, ...]], int] +t4: Tuple[int, Unpack[Tuple[int, ...]], int] +t5: Tuple[int, ...] + +tl: Tuple[int, int, Unpack[Tuple[int, ...]]] +tr: Tuple[Unpack[Tuple[int, ...]], int, int] + +f1(t1) +f1(t2) +f1(t3) +f1(t4) +f1(t5) + +f1(tl) +f1(tr) + +f2(t1) +f2(t2) +f2(t3) +f2(t4) +f2(t5) # E: Argument 1 to "f2" has incompatible type "Tuple[int, ...]"; expected "Tuple[float, Unpack[Tuple[float, ...]]]" + +f2(tl) +f2(tr) + +f3(t1) +f3(t2) +f3(t3) +f3(t4) +f3(t5) # E: Argument 1 to "f3" has incompatible type "Tuple[int, ...]"; expected "Tuple[Unpack[Tuple[float, ...]], float]" + +f3(tl) +f3(tr) + +f4(t1) +f4(t2) # E: Argument 1 to "f4" has incompatible type "Tuple[int, Unpack[Tuple[int, ...]]]"; expected "Tuple[float, Unpack[Tuple[float, ...]], float]" +f4(t3) # E: Argument 1 to "f4" has incompatible type "Tuple[Unpack[Tuple[int, ...]], int]"; expected "Tuple[float, Unpack[Tuple[float, ...]], float]" +f4(t4) +f4(t5) # E: Argument 1 to "f4" has incompatible type "Tuple[int, ...]"; expected "Tuple[float, Unpack[Tuple[float, ...]], float]" + +f4(tl) +f4(tr) + +t5_verbose: Tuple[Unpack[Tuple[int, ...]]] +t5 = t5_verbose # OK +[builtins fixtures/tuple.pyi] + +[case testVariadicTupleInference] +from typing import List, Tuple, TypeVar +from typing_extensions import TypeVarTuple, Unpack + +T = TypeVar("T") +def f(x: Tuple[int, Unpack[Tuple[T, ...]]]) -> T: ... + +vt0: Tuple[int, ...] +f(vt0) # E: Argument 1 to "f" has incompatible type "Tuple[int, ...]"; expected "Tuple[int, Unpack[Tuple[int, ...]]]" + +vt1: Tuple[Unpack[Tuple[int, ...]], int] +reveal_type(f(vt1)) # N: Revealed type is "builtins.int" + +S = TypeVar("S") +Ts = TypeVarTuple("Ts") +def g(x: Tuple[T, Unpack[Ts], S]) -> Tuple[T, Unpack[Ts], S]: ... +g(vt0) # E: Argument 1 to "g" has incompatible type "Tuple[int, ...]"; expected "Tuple[int, Unpack[Tuple[int, ...]], int]" + +U = TypeVar("U") +def h(x: List[Tuple[T, S, U]]) -> Tuple[T, S, U]: ... +vt2: Tuple[Unpack[Tuple[int, ...]], int] +vt2 = h(reveal_type([])) # N: Revealed type is "builtins.list[Tuple[builtins.int, builtins.int, builtins.int]]" +[builtins fixtures/tuple.pyi] + +[case testVariadicSelfTypeErasure] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class Array(Generic[Unpack[Ts]]): + def _close(self) -> None: ... + + def close(self) -> None: + self._close() +[builtins fixtures/tuple.pyi] + +[case testVariadicSubclassFixed] +from typing import Generic, Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class B(Generic[Unpack[Ts]]): ... +class C(B[int, str]): ... +class D(B[Unpack[Tuple[int, ...]]]): ... + +def fii(x: B[int, int]) -> None: ... +def fis(x: B[int, str]) -> None: ... +def fiv(x: B[Unpack[Tuple[int, ...]]]) -> None: ... + +fii(C()) # E: Argument 1 to "fii" has incompatible type "C"; expected "B[int, int]" +fii(D()) # E: Argument 1 to "fii" has incompatible type "D"; expected "B[int, int]" +fis(C()) +fis(D()) # E: Argument 1 to "fis" has incompatible type "D"; expected "B[int, str]" +fiv(C()) # E: Argument 1 to "fiv" has incompatible type "C"; expected "B[Unpack[Tuple[int, ...]]]" +fiv(D()) +[builtins fixtures/tuple.pyi] + +[case testVariadicSubclassSame] +from typing import Generic, Tuple, TypeVar +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class B(Generic[Unpack[Ts]]): ... +class C(B[Unpack[Ts]]): ... + +def fii(x: B[int, int]) -> None: ... +def fis(x: B[int, str]) -> None: ... +def fiv(x: B[Unpack[Tuple[int, ...]]]) -> None: ... + +cii: C[int, int] +cis: C[int, str] +civ: C[Unpack[Tuple[int, ...]]] + +fii(cii) +fii(cis) # E: Argument 1 to "fii" has incompatible type "C[int, str]"; expected "B[int, int]" +fii(civ) # E: Argument 1 to "fii" has incompatible type "C[Unpack[Tuple[int, ...]]]"; expected "B[int, int]" + +fis(cii) # E: Argument 1 to "fis" has incompatible type "C[int, int]"; expected "B[int, str]" +fis(cis) +fis(civ) # E: Argument 1 to "fis" has incompatible type "C[Unpack[Tuple[int, ...]]]"; expected "B[int, str]" + +fiv(cii) +fiv(cis) # E: Argument 1 to "fiv" has incompatible type "C[int, str]"; expected "B[Unpack[Tuple[int, ...]]]" +fiv(civ) +[builtins fixtures/tuple.pyi] + +[case testVariadicSubclassExtra] +from typing import Generic, Tuple, TypeVar +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class B(Generic[Unpack[Ts]]): ... + +T = TypeVar("T") +class C(B[int, Unpack[Ts], T]): ... + +def ff(x: B[int, int, int]) -> None: ... +def fv(x: B[Unpack[Tuple[int, ...]]]) -> None: ... + +cii: C[int, int] +cis: C[int, str] +civ: C[Unpack[Tuple[int, ...]]] + +ff(cii) +ff(cis) # E: Argument 1 to "ff" has incompatible type "C[int, str]"; expected "B[int, int, int]" +ff(civ) # E: Argument 1 to "ff" has incompatible type "C[Unpack[Tuple[int, ...]]]"; expected "B[int, int, int]" + +fv(cii) +fv(cis) # E: Argument 1 to "fv" has incompatible type "C[int, str]"; expected "B[Unpack[Tuple[int, ...]]]" +fv(civ) +[builtins fixtures/tuple.pyi] + +[case testVariadicSubclassVariadic] +from typing import Generic, Tuple, TypeVar +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class B(Generic[Unpack[Ts]]): ... +T = TypeVar("T") +class C(B[Unpack[Tuple[T, ...]]]): ... + +def ff(x: B[int, int]) -> None: ... +def fv(x: B[Unpack[Tuple[int, ...]]]) -> None: ... + +ci: C[int] +ff(ci) # E: Argument 1 to "ff" has incompatible type "C[int]"; expected "B[int, int]" +fv(ci) +[builtins fixtures/tuple.pyi] + +[case testVariadicSubclassMethodAccess] +from typing import Generic, Tuple, TypeVar +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class B(Generic[Unpack[Ts]]): + def meth(self) -> Tuple[Unpack[Ts]]: ... + +class C1(B[int, str]): ... +class C2(B[Unpack[Ts]]): ... +T = TypeVar("T") +class C3(B[int, Unpack[Ts], T]): ... +class C4(B[Unpack[Tuple[T, ...]]]): ... + +c1: C1 +reveal_type(c1.meth()) # N: Revealed type is "Tuple[builtins.int, builtins.str]" + +c2f: C2[int, str] +c2v: C2[Unpack[Tuple[int, ...]]] +reveal_type(c2f.meth()) # N: Revealed type is "Tuple[builtins.int, builtins.str]" +reveal_type(c2v.meth()) # N: Revealed type is "builtins.tuple[builtins.int, ...]" + +c3f: C3[int, str] +c3v: C3[Unpack[Tuple[int, ...]]] +reveal_type(c3f.meth()) # N: Revealed type is "Tuple[builtins.int, builtins.int, builtins.str]" +reveal_type(c3v.meth()) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.int, ...]], builtins.int]" + +c4: C4[int] +reveal_type(c4.meth()) # N: Revealed type is "builtins.tuple[builtins.int, ...]" +[builtins fixtures/tuple.pyi] + +[case testVariadicTupleAnySubtype] +from typing import Any, Generic, Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class B(Generic[Unpack[Ts]]): ... +class C1(B[Unpack[Tuple[Any, ...]]]): ... +c1 = C1() +class C2(B): ... +c2 = C2() +x: B[int, str] +x = c1 +x = c2 +[builtins fixtures/tuple.pyi] + +[case testVariadicTupleAnySubtypeTupleType] +from typing import Any, Generic, Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class B(Tuple[Unpack[Ts]]): ... +class C1(B[Unpack[Tuple[Any, ...]]]): ... +c1 = C1() +class C2(B): ... +c2 = C2() +x: B[int, str] +x = c1 +x = c2 +[builtins fixtures/tuple.pyi] + +[case testUnpackingVariadicTuplesTypeVar] +from typing import Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +def foo(arg: Tuple[int, Unpack[Ts], str]) -> None: + x1, y1, z1 = arg # E: Variadic tuple unpacking requires a star target + reveal_type(x1) # N: Revealed type is "Any" + reveal_type(y1) # N: Revealed type is "Any" + reveal_type(z1) # N: Revealed type is "Any" + x2, *y2, z2 = arg + reveal_type(x2) # N: Revealed type is "builtins.int" + reveal_type(y2) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(z2) # N: Revealed type is "builtins.str" + x3, *y3 = arg + reveal_type(x3) # N: Revealed type is "builtins.int" + reveal_type(y3) # N: Revealed type is "builtins.list[builtins.object]" + *y4, z4 = arg + reveal_type(y4) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(z4) # N: Revealed type is "builtins.str" + x5, xx5, *y5, z5, zz5 = arg # E: Too many assignment targets for variadic unpack + reveal_type(x5) # N: Revealed type is "Any" + reveal_type(xx5) # N: Revealed type is "Any" + reveal_type(y5) # N: Revealed type is "builtins.list[Any]" + reveal_type(z5) # N: Revealed type is "Any" + reveal_type(zz5) # N: Revealed type is "Any" +[builtins fixtures/tuple.pyi] + +[case testUnpackingVariadicTuplesHomogeneous] +from typing import Tuple +from typing_extensions import Unpack + +def bar(arg: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None: + x1, y1, z1 = arg # E: Variadic tuple unpacking requires a star target + reveal_type(x1) # N: Revealed type is "Any" + reveal_type(y1) # N: Revealed type is "Any" + reveal_type(z1) # N: Revealed type is "Any" + x2, *y2, z2 = arg + reveal_type(x2) # N: Revealed type is "builtins.int" + reveal_type(y2) # N: Revealed type is "builtins.list[builtins.float]" + reveal_type(z2) # N: Revealed type is "builtins.str" + x3, *y3 = arg + reveal_type(x3) # N: Revealed type is "builtins.int" + reveal_type(y3) # N: Revealed type is "builtins.list[builtins.object]" + *y4, z4 = arg + reveal_type(y4) # N: Revealed type is "builtins.list[builtins.float]" + reveal_type(z4) # N: Revealed type is "builtins.str" + x5, xx5, *y5, z5, zz5 = arg # E: Too many assignment targets for variadic unpack + reveal_type(x5) # N: Revealed type is "Any" + reveal_type(xx5) # N: Revealed type is "Any" + reveal_type(y5) # N: Revealed type is "builtins.list[Any]" + reveal_type(z5) # N: Revealed type is "Any" + reveal_type(zz5) # N: Revealed type is "Any" +[builtins fixtures/tuple.pyi] + +[case testRepackingVariadicTuplesTypeVar] +from typing import Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +def foo(arg: Tuple[int, Unpack[Ts], str]) -> None: + x1, *y1, z1 = *arg, + reveal_type(x1) # N: Revealed type is "builtins.int" + reveal_type(y1) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(z1) # N: Revealed type is "builtins.str" + x2, *y2, z2 = 1, *arg, 2 + reveal_type(x2) # N: Revealed type is "builtins.int" + reveal_type(y2) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(z2) # N: Revealed type is "builtins.int" + x3, *y3 = *arg, 42 + reveal_type(x3) # N: Revealed type is "builtins.int" + reveal_type(y3) # N: Revealed type is "builtins.list[builtins.object]" + *y4, z4 = 42, *arg + reveal_type(y4) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(z4) # N: Revealed type is "builtins.str" + x5, xx5, *y5, z5, zz5 = 1, *arg, 2 + reveal_type(x5) # N: Revealed type is "builtins.int" + reveal_type(xx5) # N: Revealed type is "builtins.int" + reveal_type(y5) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(z5) # N: Revealed type is "builtins.str" + reveal_type(zz5) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] + +[case testRepackingVariadicTuplesHomogeneous] +from typing import Tuple +from typing_extensions import Unpack + +def foo(arg: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None: + x1, *y1, z1 = *arg, + reveal_type(x1) # N: Revealed type is "builtins.int" + reveal_type(y1) # N: Revealed type is "builtins.list[builtins.float]" + reveal_type(z1) # N: Revealed type is "builtins.str" + x2, *y2, z2 = 1, *arg, 2 + reveal_type(x2) # N: Revealed type is "builtins.int" + reveal_type(y2) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(z2) # N: Revealed type is "builtins.int" + x3, *y3 = *arg, 42 + reveal_type(x3) # N: Revealed type is "builtins.int" + reveal_type(y3) # N: Revealed type is "builtins.list[builtins.object]" + *y4, z4 = 42, *arg + reveal_type(y4) # N: Revealed type is "builtins.list[builtins.float]" + reveal_type(z4) # N: Revealed type is "builtins.str" + x5, xx5, *y5, z5, zz5 = 1, *arg, 2 + reveal_type(x5) # N: Revealed type is "builtins.int" + reveal_type(xx5) # N: Revealed type is "builtins.int" + reveal_type(y5) # N: Revealed type is "builtins.list[builtins.float]" + reveal_type(z5) # N: Revealed type is "builtins.str" + reveal_type(zz5) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] + +[case testPackingVariadicTuplesTypeVar] +from typing import Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +def foo(arg: Tuple[int, Unpack[Ts], str]) -> None: + x = *arg, + reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]" + y = 1, *arg, 2 + reveal_type(y) # N: Revealed type is "Tuple[builtins.int, builtins.int, Unpack[Ts`-1], builtins.str, builtins.int]" + z = (*arg, *arg) + reveal_type(z) # N: Revealed type is "builtins.tuple[builtins.object, ...]" +[builtins fixtures/tuple.pyi] + +[case testPackingVariadicTuplesHomogeneous] +# flags: --enable-incomplete-feature=PreciseTupleTypes +from typing import Tuple +from typing_extensions import Unpack + +a: Tuple[float, ...] +b: Tuple[int, Unpack[Tuple[float, ...]], str] + +x = *a, +reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.float, ...]" +y = 1, *a, 2 +reveal_type(y) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.int]" +z = (*a, *a) +reveal_type(z) # N: Revealed type is "builtins.tuple[builtins.float, ...]" + +x2 = *b, +reveal_type(x2) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" +y2 = 1, *b, 2 +reveal_type(y2) # N: Revealed type is "Tuple[builtins.int, builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str, builtins.int]" +z2 = (*b, *b) +reveal_type(z2) # N: Revealed type is "builtins.tuple[builtins.object, ...]" +[builtins fixtures/tuple.pyi] + +[case testVariadicTupleInListSetExpr] +from typing import Tuple +from typing_extensions import TypeVarTuple, Unpack + +vt: Tuple[int, Unpack[Tuple[float, ...]], int] +reveal_type([1, *vt]) # N: Revealed type is "builtins.list[builtins.float]" +reveal_type({1, *vt}) # N: Revealed type is "builtins.set[builtins.float]" + +Ts = TypeVarTuple("Ts") +def foo(arg: Tuple[int, Unpack[Ts], str]) -> None: + reveal_type([1, *arg]) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type({1, *arg}) # N: Revealed type is "builtins.set[builtins.object]" +[builtins fixtures/isinstancelist.pyi] + +[case testVariadicTupleInTupleContext] +# flags: --enable-incomplete-feature=PreciseTupleTypes +from typing import Tuple, Optional +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +def test(x: Optional[Tuple[Unpack[Ts]]] = None) -> Tuple[Unpack[Ts]]: ... + +vt: Tuple[int, Unpack[Tuple[float, ...]], int] +vt = 1, *test(), 2 # OK, type context is used +vt2 = 1, *test(), 2 # E: Need type annotation for "vt2" +[builtins fixtures/tuple.pyi] + +[case testVariadicTupleConcatenation] +# flags: --enable-incomplete-feature=PreciseTupleTypes +from typing import Tuple +from typing_extensions import TypeVarTuple, Unpack + +vtf: Tuple[float, ...] +vt: Tuple[int, Unpack[Tuple[float, ...]], int] + +reveal_type(vt + (1, 2)) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.int, Literal[1]?, Literal[2]?]" +reveal_type((1, 2) + vt) # N: Revealed type is "Tuple[Literal[1]?, Literal[2]?, builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.int]" +reveal_type(vt + vt) # N: Revealed type is "builtins.tuple[builtins.float, ...]" +reveal_type(vtf + (1, 2)) # N: Revealed type is "Tuple[Unpack[builtins.tuple[builtins.float, ...]], Literal[1]?, Literal[2]?]" +reveal_type((1, 2) + vtf) # N: Revealed type is "Tuple[Literal[1]?, Literal[2]?, Unpack[builtins.tuple[builtins.float, ...]]]" + +Ts = TypeVarTuple("Ts") +def foo(arg: Tuple[int, Unpack[Ts], str]) -> None: + reveal_type(arg + (1, 2)) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str, Literal[1]?, Literal[2]?]" + reveal_type((1, 2) + arg) # N: Revealed type is "Tuple[Literal[1]?, Literal[2]?, builtins.int, Unpack[Ts`-1], builtins.str]" + reveal_type(arg + arg) # N: Revealed type is "builtins.tuple[builtins.object, ...]" +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleAnyOverload] +from typing import Any, Generic, overload, Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class Array(Generic[Unpack[Ts]]): ... + +class A: + @overload + def f(self, x: Tuple[Unpack[Ts]]) -> Array[Unpack[Ts]]: ... + @overload + def f(self, x: Any) -> Any: ... + def f(self, x: Any) -> Any: + ... +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleInferAgainstAny] +from typing import Any, Tuple, TypeVar +from typing_extensions import Unpack + +T = TypeVar("T") + +def test(x: int, t: Tuple[T, ...]) -> Tuple[int, Unpack[Tuple[T, ...]]]: + ... +a: Any = test(42, ()) +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleIndexTypeVar] +from typing import Any, List, Sequence, Tuple, TypeVar +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +def f(data: Sequence[Tuple[Unpack[Ts]]]) -> List[Any]: + return [d[0] for d in data] # E: Tuple index out of range \ + # N: Variadic tuple can have length 0 + +T = TypeVar("T") +def g(data: Sequence[Tuple[T, Unpack[Ts]]]) -> List[T]: + return [d[0] for d in data] # OK +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleOverloadMatch] +from typing import Any, Generic, overload, Tuple, TypeVar +from typing_extensions import TypeVarTuple, Unpack + +_Ts = TypeVarTuple("_Ts") +_T = TypeVar("_T") +_T2 = TypeVar("_T2") + +class Container(Generic[_T]): ... +class Array(Generic[Unpack[_Ts]]): ... + +@overload +def build(entity: Container[_T], /) -> Array[_T]: ... +@overload +def build(entity: Container[_T], entity2: Container[_T2], /) -> Array[_T, _T2]: ... +@overload +def build(*entities: Container[Any]) -> Array[Unpack[Tuple[Any, ...]]]: ... +def build(*entities: Container[Any]) -> Array[Unpack[Tuple[Any, ...]]]: + ... + +def test(a: Container[Any], b: Container[int], c: Container[str]): + reveal_type(build(a, b)) # N: Revealed type is "__main__.Array[Any, builtins.int]" + reveal_type(build(b, c)) # N: Revealed type is "__main__.Array[builtins.int, builtins.str]" +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleOverloadArbitraryLength] +from typing import Any, Tuple, TypeVar, TypeVarTuple, Unpack, overload + +T = TypeVar("T") +Ts = TypeVarTuple("Ts") +@overload +def add(self: Tuple[Unpack[Ts]], other: Tuple[T]) -> Tuple[Unpack[Ts], T]: + ... +@overload +def add(self: Tuple[T, ...], other: Tuple[T, ...]) -> Tuple[T, ...]: + ... +def add(self: Any, other: Any) -> Any: + ... +def test(a: Tuple[int, str], b: Tuple[bool], c: Tuple[bool, ...]): + reveal_type(add(a, b)) # N: Revealed type is "Tuple[builtins.int, builtins.str, builtins.bool]" + reveal_type(add(b, c)) # N: Revealed type is "builtins.tuple[builtins.bool, ...]" +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleIndexOldStyleNonNormalizedAndNonLiteral] +from typing import Any, Tuple +from typing_extensions import Unpack + +t: Tuple[Unpack[Tuple[int, ...]]] +reveal_type(t[42]) # N: Revealed type is "builtins.int" +i: int +reveal_type(t[i]) # N: Revealed type is "builtins.int" +t1: Tuple[int, Unpack[Tuple[int, ...]]] +reveal_type(t1[i]) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleNotConcreteCallable] +from typing_extensions import Unpack, TypeVarTuple +from typing import Callable, TypeVar, Tuple + +T = TypeVar("T") +Args = TypeVarTuple("Args") +Args2 = TypeVarTuple("Args2") + +def submit(fn: Callable[[Unpack[Args]], T], *args: Unpack[Args]) -> T: + ... + +def submit2(fn: Callable[[int, Unpack[Args]], T], *args: Unpack[Tuple[int, Unpack[Args]]]) -> T: + ... + +def foo(func: Callable[[Unpack[Args]], T], *args: Unpack[Args]) -> T: + return submit(func, *args) + +def foo2(func: Callable[[Unpack[Args2]], T], *args: Unpack[Args2]) -> T: + return submit(func, *args) + +def foo3(func: Callable[[int, Unpack[Args2]], T], *args: Unpack[Args2]) -> T: + return submit2(func, 1, *args) + +def foo_bad(func: Callable[[Unpack[Args2]], T], *args: Unpack[Args2]) -> T: + return submit2(func, 1, *args) # E: Argument 1 to "submit2" has incompatible type "Callable[[VarArg(Unpack[Args2])], T]"; expected "Callable[[int, VarArg(Unpack[Args2])], T]" +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleParamSpecInteraction] +from typing_extensions import Unpack, TypeVarTuple, ParamSpec +from typing import Callable, TypeVar + +T = TypeVar("T") +Args = TypeVarTuple("Args") +Args2 = TypeVarTuple("Args2") +P = ParamSpec("P") + +def submit(fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T: + ... + +def foo(func: Callable[[Unpack[Args]], T], *args: Unpack[Args]) -> T: + return submit(func, *args) + +def foo2(func: Callable[[Unpack[Args]], T], *args: Unpack[Args2]) -> T: + return submit(func, *args) # E: Argument 2 to "submit" has incompatible type "*Tuple[Unpack[Args2]]"; expected "Unpack[Args]" + +def foo3(func: Callable[[int, Unpack[Args2]], T], *args: Unpack[Args2]) -> T: + return submit(func, 1, *args) +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleEmptySpecialCase] +from typing import Any, Callable, Generic +from typing_extensions import Unpack, TypeVarTuple + +Ts = TypeVarTuple("Ts") +class MyClass(Generic[Unpack[Ts]]): + func: Callable[[Unpack[Ts]], object] + + def __init__(self, func: Callable[[Unpack[Ts]], object]) -> None: + self.func = func + +explicit: MyClass[()] +reveal_type(explicit) # N: Revealed type is "__main__.MyClass[()]" +reveal_type(explicit.func) # N: Revealed type is "def () -> builtins.object" + +a: Any +explicit_2 = MyClass[()](a) +reveal_type(explicit_2) # N: Revealed type is "__main__.MyClass[()]" +reveal_type(explicit_2.func) # N: Revealed type is "def () -> builtins.object" + +Alias = MyClass[()] +explicit_3: Alias +reveal_type(explicit_3) # N: Revealed type is "__main__.MyClass[()]" +reveal_type(explicit_3.func) # N: Revealed type is "def () -> builtins.object" + +explicit_4 = Alias(a) +reveal_type(explicit_4) # N: Revealed type is "__main__.MyClass[()]" +reveal_type(explicit_4.func) # N: Revealed type is "def () -> builtins.object" + +def no_args() -> None: ... +implicit = MyClass(no_args) +reveal_type(implicit) # N: Revealed type is "__main__.MyClass[()]" +reveal_type(implicit.func) # N: Revealed type is "def () -> builtins.object" + +def one_arg(__a: int) -> None: ... +x = MyClass(one_arg) +x = explicit # E: Incompatible types in assignment (expression has type "MyClass[()]", variable has type "MyClass[int]") + +# Consistently handle special case for no argument aliases +Direct = MyClass +y = Direct(one_arg) +reveal_type(y) # N: Revealed type is "__main__.MyClass[builtins.int]" +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleRuntimeTypeApplication] +from typing import Generic, TypeVar, Tuple +from typing_extensions import Unpack, TypeVarTuple + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") +class C(Generic[T, Unpack[Ts], S]): ... + +Ints = Tuple[int, int] +x = C[Unpack[Ints]]() +reveal_type(x) # N: Revealed type is "__main__.C[builtins.int, builtins.int]" + +y = C[Unpack[Tuple[int, ...]]]() +reveal_type(y) # N: Revealed type is "__main__.C[builtins.int, Unpack[builtins.tuple[builtins.int, ...]], builtins.int]" + +z = C[int]() # E: Bad number of arguments, expected: at least 2, given: 1 +reveal_type(z) # N: Revealed type is "__main__.C[Any, Unpack[builtins.tuple[Any, ...]], Any]" +[builtins fixtures/tuple.pyi] + +[case testVariadicTupleTupleSubclassPrefixSuffix] +from typing import Tuple +from typing_extensions import Unpack + +i: int + +class A(Tuple[int, Unpack[Tuple[int, ...]]]): ... +a: A +reveal_type(a[i]) # N: Revealed type is "builtins.int" + +class B(Tuple[Unpack[Tuple[int, ...]], int]): ... +b: B +reveal_type(b[i]) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] + +[case testVariadicClassSubclassInit] +from typing import Tuple, Generic, TypeVar +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class B(Generic[Unpack[Ts]]): + def __init__(self, x: Tuple[Unpack[Ts]], *args: Unpack[Ts]) -> None: ... +reveal_type(B) # N: Revealed type is "def [Ts] (x: Tuple[Unpack[Ts`1]], *args: Unpack[Ts`1]) -> __main__.B[Unpack[Ts`1]]" + +T = TypeVar("T") +S = TypeVar("S") +class C(B[T, S]): ... +reveal_type(C) # N: Revealed type is "def [T, S] (x: Tuple[T`1, S`2], T`1, S`2) -> __main__.C[T`1, S`2]" +[builtins fixtures/tuple.pyi] + +[case testVariadicClassGenericSelf] +from typing import Tuple, Generic, TypeVar +from typing_extensions import TypeVarTuple, Unpack + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") +class B(Generic[Unpack[Ts]]): + def copy(self: T) -> T: ... + def on_pair(self: B[T, S]) -> Tuple[T, S]: ... + +b1: B[int] +reveal_type(b1.on_pair()) # E: Invalid self argument "B[int]" to attribute function "on_pair" with type "Callable[[B[T, S]], Tuple[T, S]]" \ + # N: Revealed type is "Tuple[Never, Never]" +b2: B[int, str] +reveal_type(b2.on_pair()) # N: Revealed type is "Tuple[builtins.int, builtins.str]" +b3: B[int, str, int] +reveal_type(b3.on_pair()) # E: Invalid self argument "B[int, str, int]" to attribute function "on_pair" with type "Callable[[B[T, S]], Tuple[T, S]]" \ + # N: Revealed type is "Tuple[Never, Never]" + +class C(B[T, S]): ... +c: C[int, str] +reveal_type(c.copy()) # N: Revealed type is "__main__.C[builtins.int, builtins.str]" +[builtins fixtures/tuple.pyi] + +[case testVariadicClassNewStyleSelf] +from typing import Generic, TypeVar, Self +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class B(Generic[Unpack[Ts]]): + next: Self + def copy(self) -> Self: + return self.next + +b: B[int, str, int] +reveal_type(b.next) # N: Revealed type is "__main__.B[builtins.int, builtins.str, builtins.int]" +reveal_type(b.copy()) # N: Revealed type is "__main__.B[builtins.int, builtins.str, builtins.int]" +reveal_type(B.copy(b)) # N: Revealed type is "__main__.B[builtins.int, builtins.str, builtins.int]" + +T = TypeVar("T") +S = TypeVar("S") +class C(B[T, S]): ... +c: C[int, str] + +reveal_type(c.next) # N: Revealed type is "__main__.C[builtins.int, builtins.str]" +reveal_type(c.copy()) # N: Revealed type is "__main__.C[builtins.int, builtins.str]" +reveal_type(C.copy(c)) # N: Revealed type is "__main__.C[builtins.int, builtins.str]" +[builtins fixtures/tuple.pyi] + +[case testVariadicTupleDataclass] +from dataclasses import dataclass +from typing import Generic, TypeVar, Tuple +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") + +@dataclass +class B(Generic[Unpack[Ts]]): + items: Tuple[Unpack[Ts]] + +reveal_type(B) # N: Revealed type is "def [Ts] (items: Tuple[Unpack[Ts`1]]) -> __main__.B[Unpack[Ts`1]]" +b = B((1, "yes")) +reveal_type(b.items) # N: Revealed type is "Tuple[builtins.int, builtins.str]" + +T = TypeVar("T") +S = TypeVar("S") + +@dataclass +class C(B[T, S]): + first: T + second: S + +reveal_type(C) # N: Revealed type is "def [T, S] (items: Tuple[T`1, S`2], first: T`1, second: S`2) -> __main__.C[T`1, S`2]" +c = C((1, "yes"), 2, "no") +reveal_type(c.items) # N: Revealed type is "Tuple[builtins.int, builtins.str]" +reveal_type(c.first) # N: Revealed type is "builtins.int" +reveal_type(c.second) # N: Revealed type is "builtins.str" +[builtins fixtures/dataclasses.pyi] +[typing fixtures/typing-medium.pyi] + +[case testVariadicTupleInProtocol] +from typing import Protocol, Tuple, List +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class P(Protocol[Unpack[Ts]]): + def items(self) -> Tuple[Unpack[Ts]]: ... + +class PC(Protocol[Unpack[Ts]]): + def meth(self, *args: Unpack[Ts]) -> None: ... + +def get_items(x: P[Unpack[Ts]]) -> Tuple[Unpack[Ts]]: ... +def match(x: PC[Unpack[Ts]]) -> Tuple[Unpack[Ts]]: ... + +class Bad: + def items(self) -> List[int]: ... + def meth(self, *, named: int) -> None: ... + +class Good: + def items(self) -> Tuple[int, str]: ... + def meth(self, __x: int, y: str) -> None: ... + +g: Good +reveal_type(get_items(g)) # N: Revealed type is "Tuple[builtins.int, builtins.str]" +reveal_type(match(g)) # N: Revealed type is "Tuple[builtins.int, builtins.str]" + +b: Bad +get_items(b) # E: Argument 1 to "get_items" has incompatible type "Bad"; expected "P[Unpack[Tuple[Never, ...]]]" \ + # N: Following member(s) of "Bad" have conflicts: \ + # N: Expected: \ + # N: def items(self) -> Tuple[Never, ...] \ + # N: Got: \ + # N: def items(self) -> List[int] +match(b) # E: Argument 1 to "match" has incompatible type "Bad"; expected "PC[Unpack[Tuple[Never, ...]]]" \ + # N: Following member(s) of "Bad" have conflicts: \ + # N: Expected: \ + # N: def meth(self, *args: Never) -> None \ + # N: Got: \ + # N: def meth(self, *, named: int) -> None +[builtins fixtures/tuple.pyi] + +[case testVariadicTupleCollectionCheck] +from typing import Tuple, Optional +from typing_extensions import Unpack + +allowed: Tuple[int, Unpack[Tuple[int, ...]]] + +x: Optional[int] +if x in allowed: + reveal_type(x) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] + +[case testJoinOfVariadicTupleCallablesNoCrash] +from typing import Callable, Tuple + +f: Callable[[int, *Tuple[str, ...], int], None] +g: Callable[[int, *Tuple[str, ...], int], None] +reveal_type([f, g]) # N: Revealed type is "builtins.list[def (builtins.int, *Unpack[Tuple[Unpack[builtins.tuple[builtins.str, ...]], builtins.int]])]" + +h: Callable[[int, *Tuple[str, ...], str], None] +reveal_type([f, h]) # N: Revealed type is "builtins.list[def (builtins.int, *Unpack[Tuple[Unpack[builtins.tuple[builtins.str, ...]], Never]])]" +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleBothUnpacksSimple] +from typing import Tuple +from typing_extensions import Unpack, TypeVarTuple, TypedDict + +class Keywords(TypedDict): + a: str + b: str + +Ints = Tuple[int, ...] + +def f(*args: Unpack[Ints], other: str = "no", **kwargs: Unpack[Keywords]) -> None: ... +reveal_type(f) # N: Revealed type is "def (*args: builtins.int, other: builtins.str =, **kwargs: Unpack[TypedDict('__main__.Keywords', {'a': builtins.str, 'b': builtins.str})])" +f(1, 2, a="a", b="b") # OK +f(1, 2, 3) # E: Missing named argument "a" for "f" \ + # E: Missing named argument "b" for "f" + +Ts = TypeVarTuple("Ts") +def g(*args: Unpack[Ts], other: str = "no", **kwargs: Unpack[Keywords]) -> None: ... +reveal_type(g) # N: Revealed type is "def [Ts] (*args: Unpack[Ts`-1], other: builtins.str =, **kwargs: Unpack[TypedDict('__main__.Keywords', {'a': builtins.str, 'b': builtins.str})])" +g(1, 2, a="a", b="b") # OK +g(1, 2, 3) # E: Missing named argument "a" for "g" \ + # E: Missing named argument "b" for "g" + +def bad( + *args: Unpack[Keywords], # E: "Keywords" cannot be unpacked (must be tuple or TypeVarTuple) + **kwargs: Unpack[Ints], # E: Unpack item in ** argument must be a TypedDict +) -> None: ... +reveal_type(bad) # N: Revealed type is "def (*args: Any, **kwargs: Any)" + +def bad2( + one: int, + *args: Unpack[Keywords], # E: "Keywords" cannot be unpacked (must be tuple or TypeVarTuple) + other: str = "no", + **kwargs: Unpack[Ints], # E: Unpack item in ** argument must be a TypedDict +) -> None: ... +reveal_type(bad2) # N: Revealed type is "def (one: builtins.int, *args: Any, other: builtins.str =, **kwargs: Any)" +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleBothUnpacksCallable] +from typing import Callable, Tuple +from typing_extensions import Unpack, TypedDict + +class Keywords(TypedDict): + a: str + b: str +Ints = Tuple[int, ...] + +cb: Callable[[Unpack[Ints], Unpack[Keywords]], None] +reveal_type(cb) # N: Revealed type is "def (*builtins.int, **Unpack[TypedDict('__main__.Keywords', {'a': builtins.str, 'b': builtins.str})])" + +cb2: Callable[[int, Unpack[Ints], int, Unpack[Keywords]], None] +reveal_type(cb2) # N: Revealed type is "def (builtins.int, *Unpack[Tuple[Unpack[builtins.tuple[builtins.int, ...]], builtins.int]], **Unpack[TypedDict('__main__.Keywords', {'a': builtins.str, 'b': builtins.str})])" +cb2(1, 2, 3, a="a", b="b") +cb2(1, a="a", b="b") # E: Too few arguments +cb2(1, 2, 3, a="a") # E: Missing named argument "b" + +bad1: Callable[[Unpack[Ints], Unpack[Ints]], None] # E: More than one Unpack in a type is not allowed +reveal_type(bad1) # N: Revealed type is "def (*builtins.int)" +bad2: Callable[[Unpack[Keywords], Unpack[Keywords]], None] # E: "Keywords" cannot be unpacked (must be tuple or TypeVarTuple) +reveal_type(bad2) # N: Revealed type is "def (*Any, **Unpack[TypedDict('__main__.Keywords', {'a': builtins.str, 'b': builtins.str})])" +bad3: Callable[[Unpack[Keywords], Unpack[Ints]], None] # E: "Keywords" cannot be unpacked (must be tuple or TypeVarTuple) \ + # E: More than one Unpack in a type is not allowed +reveal_type(bad3) # N: Revealed type is "def (*Any)" +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleBothUnpacksApplication] +from typing import Callable, TypeVar, Optional +from typing_extensions import Unpack, TypeVarTuple, TypedDict + +class Keywords(TypedDict): + a: str + b: str + +T = TypeVar("T") +Ts = TypeVarTuple("Ts") +def test( + x: int, + func: Callable[[Unpack[Ts]], T], + *args: Unpack[Ts], + other: Optional[str] = None, + **kwargs: Unpack[Keywords], +) -> T: + if bool(): + func(*args, **kwargs) # E: Extra argument "a" from **args + return func(*args) +def test2( + x: int, + func: Callable[[Unpack[Ts], Unpack[Keywords]], T], + *args: Unpack[Ts], + other: Optional[str] = None, + **kwargs: Unpack[Keywords], +) -> T: + if bool(): + func(*args) # E: Missing named argument "a" \ + # E: Missing named argument "b" + return func(*args, **kwargs) +[builtins fixtures/tuple.pyi] + +[case testUnpackTupleSpecialCaseNoCrash] +from typing import Tuple, TypeVar +from typing_extensions import Unpack + +T = TypeVar("T") + +def foo(*x: object) -> None: ... +def bar(*x: int) -> None: ... +def baz(*x: T) -> T: ... + +keys: Tuple[Unpack[Tuple[int, ...]]] + +foo(keys, 1) +foo(*keys, 1) + +bar(keys, 1) # E: Argument 1 to "bar" has incompatible type "Tuple[Unpack[Tuple[int, ...]]]"; expected "int" +bar(*keys, 1) # OK + +reveal_type(baz(keys, 1)) # N: Revealed type is "builtins.object" +reveal_type(baz(*keys, 1)) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] + +[case testVariadicTupleContextNoCrash] +from typing import Tuple, Unpack + +x: Tuple[int, Unpack[Tuple[int, ...]]] = () # E: Incompatible types in assignment (expression has type "Tuple[()]", variable has type "Tuple[int, Unpack[Tuple[int, ...]]]") +y: Tuple[int, Unpack[Tuple[int, ...]]] = (1, 2) +z: Tuple[int, Unpack[Tuple[int, ...]]] = (1,) +w: Tuple[int, Unpack[Tuple[int, ...]]] = (1, *[2, 3, 4]) +t: Tuple[int, Unpack[Tuple[int, ...]]] = (1, *(2, 3, 4)) +[builtins fixtures/tuple.pyi] + +[case testAliasToCallableWithUnpack] +from typing import Any, Callable, Tuple, Unpack + +_CallableValue = Callable[[Unpack[Tuple[Any, ...]]], Any] +def higher_order(f: _CallableValue) -> None: ... + +def good1(*args: int) -> None: ... +def good2(*args: str) -> int: ... + +def bad1(a: str, b: int, /) -> None: ... +def bad2(c: bytes, *args: int) -> str: ... +def bad3(*, d: str) -> int: ... +def bad4(**kwargs: None) -> None: ... + +higher_order(good1) +higher_order(good2) + +higher_order(bad1) # E: Argument 1 to "higher_order" has incompatible type "Callable[[str, int], None]"; expected "Callable[[VarArg(Any)], Any]" +higher_order(bad2) # E: Argument 1 to "higher_order" has incompatible type "Callable[[bytes, VarArg(int)], str]"; expected "Callable[[VarArg(Any)], Any]" +higher_order(bad3) # E: Argument 1 to "higher_order" has incompatible type "Callable[[NamedArg(str, 'd')], int]"; expected "Callable[[VarArg(Any)], Any]" +higher_order(bad4) # E: Argument 1 to "higher_order" has incompatible type "Callable[[KwArg(None)], None]"; expected "Callable[[VarArg(Any)], Any]" +[builtins fixtures/tuple.pyi] + +[case testAliasToCallableWithUnpack2] +from typing import Any, Callable, Tuple, Unpack + +_CallableValue = Callable[[int, str, Unpack[Tuple[Any, ...]], int], Any] +def higher_order(f: _CallableValue) -> None: ... + +def good(a: int, b: str, *args: Unpack[Tuple[Unpack[Tuple[Any, ...]], int]]) -> int: ... +def bad1(a: str, b: int, /) -> None: ... +def bad2(c: bytes, *args: int) -> str: ... +def bad3(*, d: str) -> int: ... +def bad4(**kwargs: None) -> None: ... + +higher_order(good) +higher_order(bad1) # E: Argument 1 to "higher_order" has incompatible type "Callable[[str, int], None]"; expected "Callable[[int, str, VarArg(Unpack[Tuple[Unpack[Tuple[Any, ...]], int]])], Any]" +higher_order(bad2) # E: Argument 1 to "higher_order" has incompatible type "Callable[[bytes, VarArg(int)], str]"; expected "Callable[[int, str, VarArg(Unpack[Tuple[Unpack[Tuple[Any, ...]], int]])], Any]" +higher_order(bad3) # E: Argument 1 to "higher_order" has incompatible type "Callable[[NamedArg(str, 'd')], int]"; expected "Callable[[int, str, VarArg(Unpack[Tuple[Unpack[Tuple[Any, ...]], int]])], Any]" +higher_order(bad4) # E: Argument 1 to "higher_order" has incompatible type "Callable[[KwArg(None)], None]"; expected "Callable[[int, str, VarArg(Unpack[Tuple[Unpack[Tuple[Any, ...]], int]])], Any]" +[builtins fixtures/tuple.pyi] + +[case testAliasToCallableWithUnpackInvalid] +from typing import Any, Callable, List, Tuple, TypeVar, Unpack + +T = TypeVar("T") +Ts = TypeVarTuple("Ts") # E: Name "TypeVarTuple" is not defined + +def good(*x: int) -> int: ... +def bad(*x: int, y: int) -> int: ... + +Alias = Callable[[Unpack[T]], int] # E: "T" cannot be unpacked (must be tuple or TypeVarTuple) +x: Alias[int] +reveal_type(x) # N: Revealed type is "def (*Any) -> builtins.int" +x = good +x = bad # E: Incompatible types in assignment (expression has type "Callable[[VarArg(int), NamedArg(int, 'y')], int]", variable has type "Callable[[VarArg(Any)], int]") +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleInvariant] +from typing import Generic, Tuple +from typing_extensions import Unpack, TypeVarTuple +Ts = TypeVarTuple("Ts") + +class Array(Generic[Unpack[Ts]]): ... + +def pointwise_multiply(x: Array[Unpack[Ts]], y: Array[Unpack[Ts]]) -> Array[Unpack[Ts]]: ... + +def a1(x: Array[int], y: Array[str], z: Array[int, str]) -> None: + reveal_type(pointwise_multiply(x, x)) # N: Revealed type is "__main__.Array[builtins.int]" + reveal_type(pointwise_multiply(x, y)) # E: Cannot infer type argument 1 of "pointwise_multiply" \ + # N: Revealed type is "__main__.Array[Unpack[builtins.tuple[Any, ...]]]" + reveal_type(pointwise_multiply(x, z)) # E: Cannot infer type argument 1 of "pointwise_multiply" \ + # N: Revealed type is "__main__.Array[Unpack[builtins.tuple[Any, ...]]]" + +def func(x: Array[Unpack[Ts]], *args: Unpack[Ts]) -> Tuple[Unpack[Ts]]: + ... + +def a2(x: Array[int, str]) -> None: + reveal_type(func(x, 2, "Hello")) # N: Revealed type is "Tuple[builtins.int, builtins.str]" + reveal_type(func(x, 2)) # E: Cannot infer type argument 1 of "func" \ + # N: Revealed type is "builtins.tuple[Any, ...]" + reveal_type(func(x, 2, "Hello", True)) # E: Cannot infer type argument 1 of "func" \ + # N: Revealed type is "builtins.tuple[Any, ...]" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-typevar-unbound.test b/test-data/unit/check-typevar-unbound.test index d3e54c75e373..ed6beaa100db 100644 --- a/test-data/unit/check-typevar-unbound.test +++ b/test-data/unit/check-typevar-unbound.test @@ -15,8 +15,7 @@ def g() -> U: # E: A function returning TypeVar should receive at least one argu V = TypeVar('V', int, str) -# TODO: this should also give an error -def h() -> V: +def h() -> V: # E: A function returning TypeVar should receive at least one argument containing the same TypeVar ... [case testInnerFunctionTypeVar] diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index a4a4d68bd9fe..effaf620f1f0 100644 --- a/test-data/unit/check-typevar-values.test +++ b/test-data/unit/check-typevar-values.test @@ -97,8 +97,8 @@ def f(x: AB) -> AB: from typing import TypeVar T = TypeVar('T', int, str) def f(x: T) -> T: - a = None # type: T - b = None # type: T + a: T + b: T if 1: a = x b = x @@ -248,10 +248,10 @@ def g(a: T) -> None: from typing import TypeVar, Generic, Any X = TypeVar('X', int, str) class A(Generic[X]): pass -a = None # type: A[int] -b = None # type: A[str] -d = None # type: A[object] # E: Value of type variable "X" of "A" cannot be "object" -c = None # type: A[Any] +a: A[int] +b: A[str] +d: A[object] # E: Value of type variable "X" of "A" cannot be "object" +c: A[Any] [case testConstructGenericTypeWithTypevarValuesAndTypeInference] from typing import TypeVar, Generic, Any, cast @@ -272,11 +272,11 @@ Z = TypeVar('Z') class D(Generic[X]): def __init__(self, x: X) -> None: pass def f(x: X) -> None: - a = None # type: D[X] + a: D[X] def g(x: Y) -> None: - a = None # type: D[Y] + a: D[Y] def h(x: Z) -> None: - a = None # type: D[Z] + a: D[Z] [out] main:11: error: Invalid type argument value for "D" main:13: error: Type variable "Z" not valid as type argument value for "D" @@ -287,7 +287,7 @@ X = TypeVar('X', int, str) class S(str): pass class C(Generic[X]): def __init__(self, x: X) -> None: pass -x = None # type: C[str] +x: C[str] y = C(S()) if int(): x = y @@ -412,10 +412,10 @@ class B: pass X = TypeVar('X', A, B) Y = TypeVar('Y', int, str) class C(Generic[X, Y]): pass -a = None # type: C[A, int] -b = None # type: C[B, str] -c = None # type: C[int, int] # E: Value of type variable "X" of "C" cannot be "int" -d = None # type: C[A, A] # E: Value of type variable "Y" of "C" cannot be "A" +a: C[A, int] +b: C[B, str] +c: C[int, int] # E: Value of type variable "X" of "C" cannot be "int" +d: C[A, A] # E: Value of type variable "Y" of "C" cannot be "A" [case testCallGenericFunctionUsingMultipleTypevarsWithValues] from typing import TypeVar @@ -512,7 +512,7 @@ class C(A[str]): from typing import TypeVar, Generic T = TypeVar('T', int, str) class C(Generic[T]): - def f(self, x: int = None) -> None: pass + def f(self, x: int = 2) -> None: pass [case testTypevarValuesWithOverloadedFunctionSpecialCase] from foo import * @@ -660,8 +660,6 @@ T = TypeVar("T", bound=Union[Data, Dict[str, str]]) def f(data: T) -> None: reveal_type(data["x"]) # N: Revealed type is "Union[builtins.int, builtins.str]" - -[builtins fixtures/tuple.pyi] [builtins fixtures/dict.pyi] [case testTypeVarWithTypedDictValueInIndexExpression] @@ -677,7 +675,6 @@ T = TypeVar("T", Data, Dict[str, str]) def f(data: T) -> None: _: Union[str, int] = data["x"] -[builtins fixtures/tuple.pyi] [builtins fixtures/dict.pyi] [case testSelfTypeVarIndexExpr] @@ -699,8 +696,6 @@ class Indexable: def m(self: T) -> T: return self["foo"] - -[builtins fixtures/tuple.pyi] [builtins fixtures/classmethod.pyi] [case testTypeVarWithValueDeferral] diff --git a/test-data/unit/check-underscores.test b/test-data/unit/check-underscores.test index ac9fad2ca792..2a789b3314f3 100644 --- a/test-data/unit/check-underscores.test +++ b/test-data/unit/check-underscores.test @@ -1,10 +1,4 @@ -[case testUnderscoresRequire36] -# flags: --python-version 3.5 -x = 1000_000 # E: Underscores in numeric literals are only supported in Python 3.6 and greater -[out] - [case testUnderscoresBasics] -# flags: --python-version 3.6 x: int x = 1000_000 x = 0x_FF_FF_FF_FF diff --git a/test-data/unit/check-union-error-syntax.test b/test-data/unit/check-union-error-syntax.test new file mode 100644 index 000000000000..2928cc312709 --- /dev/null +++ b/test-data/unit/check-union-error-syntax.test @@ -0,0 +1,61 @@ +[case testUnionErrorSyntax] +# flags: --python-version 3.10 --no-force-union-syntax +from typing import Union +x : Union[bool, str] +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "bool | str") + +[case testOrErrorSyntax] +# flags: --python-version 3.10 --force-union-syntax +from typing import Union +x : Union[bool, str] +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "Union[bool, str]") + +[case testOrNoneErrorSyntax] +# flags: --python-version 3.10 --no-force-union-syntax +from typing import Union +x : Union[bool, None] +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "bool | None") + +[case testOptionalErrorSyntax] +# flags: --python-version 3.10 --force-union-syntax +from typing import Union +x : Union[bool, None] +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "Optional[bool]") + +[case testNoneAsFinalItem] +# flags: --python-version 3.10 --no-force-union-syntax +from typing import Union +x : Union[bool, None, str] +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "bool | str | None") + +[case testLiteralOrErrorSyntax] +# flags: --python-version 3.10 --no-force-union-syntax +from typing import Union +from typing_extensions import Literal +x : Union[Literal[1], Literal[2], str] +x = 3 # E: Incompatible types in assignment (expression has type "Literal[3]", variable has type "Literal[1, 2] | str") +[builtins fixtures/tuple.pyi] + +[case testLiteralUnionErrorSyntax] +# flags: --python-version 3.10 --force-union-syntax +from typing import Union +from typing_extensions import Literal +x : Union[Literal[1], Literal[2], str] +x = 3 # E: Incompatible types in assignment (expression has type "Literal[3]", variable has type "Union[str, Literal[1, 2]]") +[builtins fixtures/tuple.pyi] + +[case testLiteralOrNoneErrorSyntax] +# flags: --python-version 3.10 --no-force-union-syntax +from typing import Union +from typing_extensions import Literal +x : Union[Literal[1], None] +x = 3 # E: Incompatible types in assignment (expression has type "Literal[3]", variable has type "Literal[1] | None") +[builtins fixtures/tuple.pyi] + +[case testLiteralOptionalErrorSyntax] +# flags: --python-version 3.10 --force-union-syntax +from typing import Union +from typing_extensions import Literal +x : Union[Literal[1], None] +x = 3 # E: Incompatible types in assignment (expression has type "Literal[3]", variable has type "Optional[Literal[1]]") +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-union-or-syntax.test b/test-data/unit/check-union-or-syntax.test index 58526cfd0623..85e268f348f0 100644 --- a/test-data/unit/check-union-or-syntax.test +++ b/test-data/unit/check-union-or-syntax.test @@ -66,8 +66,8 @@ x: List[int | str] reveal_type(x) # N: Revealed type is "builtins.list[Union[builtins.int, builtins.str]]" [builtins fixtures/list.pyi] -[case testUnionOrSyntaxWithQuotedFunctionTypes] -# flags: --python-version 3.4 +[case testUnionOrSyntaxWithQuotedFunctionTypesPre310] +# flags: --python-version 3.9 from typing import Union def f(x: 'Union[int, str, None]') -> 'Union[int, None]': reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str, None]" @@ -79,8 +79,8 @@ def g(x: "int | str | None") -> "int | None": return 42 reveal_type(g) # N: Revealed type is "def (x: Union[builtins.int, builtins.str, None]) -> Union[builtins.int, None]" -[case testUnionOrSyntaxWithQuotedVariableTypes] -# flags: --python-version 3.6 +[case testUnionOrSyntaxWithQuotedVariableTypesPre310] +# flags: --python-version 3.9 y: "int | str" = 42 reveal_type(y) # N: Revealed type is "Union[builtins.int, builtins.str]" @@ -124,11 +124,9 @@ cast(str | int, 'x') # E: Cast target is not a type [typing fixtures/typing-full.pyi] [case testUnionOrSyntaxInComment] -# flags: --python-version 3.6 x = 1 # type: int | str [case testUnionOrSyntaxFutureImport] -# flags: --python-version 3.7 from __future__ import annotations x: int | None [builtins fixtures/tuple.pyi] @@ -138,7 +136,7 @@ x: int | None x: int | None # E: X | Y syntax for unions requires Python 3.10 [case testUnionOrSyntaxInStubFile] -# flags: --python-version 3.6 +# flags: --python-version 3.9 from lib import x [file lib.pyi] x: int | None diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index 65d5c1abc7e8..2e69a96f0c78 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -55,12 +55,12 @@ class B: y = 2 class C: pass class D: pass -u = None # type: Union[A, C, D] -v = None # type: Union[C, D] -w = None # type: Union[A, B] -x = None # type: Union[A, C] -y = None # type: int -z = None # type: str +u: Union[A, C, D] +v: Union[C, D] +w: Union[A, B] +x: Union[A, C] +y: int +z: str if int(): y = w.y @@ -89,9 +89,9 @@ class B: class C: def foo(self) -> str: pass -x = None # type: Union[A, B] -y = None # type: Union[A, C] -i = None # type: int +x: Union[A, B] +y: Union[A, C] +i: int x.foo() y.foo() @@ -103,7 +103,7 @@ if int(): [case testUnionIndexing] from typing import Union, List -x = None # type: Union[List[int], str] +x: Union[List[int], str] x[2] x[2] + 1 # E: Unsupported operand types for + ("str" and "int") \ # N: Left operand is of type "Union[int, str]" @@ -132,6 +132,7 @@ def f(x: Union[int, str]) -> int: pass def f(x: type) -> str: pass [case testUnionWithNoneItem] +# flags: --no-strict-optional from typing import Union def f() -> Union[int, None]: pass x = 1 @@ -221,6 +222,7 @@ else: # N: def g(x: Union[int, str]) -> None [case testUnionSimplificationSpecialCases] +# flags: --no-strict-optional from typing import Any, TypeVar, Union class C(Any): pass @@ -266,9 +268,10 @@ class M(Generic[V]): def f(x: M[C]) -> None: y = x.get(None) - reveal_type(y) # N: Revealed type is "__main__.C" + reveal_type(y) # N: Revealed type is "Union[__main__.C, None]" [case testUnionSimplificationSpecialCases2] +# flags: --no-strict-optional from typing import Any, TypeVar, Union class C(Any): pass @@ -317,10 +320,10 @@ S = TypeVar('S') R = TypeVar('R') def u(x: T, y: S, z: R) -> Union[R, S, T]: pass -a = None # type: Any +a: Any reveal_type(u(1, 1, 1)) # N: Revealed type is "builtins.int" -reveal_type(u(C(), C(), None)) # N: Revealed type is "__main__.C" +reveal_type(u(C(), C(), None)) # N: Revealed type is "Union[None, __main__.C]" reveal_type(u(a, a, 1)) # N: Revealed type is "Union[builtins.int, Any]" reveal_type(u(a, C(), a)) # N: Revealed type is "Union[Any, __main__.C]" reveal_type(u('', 1, 1)) # N: Revealed type is "Union[builtins.int, builtins.str]" @@ -370,9 +373,9 @@ T = TypeVar('T') S = TypeVar('S') def u(x: T, y: S) -> Union[S, T]: pass -t_o = None # type: Type[object] -t_s = None # type: Type[str] -t_a = None # type: Type[Any] +t_o: Type[object] +t_s: Type[str] +t_a: Type[Any] # Two identical items reveal_type(u(t_o, t_o)) # N: Revealed type is "Type[builtins.object]" @@ -397,10 +400,10 @@ T = TypeVar('T') S = TypeVar('S') def u(x: T, y: S) -> Union[S, T]: pass -t_o = None # type: Type[object] -t_s = None # type: Type[str] -t_a = None # type: Type[Any] -t = None # type: type +t_o: Type[object] +t_s: Type[str] +t_a: Type[Any] +t: type # Union with object reveal_type(u(t_o, object())) # N: Revealed type is "builtins.object" @@ -926,7 +929,6 @@ reveal_type(z) # N: Revealed type is "Union[builtins.int, __main__.A, builtins.s [out] [case testUnpackUnionNoCrashOnPartialNone] -# flags: --strict-optional from typing import Dict, Tuple, List, Any a: Any @@ -941,7 +943,6 @@ if x: [out] [case testUnpackUnionNoCrashOnPartialNone2] -# flags: --strict-optional from typing import Dict, Tuple, List, Any a: Any @@ -957,7 +958,6 @@ if x: [out] [case testUnpackUnionNoCrashOnPartialNoneBinder] -# flags: --strict-optional from typing import Dict, Tuple, List, Any x: object @@ -972,7 +972,6 @@ if x: [out] [case testUnpackUnionNoCrashOnPartialList] -# flags: --strict-optional from typing import Dict, Tuple, List, Any a: Any @@ -1004,12 +1003,15 @@ def takes_int(arg: int) -> None: pass takes_int(x) # E: Argument 1 to "takes_int" has incompatible type "Union[ExtremelyLongTypeNameWhichIsGenericSoWeCanUseItMultipleTimes[int], ExtremelyLongTypeNameWhichIsGenericSoWeCanUseItMultipleTimes[object], ExtremelyLongTypeNameWhichIsGenericSoWeCanUseItMultipleTimes[float], ExtremelyLongTypeNameWhichIsGenericSoWeCanUseItMultipleTimes[str], ExtremelyLongTypeNameWhichIsGenericSoWeCanUseItMultipleTimes[Any], ExtremelyLongTypeNameWhichIsGenericSoWeCanUseItMultipleTimes[bytes]]"; expected "int" [case testRecursiveForwardReferenceInUnion] -# flags: --disable-recursive-aliases from typing import List, Union -MYTYPE = List[Union[str, "MYTYPE"]] # E: Cannot resolve name "MYTYPE" (possible cyclic definition) + +def test() -> None: + MYTYPE = List[Union[str, "MYTYPE"]] # E: Cannot resolve name "MYTYPE" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope [builtins fixtures/list.pyi] [case testNonStrictOptional] +# flags: --no-strict-optional from typing import Optional, List def union_test1(x): @@ -1077,7 +1079,6 @@ def bar(a: T4, b: T4) -> T4: # test multi-level alias [builtins fixtures/ops.pyi] [case testJoinUnionWithUnionAndAny] -# flags: --strict-optional from typing import TypeVar, Union, Any T = TypeVar("T") def f(x: T, y: T) -> T: @@ -1219,3 +1220,41 @@ nc: Union[Container[str], int] 'x' in nc # E: Unsupported right operand type for in ("Union[Container[str], int]") [builtins fixtures/tuple.pyi] [typing fixtures/typing-full.pyi] + +[case testDescriptorAccessForUnionOfTypes] +from typing import overload, Generic, Any, TypeVar, List, Optional, Union, Type + +_T_co = TypeVar("_T_co", bound=Any, covariant=True) + +class Mapped(Generic[_T_co]): + def __init__(self, value: _T_co): + self.value = value + + @overload + def __get__( + self, instance: None, owner: Any + ) -> List[_T_co]: + ... + + @overload + def __get__(self, instance: object, owner: Any) -> _T_co: + ... + + def __get__( + self, instance: Optional[object], owner: Any + ) -> Union[List[_T_co], _T_co]: + return self.value + +class A: + field_1: Mapped[int] = Mapped(1) + field_2: Mapped[str] = Mapped('1') + +class B: + field_1: Mapped[int] = Mapped(2) + field_2: Mapped[str] = Mapped('2') + +mix: Union[Type[A], Type[B]] = A +reveal_type(mix) # N: Revealed type is "Union[Type[__main__.A], Type[__main__.B]]" +reveal_type(mix.field_1) # N: Revealed type is "builtins.list[builtins.int]" +reveal_type(mix().field_1) # N: Revealed type is "builtins.int" +[builtins fixtures/list.pyi] diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index 48459dd8941a..b8b438b979c6 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -422,9 +422,9 @@ x = 1 [out] [case testCustomSysVersionInfo] -# flags: --python-version 3.5 +# flags: --python-version 3.11 import sys -if sys.version_info == (3, 5): +if sys.version_info == (3, 11): x = "foo" else: x = 3 @@ -433,7 +433,7 @@ reveal_type(x) # N: Revealed type is "builtins.str" [out] [case testCustomSysVersionInfo2] -# flags: --python-version 3.5 +# flags: --python-version 3.11 import sys if sys.version_info == (3, 6): x = "foo" @@ -615,7 +615,6 @@ reveal_type(x) # N: Revealed type is "__main__.B" [typing fixtures/typing-medium.pyi] [case testUnreachableWhenSuperclassIsAny] -# flags: --strict-optional from typing import Any # This can happen if we're importing a class from a missing module @@ -799,7 +798,7 @@ def baz(x: int) -> int: [builtins fixtures/exception.pyi] [case testUnreachableFlagIgnoresSemanticAnalysisUnreachable] -# flags: --warn-unreachable --python-version 3.7 --platform win32 --always-false FOOBAR +# flags: --warn-unreachable --python-version 3.8 --platform win32 --always-false FOOBAR import sys from typing import TYPE_CHECKING @@ -829,7 +828,7 @@ if sys.version_info == (2, 7): else: reveal_type(x) # N: Revealed type is "builtins.int" -if sys.version_info == (3, 7): +if sys.version_info == (3, 8): reveal_type(x) # N: Revealed type is "builtins.int" else: reveal_type(x) @@ -873,15 +872,15 @@ def expect_str(x: str) -> str: pass x: int if False: assert False - reveal_type(x) + reveal_type(x) # E: Statement is unreachable if False: raise Exception() - reveal_type(x) + reveal_type(x) # E: Statement is unreachable if False: assert_never(x) - reveal_type(x) + reveal_type(x) # E: Statement is unreachable if False: nonthrowing_assert_never(x) # E: Statement is unreachable @@ -890,7 +889,7 @@ if False: if False: # Ignore obvious type errors assert_never(expect_str(x)) - reveal_type(x) + reveal_type(x) # E: Statement is unreachable [builtins fixtures/exception.pyi] [case testNeverVariants] @@ -901,15 +900,15 @@ from typing_extensions import NoReturn as TENoReturn from mypy_extensions import NoReturn as MENoReturn bottom1: Never -reveal_type(bottom1) # N: Revealed type is "" +reveal_type(bottom1) # N: Revealed type is "Never" bottom2: TENever -reveal_type(bottom2) # N: Revealed type is "" +reveal_type(bottom2) # N: Revealed type is "Never" bottom3: NoReturn -reveal_type(bottom3) # N: Revealed type is "" +reveal_type(bottom3) # N: Revealed type is "Never" bottom4: TENoReturn -reveal_type(bottom4) # N: Revealed type is "" +reveal_type(bottom4) # N: Revealed type is "Never" bottom5: MENoReturn -reveal_type(bottom5) # N: Revealed type is "" +reveal_type(bottom5) # N: Revealed type is "Never" [builtins fixtures/tuple.pyi] @@ -936,7 +935,7 @@ class Case1: return False and self.missing() # E: Right operand of "and" is never evaluated def test2(self) -> bool: - return not self.property_decorator_missing and self.missing() # E: Function "Callable[[], bool]" could always be true in boolean context \ + return not self.property_decorator_missing and self.missing() # E: Function "property_decorator_missing" could always be true in boolean context \ # E: Right operand of "and" is never evaluated def property_decorator_missing(self) -> bool: @@ -1156,7 +1155,7 @@ def f_suppress() -> int: # E: Missing return statement [builtins fixtures/tuple.pyi] [case testUnreachableFlagContextAsyncManagersNoSuppress] -# flags: --warn-unreachable --python-version 3.7 +# flags: --warn-unreachable from contextlib import asynccontextmanager from typing import Optional, AsyncIterator, Any from typing_extensions import Literal @@ -1222,7 +1221,7 @@ async def f_no_suppress_5() -> int: [builtins fixtures/tuple.pyi] [case testUnreachableFlagContextAsyncManagersSuppressed] -# flags: --warn-unreachable --python-version 3.7 +# flags: --warn-unreachable from contextlib import asynccontextmanager from typing import Optional, AsyncIterator, Any from typing_extensions import Literal @@ -1269,7 +1268,7 @@ async def f_mix() -> int: # E: Missing return statement [builtins fixtures/tuple.pyi] [case testUnreachableFlagContextAsyncManagersAbnormal] -# flags: --warn-unreachable --python-version 3.7 +# flags: --warn-unreachable from contextlib import asynccontextmanager from typing import Optional, AsyncIterator, Any from typing_extensions import Literal @@ -1380,6 +1379,38 @@ def f() -> None: x = 1 # E: Statement is unreachable [builtins fixtures/dict.pyi] +[case testUnreachableLiteralFrom__bool__] +# flags: --warn-unreachable +from typing_extensions import Literal + +class Truth: + def __bool__(self) -> Literal[True]: ... + +class Lie: + def __bool__(self) -> Literal[False]: ... + +class Maybe: + def __bool__(self) -> Literal[True | False]: ... + +t = Truth() +if t: + x = 1 +else: + x = 2 # E: Statement is unreachable + +if Lie(): + x = 3 # E: Statement is unreachable + +if Maybe(): + x = 4 + + +def foo() -> bool: ... + +y = Truth() or foo() # E: Right operand of "or" is never evaluated +z = Lie() and foo() # E: Right operand of "and" is never evaluated +[builtins fixtures/dict.pyi] + [case testUnreachableModuleBody1] # flags: --warn-unreachable from typing import NoReturn @@ -1423,3 +1454,43 @@ def f(value: None) -> None: x = force_forward_ref() [builtins fixtures/exception.pyi] + +[case testSetitemNoReturn] +# flags: --warn-unreachable +from typing import NoReturn +class Foo: + def __setitem__(self, key: str, value: str) -> NoReturn: + raise Exception +Foo()['a'] = 'a' +x = 0 # E: Statement is unreachable +[builtins fixtures/exception.pyi] + +[case TestNoImplicNoReturnFromError] +# flags: --warn-unreachable +from typing import TypeVar + +T = TypeVar("T") +class Foo: + def __setitem__(self, key: str, value: str) -> T: # E: A function returning TypeVar should receive at least one argument containing the same TypeVar + raise Exception + +def f() -> None: + Foo()['a'] = 'a' + x = 0 # This should not be reported as unreachable +[builtins fixtures/exception.pyi] + +[case testIntentionallyEmptyGeneratorFunction] +# flags: --warn-unreachable +from typing import Generator + +def f() -> Generator[None, None, None]: + return + yield + +[case testIntentionallyEmptyGeneratorFunction_None] +# flags: --warn-unreachable +from typing import Generator + +def f() -> Generator[None, None, None]: + return None + yield None diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index d598fe13b7e9..2495a883aa71 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -8,8 +8,8 @@ [case testVarArgsWithinFunction] from typing import Tuple def f( *b: 'B') -> None: - ab = None # type: Tuple[B, ...] - ac = None # type: Tuple[C, ...] + ab: Tuple[B, ...] + ac: Tuple[C, ...] if int(): b = ac # E: Incompatible types in assignment (expression has type "Tuple[C, ...]", variable has type "Tuple[B, ...]") ac = b # E: Incompatible types in assignment (expression has type "Tuple[B, ...]", variable has type "Tuple[C, ...]") @@ -46,14 +46,14 @@ class A: pass class B(A): pass class C: pass -a = None # type: A -b = None # type: B -c = None # type: C +a: A +b: B +c: C f(c) # E: Argument 1 to "f" has incompatible type "C"; expected "A" f(a, b, c) # E: Argument 3 to "f" has incompatible type "C"; expected "A" -f(g()) # E: "g" does not return a value -f(a, g()) # E: "g" does not return a value +f(g()) # E: "g" does not return a value (it only ever returns None) +f(a, g()) # E: "g" does not return a value (it only ever returns None) f() f(a) f(b) @@ -67,9 +67,9 @@ class A: pass class B(A): pass class C: pass -a = None # type: A -b = None # type: B -c = None # type: C +a: A +b: B +c: C f(a) # E: Argument 1 to "f" has incompatible type "A"; expected "C" f(c, c) # E: Argument 2 to "f" has incompatible type "C"; expected "A" @@ -88,9 +88,9 @@ class A: pass class B(A): pass class C: pass -a = None # type: A -b = None # type: B -c = None # type: C +a: A +b: B +c: C f(a) # E: Argument 1 to "f" has incompatible type "A"; expected "Optional[C]" f(c, c) # E: Argument 2 to "f" has incompatible type "C"; expected "A" @@ -103,8 +103,8 @@ f(c, b, b, a, b) [case testCallVarargsFunctionWithIterable] from typing import Iterable -it1 = None # type: Iterable[int] -it2 = None # type: Iterable[str] +it1: Iterable[int] +it2: Iterable[str] def f(*x: int) -> None: pass f(*it1) f(*it2) # E: Argument 1 to "f" has incompatible type "*Iterable[str]"; expected "int" @@ -127,7 +127,7 @@ reveal_type(f(*x, *y)) # N: Revealed type is "Tuple[builtins.int, builtins.str, [case testCallVarargsFunctionWithIterableAndPositional] from typing import Iterable -it1 = None # type: Iterable[int] +it1: Iterable[int] def f(*x: int) -> None: pass f(*it1, 1, 2) f(*it1, 1, *it1, 2) @@ -161,10 +161,10 @@ class A: pass class B(A): pass class C: pass -a = None # type: A -b = None # type: B -c = None # type: C -o = None # type: object +a: A +b: B +c: C +o: object if int(): a = f(o) # E: Incompatible types in assignment (expression has type "object", variable has type "A") @@ -188,6 +188,7 @@ if int(): [builtins fixtures/list.pyi] [case testTypeInferenceWithCalleeVarArgsAndDefaultArgs] +# flags: --no-strict-optional from typing import TypeVar T = TypeVar('T') a = None # type: A @@ -229,10 +230,10 @@ def f(a: 'A', b: 'B') -> None: class A: pass class B: pass -aa = None # type: List[A] -ab = None # type: List[B] -a = None # type: A -b = None # type: B +aa: List[A] +ab: List[B] +a: A +b: B f(*aa) # E: Argument 1 to "f" has incompatible type "*List[A]"; expected "B" f(a, *ab) # Ok @@ -248,10 +249,10 @@ class B: pass class C: pass class CC(C): pass -a = None # type: A -b = None # type: B -c = None # type: C -cc = None # type: CC +a: A +b: B +c: C +cc: CC f(*(a, b, b)) # E: Argument 1 to "f" has incompatible type "*Tuple[A, B, B]"; expected "C" f(*(b, b, c)) # E: Argument 1 to "f" has incompatible type "*Tuple[B, B, C]"; expected "A" @@ -267,7 +268,7 @@ f(a, *(b, cc)) [builtins fixtures/tuple.pyi] [case testInvalidVarArg] - +# flags: --no-strict-optional def f(a: 'A') -> None: pass @@ -293,7 +294,10 @@ def g(a: 'A', *b: 'A') -> None: pass class A: pass class B: pass -aa, ab, a, b = None, None, None, None # type: (List[A], List[B], A, B) +aa: List[A] +ab: List[B] +a: A +b: B f(*aa) # E: Argument 1 to "f" has incompatible type "*List[A]"; expected "B" f(a, *aa) # E: Argument 2 to "f" has incompatible type "*List[A]"; expected "B" f(b, *ab) # E: Argument 1 to "f" has incompatible type "B"; expected "A" @@ -315,7 +319,10 @@ class B: pass class C: pass class CC(C): pass -a, b, c, cc = None, None, None, None # type: (A, B, C, CC) +a: A +b: B +c: C +cc: CC f(*(b, b, b)) # E: Argument 1 to "f" has incompatible type "*Tuple[B, B, B]"; expected "A" f(*(a, a, b)) # E: Argument 1 to "f" has incompatible type "*Tuple[A, A, B]"; expected "B" @@ -342,7 +349,8 @@ def f(a: 'A') -> None: pass def g(a: 'A', *b: 'A') -> None: pass class A: pass -d, a = None, None # type: (Any, A) +d: Any +a: A f(a, a, *d) # E: Too many arguments for "f" f(a, *d) # Ok f(*d) # Ok @@ -351,6 +359,7 @@ g(*d) g(a, *d) g(a, a, *d) [builtins fixtures/list.pyi] + [case testListVarArgsAndSubtyping] from typing import List def f( *a: 'A') -> None: @@ -362,8 +371,8 @@ def g( *a: 'B') -> None: class A: pass class B(A): pass -aa = None # type: List[A] -ab = None # type: List[B] +aa: List[A] +ab: List[B] g(*aa) # E: Argument 1 to "g" has incompatible type "*List[A]"; expected "B" f(*aa) @@ -449,6 +458,7 @@ foo(*()) [case testIntersectionTypesAndVarArgs] +# flags: --no-strict-optional from foo import * [file foo.pyi] from typing import overload @@ -518,7 +528,9 @@ def f(a: S, *b: T) -> Tuple[S, T]: class A: pass class B: pass -a, b, aa = None, None, None # type: (A, B, List[A]) +a: A +b: B +aa: List[A] if int(): a, b = f(*aa) # E: Argument 1 to "f" has incompatible type "*List[A]"; expected "B" @@ -553,7 +565,8 @@ def f(a: S, b: T) -> Tuple[S, T]: pass class A: pass class B: pass -a, b = None, None # type: (A, B) +a: A +b: B if int(): a, a = f(*(a, b)) # E: Argument 1 to "f" has incompatible type "*Tuple[A, B]"; expected "A" @@ -575,10 +588,11 @@ if int(): from typing import List, TypeVar, Generic, Tuple T = TypeVar('T') S = TypeVar('S') -a, b = None, None # type: (A, B) -ao = None # type: List[object] -aa = None # type: List[A] -ab = None # type: List[B] +a: A +b: B +ao: List[object] +aa: List[A] +ab: List[B] class G(Generic[T]): def f(self, *a: S) -> Tuple[List[S], List[T]]: @@ -588,35 +602,18 @@ class A: pass class B: pass if int(): - a, aa = G().f(*[a]) \ - # E: Incompatible types in assignment (expression has type "List[A]", variable has type "A") \ - # E: Incompatible types in assignment (expression has type "List[]", variable has type "List[A]") \ - # N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \ - # N: Consider using "Sequence" instead, which is covariant - + a, aa = G().f(*[a]) # E: Incompatible types in assignment (expression has type "List[A]", variable has type "A") if int(): - aa, a = G().f(*[a]) # E: Incompatible types in assignment (expression has type "List[]", variable has type "A") + aa, a = G().f(*[a]) # E: Incompatible types in assignment (expression has type "List[Never]", variable has type "A") if int(): - ab, aa = G().f(*[a]) \ - # E: Incompatible types in assignment (expression has type "List[]", variable has type "List[A]") \ - # N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \ - # N: Consider using "Sequence" instead, which is covariant \ - # E: Argument 1 to "f" of "G" has incompatible type "*List[A]"; expected "B" - + ab, aa = G().f(*[a]) # E: Argument 1 to "f" of "G" has incompatible type "*List[A]"; expected "B" if int(): - ao, ao = G().f(*[a]) \ - # E: Incompatible types in assignment (expression has type "List[]", variable has type "List[object]") \ - # N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \ - # N: Consider using "Sequence" instead, which is covariant + ao, ao = G().f(*[a]) if int(): - aa, aa = G().f(*[a]) \ - # E: Incompatible types in assignment (expression has type "List[]", variable has type "List[A]") \ - # N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \ - # N: Consider using "Sequence" instead, which is covariant + aa, aa = G().f(*[a]) [builtins fixtures/list.pyi] [case testCallerTupleVarArgsAndGenericCalleeVarArg] -# flags: --strict-optional from typing import TypeVar T = TypeVar('T') @@ -649,7 +646,7 @@ f(1, '') # E: Argument 2 to "f" has incompatible type "str"; expected "int" [case testVarArgsFunctionSubtyping] from typing import Callable -x = None # type: Callable[[int], None] +x: Callable[[int], None] def f(*x: int) -> None: pass def g(*x: str) -> None: pass x = f @@ -762,9 +759,9 @@ class Person(TypedDict): name: str age: int -def foo(x: Unpack[Person]) -> None: # E: TypedDict('__main__.Person', {'name': builtins.str, 'age': builtins.int}) cannot be unpacked (must be tuple or TypeVarTuple) +def foo(x: Unpack[Person]) -> None: # E: Unpack is only valid in a variadic position ... -def bar(x: int, *args: Unpack[Person]) -> None: # E: TypedDict('__main__.Person', {'name': builtins.str, 'age': builtins.int}) cannot be unpacked (must be tuple or TypeVarTuple) +def bar(x: int, *args: Unpack[Person]) -> None: # E: "Person" cannot be unpacked (must be tuple or TypeVarTuple) ... def baz(**kwargs: Unpack[Person]) -> None: # OK ... @@ -1066,3 +1063,18 @@ class C: class D: def __init__(self, **kwds: Unpack[int, str]) -> None: ... # E: Unpack[...] requires exactly one type argument [builtins fixtures/dict.pyi] + +[case testUnpackInCallableType] +from typing import Callable +from typing_extensions import Unpack, TypedDict + +class TD(TypedDict): + key: str + value: str + +foo: Callable[[Unpack[TD]], None] +foo(key="yes", value=42) # E: Argument "value" has incompatible type "int"; expected "str" +foo(key="yes", value="ok") + +bad: Callable[[*TD], None] # E: "TD" cannot be unpacked (must be tuple or TypeVarTuple) +[builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-warnings.test b/test-data/unit/check-warnings.test index 10c7968be475..90f40777d6b7 100644 --- a/test-data/unit/check-warnings.test +++ b/test-data/unit/check-warnings.test @@ -207,7 +207,7 @@ def f() -> Any: return g() [out] [case testOKReturnAnyIfProperSubtype] -# flags: --warn-return-any --strict-optional +# flags: --warn-return-any from typing import Any, Optional class Test(object): diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index c2e98cdb74f9..2262b7e7280c 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -296,7 +296,7 @@ mypy.ini: [mypy]: ignore_missing_imports: Not a boolean: nah [file mypy.ini] \[mypy] \[mypy-*] -python_version = 3.4 +python_version = 3.11 [out] mypy.ini: [mypy-*]: Per-module sections should only specify per-module flags (python_version) == Return code: 0 @@ -592,7 +592,7 @@ main.py:1: error: Cannot find implementation or library stub for module named "a \[tool.mypy] python_version = 3.10 [out] -pyproject.toml: [mypy]: python_version: Python 3.1 is not supported (must be 3.4 or higher). You may need to put quotes around your Python version +pyproject.toml: [mypy]: python_version: Python 3.1 is not supported (must be 3.8 or higher). You may need to put quotes around your Python version == Return code: 0 [case testPythonVersionTooOld10] @@ -604,13 +604,13 @@ python_version = 1.0 mypy.ini: [mypy]: python_version: Python major version '1' out of range (must be 3) == Return code: 0 -[case testPythonVersionTooOld33] +[case testPythonVersionTooOld37] # cmd: mypy -c pass [file mypy.ini] \[mypy] -python_version = 3.3 +python_version = 3.7 [out] -mypy.ini: [mypy]: python_version: Python 3.3 is not supported (must be 3.4 or higher) +mypy.ini: [mypy]: python_version: Python 3.7 is not supported (must be 3.8 or higher) == Return code: 0 [case testPythonVersionTooNew40] @@ -633,18 +633,18 @@ usage: mypy [-h] [-v] [-V] [more options; see below] mypy: error: Mypy no longer supports checking Python 2 code. Consider pinning to mypy<0.980 if you need to check Python 2 code. == Return code: 2 -[case testPythonVersionAccepted34] +[case testPythonVersionAccepted38] # cmd: mypy -c pass [file mypy.ini] \[mypy] -python_version = 3.4 +python_version = 3.8 [out] -[case testPythonVersionAccepted36] +[case testPythonVersionAccepted311] # cmd: mypy -c pass [file mypy.ini] \[mypy] -python_version = 3.6 +python_version = 3.11 [out] -- This should be a dumping ground for tests of plugins that are sensitive to @@ -676,11 +676,11 @@ int_pow.py:10: note: Revealed type is "builtins.int" int_pow.py:11: note: Revealed type is "Any" == Return code: 0 -[case testDisallowAnyGenericsBuiltinCollections] +[case testDisallowAnyGenericsBuiltinCollectionsPre39] # cmd: mypy m.py [file mypy.ini] \[mypy] -python_version=3.6 +python_version = 3.8 \[mypy-m] disallow_any_generics = True @@ -1122,19 +1122,6 @@ class AnotherCustomClassDefinedBelow: def another_even_more_interesting_method(self, arg: Union[int, str, float]) -> None: self.very_important_attribute_with_long_name: OneCustomClassName = OneCustomClassName().some_interesting_method(arg) [out] -some_file.py:3: error: Unsupported operand types for + ("int" and "str") - 42 + 'no way' - ^ -some_file.py:11: error: Incompatible types in assignment (expression has type -"AnotherCustomClassDefinedBelow", variable has type "OneCustomClassName") - ...t_attribute_with_long_name: OneCustomClassName = OneCustomClassName().... - ^ -some_file.py:11: error: Argument 1 to "some_interesting_method" of -"OneCustomClassName" has incompatible type "Union[int, str, float]"; expected -"AnotherCustomClassDefinedBelow" - ...OneCustomClassName = OneCustomClassName().some_interesting_method(arg) - ^ -[out version>=3.8] some_file.py:3: error: Unsupported operand types for + ("int" and "str") 42 + 'no way' ^~~~~~~~ @@ -1165,14 +1152,6 @@ def test_tabs() -> str: def test_between(x: str) -> None: ... test_between(1 + 1) [out] -tabs.py:2: error: Incompatible return value type (got "None", expected "str") - return None - ^ -tabs.py:4: error: Argument 1 to "test_between" has incompatible type "int"; -expected "str" - test_between(1 + 1) - ^ -[out version>=3.8] tabs.py:2: error: Incompatible return value type (got "None", expected "str") return None ^~~~ @@ -1442,14 +1421,6 @@ b \d+ b\.c \d+ .* -[case testCmdlineEnableIncompleteFeatures] -# cmd: mypy --enable-incomplete-features a.py -[file a.py] -pass -[out] -Warning: --enable-incomplete-features is deprecated, use --enable-incomplete-feature=FEATURE instead -== Return code: 0 - [case testShadowTypingModuleEarlyLoad] # cmd: mypy dir [file dir/__init__.py] @@ -1484,17 +1455,21 @@ note: A user-defined top-level module with name "typing" is not supported [file dir/stdlib/types.pyi] [file dir/stdlib/typing.pyi] [file dir/stdlib/typing_extensions.pyi] +[file dir/stdlib/_typeshed.pyi] +[file dir/stdlib/_collections_abc.pyi] +[file dir/stdlib/collections/abc.pyi] +[file dir/stdlib/collections/__init__.pyi] [file dir/stdlib/VERSIONS] [out] Failed to find builtin module mypy_extensions, perhaps typeshed is broken? == Return code: 2 -[case testRecursiveAliasesFlagDeprecated] -# cmd: mypy --enable-recursive-aliases a.py +[case testNewTypeInferenceFlagDeprecated] +# cmd: mypy --new-type-inference a.py [file a.py] pass [out] -Warning: --enable-recursive-aliases is deprecated; recursive types are enabled by default +Warning: --new-type-inference flag is deprecated; new type inference algorithm is already enabled by default == Return code: 0 [case testNotesOnlyResultInExitSuccess] @@ -1523,6 +1498,10 @@ class dict: pass [file dir/stdlib/typing.pyi] [file dir/stdlib/mypy_extensions.pyi] [file dir/stdlib/typing_extensions.pyi] +[file dir/stdlib/_typeshed.pyi] +[file dir/stdlib/_collections_abc.pyi] +[file dir/stdlib/collections/abc.pyi] +[file dir/stdlib/collections/__init__.pyi] [file dir/stdlib/foo.pyi] 1() # Errors are reported if the file was explicitly passed on the command line [file dir/stdlib/VERSIONS] @@ -1598,3 +1577,13 @@ disable_error_code = always_true = MY_VAR, [out] + +[case testTypeVarTupleUnpackEnabled] +# cmd: mypy --enable-incomplete-feature=TypeVarTuple --enable-incomplete-feature=Unpack a.py +[file a.py] +from typing_extensions import TypeVarTuple +Ts = TypeVarTuple("Ts") +[out] +Warning: TypeVarTuple is already enabled by default +Warning: Unpack is already enabled by default +== Return code: 0 diff --git a/test-data/unit/daemon.test b/test-data/unit/daemon.test index 7586c8763d33..ca2c969d2f5e 100644 --- a/test-data/unit/daemon.test +++ b/test-data/unit/daemon.test @@ -28,6 +28,33 @@ Daemon stopped [file foo.py] def f(): pass +[case testDaemonRunIgnoreMissingImports] +$ dmypy run -- foo.py --follow-imports=error --ignore-missing-imports +Daemon started +Success: no issues found in 1 source file +$ dmypy stop +Daemon stopped +[file foo.py] +def f(): pass + +[case testDaemonRunErrorCodes] +$ dmypy run -- foo.py --follow-imports=error --disable-error-code=type-abstract +Daemon started +Success: no issues found in 1 source file +$ dmypy stop +Daemon stopped +[file foo.py] +def f(): pass + +[case testDaemonRunCombinedOptions] +$ dmypy run -- foo.py --follow-imports=error --ignore-missing-imports --disable-error-code=type-abstract +Daemon started +Success: no issues found in 1 source file +$ dmypy stop +Daemon stopped +[file foo.py] +def f(): pass + [case testDaemonIgnoreConfigFiles] $ dmypy start -- --follow-imports=error Daemon started @@ -35,6 +62,28 @@ Daemon started \[mypy] files = ./foo.py +[case testDaemonRunMultipleStrict] +$ dmypy run -- foo.py --strict --follow-imports=error +Daemon started +foo.py:1: error: Function is missing a return type annotation +foo.py:1: note: Use "-> None" if function does not return a value +Found 1 error in 1 file (checked 1 source file) +== Return code: 1 +$ dmypy run -- bar.py --strict --follow-imports=error +bar.py:1: error: Function is missing a return type annotation +bar.py:1: note: Use "-> None" if function does not return a value +Found 1 error in 1 file (checked 1 source file) +== Return code: 1 +$ dmypy run -- foo.py --strict --follow-imports=error +foo.py:1: error: Function is missing a return type annotation +foo.py:1: note: Use "-> None" if function does not return a value +Found 1 error in 1 file (checked 1 source file) +== Return code: 1 +[file foo.py] +def f(): pass +[file bar.py] +def f(): pass + [case testDaemonRunRestart] $ dmypy run -- foo.py --follow-imports=error Daemon started @@ -110,18 +159,18 @@ def plugin(version): return Dummy [case testDaemonRunRestartGlobs] -- Ensure dmypy is not restarted if the configuration doesn't change and it contains globs -- Note: Backslash path separator in output is replaced with forward slash so the same test succeeds on Windows as well -$ dmypy run -- foo --follow-imports=error --python-version=3.6 +$ dmypy run -- foo --follow-imports=error Daemon started foo/lol.py:1: error: Name "fail" is not defined Found 1 error in 1 file (checked 3 source files) == Return code: 1 -$ dmypy run -- foo --follow-imports=error --python-version=3.6 +$ dmypy run -- foo --follow-imports=error foo/lol.py:1: error: Name "fail" is not defined Found 1 error in 1 file (checked 3 source files) == Return code: 1 $ {python} -c "print('[mypy]')" >mypy.ini $ {python} -c "print('ignore_errors=True')" >>mypy.ini -$ dmypy run -- foo --follow-imports=error --python-version=3.6 +$ dmypy run -- foo --follow-imports=error Restarting: configuration changed Daemon stopped Daemon started @@ -215,7 +264,7 @@ $ dmypy stop Daemon stopped [case testDaemonWarningSuccessExitCode-posix] -$ dmypy run -- foo.py --follow-imports=error +$ dmypy run -- foo.py --follow-imports=error --python-version=3.11 Daemon started foo.py:2: note: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs Success: no issues found in 1 source file @@ -233,13 +282,13 @@ def foo(): [case testDaemonQuickstart] $ {python} -c "print('x=1')" >foo.py $ {python} -c "print('x=1')" >bar.py -$ mypy --local-partial-types --cache-fine-grained --follow-imports=error --no-sqlite-cache --python-version=3.6 -- foo.py bar.py +$ mypy --local-partial-types --cache-fine-grained --follow-imports=error --no-sqlite-cache --python-version=3.11 -- foo.py bar.py Success: no issues found in 2 source files -$ {python} -c "import shutil; shutil.copy('.mypy_cache/3.6/bar.meta.json', 'asdf.json')" +$ {python} -c "import shutil; shutil.copy('.mypy_cache/3.11/bar.meta.json', 'asdf.json')" -- update bar's timestamp but don't change the file $ {python} -c "import time;time.sleep(1)" $ {python} -c "print('x=1')" >bar.py -$ dmypy run -- foo.py bar.py --follow-imports=error --use-fine-grained-cache --no-sqlite-cache --python-version=3.6 +$ dmypy run -- foo.py bar.py --follow-imports=error --use-fine-grained-cache --no-sqlite-cache --python-version=3.11 Daemon started Success: no issues found in 2 source files $ dmypy status --fswatcher-dump-file test.json @@ -247,11 +296,11 @@ Daemon is up and running $ dmypy stop Daemon stopped -- copy the original bar cache file back so that the mtime mismatches -$ {python} -c "import shutil; shutil.copy('asdf.json', '.mypy_cache/3.6/bar.meta.json')" +$ {python} -c "import shutil; shutil.copy('asdf.json', '.mypy_cache/3.11/bar.meta.json')" -- sleep guarantees timestamp changes $ {python} -c "import time;time.sleep(1)" $ {python} -c "print('lol')" >foo.py -$ dmypy run --log-file=log -- foo.py bar.py --follow-imports=error --use-fine-grained-cache --no-sqlite-cache --python-version=3.6 --quickstart-file test.json +$ dmypy run --log-file=log -- foo.py bar.py --follow-imports=error --use-fine-grained-cache --no-sqlite-cache --python-version=3.11 --quickstart-file test.json Daemon started foo.py:1: error: Name "lol" is not defined Found 1 error in 1 file (checked 2 source files) @@ -260,7 +309,7 @@ Found 1 error in 1 file (checked 2 source files) $ {python} -c "import sys; sys.stdout.write(open('log').read())" -- make sure the meta file didn't get updated. we use this as an imperfect proxy for -- whether the source file got rehashed, which we don't want it to have been. -$ {python} -c "x = open('.mypy_cache/3.6/bar.meta.json').read(); y = open('asdf.json').read(); assert x == y" +$ {python} -c "x = open('.mypy_cache/3.11/bar.meta.json').read(); y = open('asdf.json').read(); assert x == y" [case testDaemonSuggest] $ dmypy start --log-file log.txt -- --follow-imports=error --no-error-summary @@ -311,8 +360,35 @@ def bar() -> None: x = foo('abc') # type: str foo(arg='xyz') -[case testDaemonGetType_python38] -$ dmypy start --log-file log.txt -- --follow-imports=error --no-error-summary +[case testDaemonInspectCheck] +$ dmypy start +Daemon started +$ dmypy check foo.py +Success: no issues found in 1 source file +$ dmypy check foo.py --export-types +Success: no issues found in 1 source file +$ dmypy inspect foo.py:1:1 +"int" +[file foo.py] +x = 1 + +[case testDaemonInspectRun] +$ dmypy run test1.py +Daemon started +Success: no issues found in 1 source file +$ dmypy run test2.py +Success: no issues found in 1 source file +$ dmypy run test1.py --export-types +Success: no issues found in 1 source file +$ dmypy inspect test1.py:1:1 +"int" +[file test1.py] +a: int +[file test2.py] +a: str + +[case testDaemonGetType] +$ dmypy start --log-file log.txt -- --follow-imports=error --no-error-summary --python-version 3.8 Daemon started $ dmypy inspect foo:1:2:3:4 Command "inspect" is only valid after a "check" command (that produces no parse errors) @@ -323,6 +399,9 @@ foo.py:3: error: Incompatible types in assignment (expression has type "str", va $ dmypy inspect foo:1 Format should be file:line:column[:end_line:end_column] == Return code: 2 +$ dmypy inspect foo:1:2:3 +Source file is not a Python file +== Return code: 2 $ dmypy inspect foo.py:1:2:a:b invalid literal for int() with base 10: 'a' == Return code: 2 @@ -330,7 +409,7 @@ $ dmypy inspect foo.pyc:1:1:2:2 Source file is not a Python file == Return code: 2 $ dmypy inspect bar/baz.py:1:1:2:2 -Unknown module: baz +Unknown module: bar/baz.py == Return code: 1 $ dmypy inspect foo.py:3:1:1:1 "end_line" must not be before "line" @@ -374,7 +453,7 @@ def unreachable(x: int) -> None: return x # line 17 -[case testDaemonGetTypeInexact_python38] +[case testDaemonGetTypeInexact] $ dmypy start --log-file log.txt -- --follow-imports=error --no-error-summary Daemon started $ dmypy check foo.py --export-types @@ -385,7 +464,7 @@ $ dmypy inspect foo.pyc:1:2 Source file is not a Python file == Return code: 2 $ dmypy inspect bar/baz.py:1:2 -Unknown module: baz +Unknown module: bar/baz.py == Return code: 1 $ dmypy inspect foo.py:7:5 --include-span 7:5:7:5 -> "int" @@ -429,7 +508,7 @@ def unreachable(x: int, y: int) -> None: return x and y # line 11 -[case testDaemonGetAttrs_python38] +[case testDaemonGetAttrs] $ dmypy start --log-file log.txt -- --follow-imports=error --no-error-summary Daemon started $ dmypy check foo.py bar.py --export-types @@ -471,7 +550,7 @@ class B: var: Union[A, B] var # line 10 -[case testDaemonGetDefinition_python38] +[case testDaemonGetDefinition] $ dmypy start --log-file log.txt -- --follow-imports=error --no-error-summary Daemon started $ dmypy check foo.py bar/baz.py bar/__init__.py --export-types @@ -522,3 +601,17 @@ class A: x: int class B: x: int + +[case testDaemonInspectSelectCorrectFile] +$ dmypy run test.py --export-types +Daemon started +Success: no issues found in 1 source file +$ dmypy inspect demo/test.py:1:1 +"int" +$ dmypy inspect test.py:1:1 +"str" +[file test.py] +b: str +from demo.test import a +[file demo/test.py] +a: int diff --git a/test-data/unit/deps-generics.test b/test-data/unit/deps-generics.test index c78f3fad90c0..6baa57266d2f 100644 --- a/test-data/unit/deps-generics.test +++ b/test-data/unit/deps-generics.test @@ -159,7 +159,7 @@ class D: pass T = TypeVar('T', A, B) S = TypeVar('S', C, D) -def f(x: T) -> S: +def f(x: T, y: S) -> S: pass [out] -> , , m, m.A, m.f diff --git a/test-data/unit/deps-types.test b/test-data/unit/deps-types.test index 1d7064cde0c7..def117fe04df 100644 --- a/test-data/unit/deps-types.test +++ b/test-data/unit/deps-types.test @@ -852,8 +852,6 @@ class I: pass -> a -> , a, mod.I -> a - -> sys - -> sys [case testAliasDepsClassInFunction] from mod import I diff --git a/test-data/unit/deps.test b/test-data/unit/deps.test index 28d51f1a4c30..d18b4aae963b 100644 --- a/test-data/unit/deps.test +++ b/test-data/unit/deps.test @@ -612,7 +612,6 @@ class A: -> , m.A.f, m.C [case testPartialNoneTypeAttributeCrash2] -# flags: --strict-optional class C: pass class A: @@ -1370,7 +1369,6 @@ def h() -> None: -> m.D, m.h [case testDataclassDepsOldVersion] -# flags: --python-version 3.7 from dataclasses import dataclass Z = int @@ -1388,11 +1386,13 @@ class B(A): -> , m -> -> , m.B.__init__ + -> , m, m.B.__mypy-replace -> -> -> -> m, m.A, m.B -> m + -> m -> m -> m.B -> m @@ -1419,11 +1419,13 @@ class B(A): -> -> , m.B.__init__ -> + -> , m, m.B.__mypy-replace -> -> -> -> m, m.A, m.B -> m + -> m -> m -> m.B -> m diff --git a/test-data/unit/diff.test b/test-data/unit/diff.test index 66adfaecd909..8fc74868123e 100644 --- a/test-data/unit/diff.test +++ b/test-data/unit/diff.test @@ -1497,3 +1497,36 @@ class C: def meth(self) -> int: return 0 [out] __main__.C.meth + +[case testGenericFunctionWithOptionalReturnType] +from typing import Type, TypeVar + +T = TypeVar("T") + +class C: + @classmethod + def get_by_team_and_id( + cls: Type[T], + raw_member_id: int, + include_removed: bool = False, + ) -> T: + pass + +[file next.py] +from typing import Type, TypeVar, Optional + +T = TypeVar("T") + +class C: + @classmethod + def get_by_team_and_id( + cls: Type[T], + raw_member_id: int, + include_removed: bool = False, + ) -> Optional[T]: + pass + +[builtins fixtures/classmethod.pyi] +[out] +__main__.C.get_by_team_and_id +__main__.Optional diff --git a/test-data/unit/envvars.test b/test-data/unit/envvars.test index 0d78590e57a5..8832f80cff3c 100644 --- a/test-data/unit/envvars.test +++ b/test-data/unit/envvars.test @@ -8,4 +8,3 @@ BAR = 0 # type: int [file subdir/mypy.ini] \[mypy] files=$MYPY_CONFIG_FILE_DIR/good.py - diff --git a/test-data/unit/fine-grained-attr.test b/test-data/unit/fine-grained-attr.test index 3fd40b774c7b..8606fea15849 100644 --- a/test-data/unit/fine-grained-attr.test +++ b/test-data/unit/fine-grained-attr.test @@ -1,23 +1,23 @@ [case updateMagicField] -from attr import Attribute +from attrs import Attribute import m def g() -> Attribute[int]: return m.A.__attrs_attrs__[0] [file m.py] -from attr import define +from attrs import define @define class A: a: int [file m.py.2] -from attr import define +from attrs import define @define class A: a: float -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [out] == main:5: error: Incompatible return value type (got "Attribute[float]", expected "Attribute[int]") @@ -26,13 +26,13 @@ main:5: error: Incompatible return value type (got "Attribute[float]", expected import m [file c.py] -from attr import define +from attrs import define @define class A: a: float b: int -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [file m.py] from c import A @@ -49,12 +49,12 @@ A.__attrs_attrs__.b [case magicAttributeConsistency2-only_when_cache] [file c.py] -import attr +import attrs -@attr.s +@attrs.define class Entry: - var: int = attr.ib() -[builtins fixtures/attr.pyi] + var: int +[builtins fixtures/plugin_attrs.pyi] [file m.py] from typing import Any, ClassVar, Protocol diff --git a/test-data/unit/fine-grained-blockers.test b/test-data/unit/fine-grained-blockers.test index a134fb1d4301..33dedd887114 100644 --- a/test-data/unit/fine-grained-blockers.test +++ b/test-data/unit/fine-grained-blockers.test @@ -48,16 +48,6 @@ a.py:1: error: invalid syntax [syntax] def f(x: int) -> ^ == -main:3: error: Missing positional argument "x" in call to "f" [call-arg] - a.f() - ^ -== -[out version>=3.8] -== -a.py:1: error: invalid syntax [syntax] - def f(x: int) -> - ^ -== main:3: error: Missing positional argument "x" in call to "f" [call-arg] a.f() ^~~~~ diff --git a/test-data/unit/fine-grained-cache-incremental.test b/test-data/unit/fine-grained-cache-incremental.test index 50f93dd35af3..00157333efd7 100644 --- a/test-data/unit/fine-grained-cache-incremental.test +++ b/test-data/unit/fine-grained-cache-incremental.test @@ -202,7 +202,7 @@ a.py:8: note: x: expected "int", got "str" [file b.py] -- This is a heinous hack, but we simulate having a invalid cache by clobbering -- the proto deps file with something with mtime mismatches. -[file ../.mypy_cache/3.7/@deps.meta.json.2] +[file ../.mypy_cache/3.8/@deps.meta.json.2] {"snapshot": {"__main__": "a7c958b001a45bd6a2a320f4e53c4c16", "a": "d41d8cd98f00b204e9800998ecf8427e", "b": "d41d8cd98f00b204e9800998ecf8427e", "builtins": "c532c89da517a4b779bcf7a964478d67"}, "deps_meta": {"@root": {"path": "@root.deps.json", "mtime": 0}, "__main__": {"path": "__main__.deps.json", "mtime": 0}, "a": {"path": "a.deps.json", "mtime": 0}, "b": {"path": "b.deps.json", "mtime": 0}, "builtins": {"path": "builtins.deps.json", "mtime": 0}}} [file b.py.2] @@ -234,8 +234,8 @@ x = 10 [file p/c.py] class C: pass -[delete ../.mypy_cache/3.7/b.meta.json.2] -[delete ../.mypy_cache/3.7/p/c.meta.json.2] +[delete ../.mypy_cache/3.8/b.meta.json.2] +[delete ../.mypy_cache/3.8/p/c.meta.json.2] [out] == diff --git a/test-data/unit/fine-grained-dataclass-transform.test b/test-data/unit/fine-grained-dataclass-transform.test new file mode 100644 index 000000000000..89628256fda5 --- /dev/null +++ b/test-data/unit/fine-grained-dataclass-transform.test @@ -0,0 +1,141 @@ +[case updateDataclassTransformParameterViaDecorator] +# flags: --python-version 3.11 +from m import my_dataclass + +@my_dataclass +class Foo: + x: int + +foo = Foo(1) +foo.x = 2 + +[file m.py] +from typing import dataclass_transform + +@dataclass_transform(frozen_default=False) +def my_dataclass(cls): return cls + +[file m.py.2] +from typing import dataclass_transform + +@dataclass_transform(frozen_default=True) +def my_dataclass(cls): return cls + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[out] +== +main:9: error: Property "x" defined in "Foo" is read-only + +[case updateDataclassTransformParameterViaParentClass] +# flags: --python-version 3.11 +from m import Dataclass + +class Foo(Dataclass): + x: int + +foo = Foo(1) +foo.x = 2 + +[file m.py] +from typing import dataclass_transform + +@dataclass_transform(frozen_default=False) +class Dataclass: ... + +[file m.py.2] +from typing import dataclass_transform + +@dataclass_transform(frozen_default=True) +class Dataclass: ... + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[out] +== +main:8: error: Property "x" defined in "Foo" is read-only + +[case updateBaseClassToUseDataclassTransform] +# flags: --python-version 3.11 +from m import A + +class B(A): + y: int + +B(x=1, y=2) + +[file m.py] +class Dataclass: ... + +class A(Dataclass): + x: int + +[file m.py.2] +from typing import dataclass_transform + +@dataclass_transform() +class Dataclass: ... + +class A(Dataclass): + x: int + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[out] +main:7: error: Unexpected keyword argument "x" for "B" +builtins.pyi:14: note: "B" defined here +main:7: error: Unexpected keyword argument "y" for "B" +builtins.pyi:14: note: "B" defined here +== + +[case frozenInheritanceViaDefault] +# flags: --python-version 3.11 +from foo import Foo + +foo = Foo(base=0, foo=1) + +[file transform.py] +from typing import dataclass_transform, Type + +@dataclass_transform(frozen_default=True) +def dataclass(cls: Type) -> Type: return cls + +[file base.py] +from transform import dataclass + +@dataclass +class Base: + base: int + +[file foo.py] +from base import Base +from transform import dataclass + +@dataclass +class Foo(Base): + foo: int + +[file foo.py.2] +from base import Base +from transform import dataclass + +@dataclass +class Foo(Base): + foo: int + bar: int = 0 + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +# If the frozen parameter is being maintained correctly, we *don't* expect to see issues; if it's +# broken in incremental mode, then we'll see an error about inheriting a non-frozen class from a +# frozen one. +# +# Ideally we'd also add a `foo.foo = 2` to confirm that frozen semantics are actually being +# enforced, but incremental tests currently can't start with an error, which makes it tricky to +# write such a test case. +[out] +== diff --git a/test-data/unit/fine-grained-dataclass.test b/test-data/unit/fine-grained-dataclass.test new file mode 100644 index 000000000000..036d858ddf69 --- /dev/null +++ b/test-data/unit/fine-grained-dataclass.test @@ -0,0 +1,25 @@ +[case testReplace] +[file model.py] +from dataclasses import dataclass + +@dataclass +class Model: + x: int = 0 +[file replace.py] +from dataclasses import replace +from model import Model + +m = Model() +replace(m, x=42) + +[file model.py.2] +from dataclasses import dataclass + +@dataclass +class Model: + x: str = 'hello' + +[builtins fixtures/dataclasses.pyi] +[out] +== +replace.py:5: error: Argument "x" to "replace" of "Model" has incompatible type "int"; expected "str" diff --git a/test-data/unit/fine-grained-inspect.test b/test-data/unit/fine-grained-inspect.test index 2c575ec365b1..f8ce35585c10 100644 --- a/test-data/unit/fine-grained-inspect.test +++ b/test-data/unit/fine-grained-inspect.test @@ -1,8 +1,8 @@ [case testInspectTypeBasic] -# inspect2: --include-kind foo.py:10:13 -# inspect2: --show=type --include-kind foo.py:10:13 -# inspect2: --include-span -vv foo.py:12:5 -# inspect2: --include-span --include-kind foo.py:12:5:12:9 +# inspect2: --include-kind tmp/foo.py:10:13 +# inspect2: --show=type --include-kind tmp/foo.py:10:13 +# inspect2: --include-span -vv tmp/foo.py:12:5 +# inspect2: --include-span --include-kind tmp/foo.py:12:5:12:9 import foo [file foo.py] from typing import TypeVar, Generic @@ -29,10 +29,10 @@ MemberExpr -> "T" CallExpr:12:5:12:9 -> "C[int]" [case testInspectAttrsBasic] -# inspect2: --show=attrs foo.py:6:1 -# inspect2: --show=attrs foo.py:7:1 -# inspect2: --show=attrs foo.py:10:1 -# inspect2: --show=attrs --include-object-attrs foo.py:10:1 +# inspect2: --show=attrs tmp/foo.py:6:1 +# inspect2: --show=attrs tmp/foo.py:7:1 +# inspect2: --show=attrs tmp/foo.py:10:1 +# inspect2: --show=attrs --include-object-attrs tmp/foo.py:10:1 import foo [file foo.py] from bar import Meta @@ -56,12 +56,12 @@ class Meta(type): {"function": ["__name__"], "object": ["__init__"]} [case testInspectDefBasic] -# inspect2: --show=definition foo.py:5:5 -# inspect2: --show=definition --include-kind foo.py:6:3 -# inspect2: --show=definition --include-span foo.py:7:5 -# inspect2: --show=definition foo.py:8:1:8:4 -# inspect2: --show=definition foo.py:8:6:8:8 -# inspect2: --show=definition foo.py:9:3 +# inspect2: --show=definition tmp/foo.py:5:5 +# inspect2: --show=definition --include-kind tmp/foo.py:6:3 +# inspect2: --show=definition --include-span tmp/foo.py:7:5 +# inspect2: --show=definition tmp/foo.py:8:1:8:4 +# inspect2: --show=definition tmp/foo.py:8:6:8:8 +# inspect2: --show=definition tmp/foo.py:9:3 import foo [file foo.py] from bar import var, test, A @@ -95,18 +95,18 @@ def foo(x: Union[int, str]) -> None: [builtins fixtures/classmethod.pyi] [out] == -bar.py:4:0:meth +tmp/bar.py:4:0:meth MemberExpr -> tmp/bar.py:2:5:x 7:1:7:5 -> tmp/bar.py:6:9:y -bar.py:9:1:test -bar.py:8:1:var -baz.py:3:2:foo +tmp/bar.py:9:1:test +tmp/bar.py:8:1:var +tmp/baz.py:3:2:foo [case testInspectFallbackAttributes] -# inspect2: --show=attrs --include-object-attrs foo.py:5:1 -# inspect2: --show=attrs foo.py:8:1 -# inspect2: --show=attrs --include-kind foo.py:10:1 -# inspect2: --show=attrs --include-kind --include-object-attrs foo.py:10:1 +# inspect2: --show=attrs --include-object-attrs tmp/foo.py:5:1 +# inspect2: --show=attrs tmp/foo.py:8:1 +# inspect2: --show=attrs --include-kind tmp/foo.py:10:1 +# inspect2: --show=attrs --include-kind --include-object-attrs tmp/foo.py:10:1 import foo [file foo.py] class B: ... @@ -128,7 +128,7 @@ NameExpr -> {} NameExpr -> {"object": ["__eq__", "__init__", "__ne__"]} [case testInspectTypeVarBoundAttrs] -# inspect2: --show=attrs foo.py:8:13 +# inspect2: --show=attrs tmp/foo.py:8:13 import foo [file foo.py] from typing import TypeVar @@ -144,10 +144,10 @@ def foo(arg: T) -> T: {"C": ["x"]} [case testInspectTypeVarValuesAttrs] -# inspect2: --show=attrs --force-reload foo.py:13:13 -# inspect2: --show=attrs --force-reload --union-attrs foo.py:13:13 -# inspect2: --show=attrs foo.py:16:5 -# inspect2: --show=attrs --union-attrs foo.py:16:5 +# inspect2: --show=attrs --force-reload tmp/foo.py:13:13 +# inspect2: --show=attrs --force-reload --union-attrs tmp/foo.py:13:13 +# inspect2: --show=attrs tmp/foo.py:16:5 +# inspect2: --show=attrs --union-attrs tmp/foo.py:16:5 import foo [file foo.py] from typing import TypeVar, Generic @@ -174,8 +174,8 @@ class C(Generic[T]): {"A": ["x", "z"], "B": ["y", "z"]} [case testInspectTypeVarBoundDef] -# inspect2: --show=definition foo.py:9:13 -# inspect2: --show=definition foo.py:8:9 +# inspect2: --show=definition tmp/foo.py:9:13 +# inspect2: --show=definition tmp/foo.py:8:9 import foo [file foo.py] from typing import TypeVar @@ -189,13 +189,13 @@ def foo(arg: T) -> T: return arg [out] == -foo.py:7:9:arg -foo.py:4:5:x +tmp/foo.py:7:9:arg +tmp/foo.py:4:5:x [case testInspectTypeVarValuesDef] -# inspect2: --show=definition --force-reload foo.py:13:9 -# inspect2: --show=definition --force-reload foo.py:14:13 -# inspect2: --show=definition foo.py:18:7 +# inspect2: --show=definition --force-reload tmp/foo.py:13:9 +# inspect2: --show=definition --force-reload tmp/foo.py:14:13 +# inspect2: --show=definition tmp/foo.py:18:7 import foo [file foo.py] from typing import TypeVar, Generic @@ -218,12 +218,12 @@ class C(Generic[T]): x.z [out] == -foo.py:5:5:z, tmp/foo.py:9:5:z -foo.py:12:9:arg -foo.py:5:5:z, tmp/foo.py:9:5:z +tmp/foo.py:5:5:z, tmp/foo.py:9:5:z +tmp/foo.py:12:9:arg +tmp/foo.py:5:5:z, tmp/foo.py:9:5:z [case testInspectModuleAttrs] -# inspect2: --show=attrs foo.py:2:1 +# inspect2: --show=attrs tmp/foo.py:2:1 import foo [file foo.py] from pack import bar @@ -239,7 +239,7 @@ class C: ... {"": ["C", "__annotations__", "__doc__", "__file__", "__name__", "__package__", "bar", "x"], "ModuleType": ["__file__", "__getattr__"]} [case testInspectModuleDef] -# inspect2: --show=definition --include-kind foo.py:2:1 +# inspect2: --show=definition --include-kind tmp/foo.py:2:1 import foo [file foo.py] from pack import bar @@ -255,7 +255,7 @@ NameExpr -> tmp/pack/bar.py:1:1:bar MemberExpr -> tmp/pack/bar.py:3:5:x [case testInspectFunctionArgDef] -# inspect2: --show=definition --include-span foo.py:4:13 +# inspect2: --show=definition --include-span tmp/foo.py:4:13 # TODO: for now all arguments have line/column set to function definition. import foo [file foo.py] diff --git a/test-data/unit/fine-grained-modules.test b/test-data/unit/fine-grained-modules.test index f76ced64341b..f28dbaa1113b 100644 --- a/test-data/unit/fine-grained-modules.test +++ b/test-data/unit/fine-grained-modules.test @@ -837,15 +837,13 @@ p.a.f(1) [file p/__init__.py] [file p/a.py] def f(x: str) -> None: pass -[delete p/__init__.py.2] -[delete p/a.py.2] -def f(x: str) -> None: pass +[delete p.2] [out] main:2: error: Argument 1 to "f" has incompatible type "int"; expected "str" == main:1: error: Cannot find implementation or library stub for module named "p.a" main:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports -main:2: error: "object" has no attribute "a" +main:1: error: Cannot find implementation or library stub for module named "p" [case testDeletePackage2] import p @@ -1279,12 +1277,12 @@ a.py:2: error: Too many arguments for "foo" [case testAddModuleAfterCache3-only_when_cache] # cmd: mypy main a.py -# cmd2: mypy main a.py b.py c.py d.py e.py f.py g.py h.py -# cmd3: mypy main a.py b.py c.py d.py e.py f.py g.py h.py +# cmd2: mypy main a.py b.py c.py d.py e.py f.py g.py h.py i.py j.py +# cmd3: mypy main a.py b.py c.py d.py e.py f.py g.py h.py i.py j.py # flags: --ignore-missing-imports --follow-imports=skip import a [file a.py] -import b, c, d, e, f, g, h +import b, c, d, e, f, g, h, i, j b.foo(10) [file b.py.2] def foo() -> None: pass @@ -1294,6 +1292,8 @@ def foo() -> None: pass [file f.py.2] [file g.py.2] [file h.py.2] +[file i.py.2] +[file j.py.2] -- No files should be stale or reprocessed in the first step since the large number -- of missing files will force build to give up on cache loading. @@ -1806,7 +1806,7 @@ import b [file b.py] [file c.py] x = 1 -[file b.py] +[file b.py.2] 1+'x' [file c.py.2] x = '2' diff --git a/test-data/unit/fine-grained-suggest.test b/test-data/unit/fine-grained-suggest.test index a28e3204b93f..02373091ad54 100644 --- a/test-data/unit/fine-grained-suggest.test +++ b/test-data/unit/fine-grained-suggest.test @@ -62,7 +62,6 @@ foo('3', '4') == [case testSuggestInferFunc1] -# flags: --strict-optional # suggest: foo.foo [file foo.py] def foo(arg, lol=None): @@ -85,7 +84,6 @@ def untyped(x) -> None: == [case testSuggestInferFunc2] -# flags: --strict-optional # suggest: foo.foo [file foo.py] def foo(arg): @@ -222,7 +220,6 @@ Foo('lol') == [case testSuggestInferMethod1] -# flags: --strict-optional # suggest: --no-any foo.Foo.foo [file foo.py] class Foo: @@ -248,7 +245,6 @@ def bar() -> None: == [case testSuggestInferMethod2] -# flags: --strict-optional # suggest: foo.Foo.foo [file foo.py] class Foo: @@ -275,7 +271,6 @@ def bar() -> None: == [case testSuggestInferMethod3] -# flags: --strict-optional # suggest2: foo.Foo.foo [file foo.py] class Foo: @@ -372,7 +367,6 @@ def has_nested(x): == [case testSuggestInferFunctionUnreachable] -# flags: --strict-optional # suggest: foo.foo [file foo.py] import sys @@ -390,7 +384,6 @@ foo('test') == [case testSuggestInferMethodStep2] -# flags: --strict-optional # suggest2: foo.Foo.foo [file foo.py] class Foo: @@ -417,7 +410,6 @@ def bar() -> None: (Union[str, int, None], Optional[int]) -> Union[int, str] [case testSuggestInferNestedMethod] -# flags: --strict-optional # suggest: foo.Foo.Bar.baz [file foo.py] class Foo: @@ -435,7 +427,6 @@ def bar() -> None: == [case testSuggestCallable] -# flags: --strict-optional # suggest: foo.foo # suggest: foo.bar # suggest: --flex-any=0.9 foo.bar @@ -483,7 +474,6 @@ No guesses that match criteria! == [case testSuggestNewSemanal] -# flags: --strict-optional # suggest: foo.Foo.foo # suggest: foo.foo [file foo.py] @@ -521,7 +511,6 @@ def baz() -> None: == [case testSuggestInferFuncDecorator1] -# flags: --strict-optional # suggest: foo.foo [file foo.py] from typing import TypeVar @@ -543,7 +532,6 @@ def bar() -> None: == [case testSuggestInferFuncDecorator2] -# flags: --strict-optional # suggest: foo.foo [file foo.py] from typing import TypeVar, Callable, Any @@ -565,7 +553,6 @@ def bar() -> None: == [case testSuggestInferFuncDecorator3] -# flags: --strict-optional # suggest: foo.foo [file foo.py] from typing import TypeVar, Callable, Any @@ -589,7 +576,6 @@ def bar() -> None: == [case testSuggestInferFuncDecorator4] -# flags: --strict-optional # suggest: foo.foo [file dec.py] from typing import TypeVar, Callable, Any @@ -616,7 +602,6 @@ def bar() -> None: == [case testSuggestFlexAny1] -# flags: --strict-optional # suggest: --flex-any=0.4 m.foo # suggest: --flex-any=0.7 m.foo # suggest: --flex-any=0.4 m.bar @@ -661,7 +646,6 @@ No guesses that match criteria! [case testSuggestFlexAny2] -# flags: --strict-optional # suggest: --flex-any=0.5 m.baz # suggest: --flex-any=0.0 m.baz # suggest: --flex-any=0.5 m.F.foo @@ -693,7 +677,6 @@ No guesses that match criteria! == [case testSuggestClassMethod] -# flags: --strict-optional # suggest: foo.F.bar # suggest: foo.F.baz # suggest: foo.F.eggs @@ -1090,7 +1073,7 @@ optional2(10) optional2('test') def optional3(x: Optional[List[Any]]): - assert not x + assert x return x[0] optional3(test) diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 58339828677d..9c379d8f60da 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -95,11 +95,6 @@ class A: def g(self, a: A) -> None: pass [out] == -main:5: error: Missing positional argument "a" in call to "g" of "A" [call-arg] - a.g() # E - ^ -[out version>=3.8] -== main:5: error: Missing positional argument "a" in call to "g" of "A" [call-arg] a.g() # E ^~~~~ @@ -196,7 +191,7 @@ main:3: error: "A" has no attribute "x" [case testVariableTypeBecomesInvalid] import m def f() -> None: - a = None # type: m.A + a: m.A [file m.py] class A: pass [file m.py.2] @@ -657,7 +652,6 @@ class M(type): a.py:4: error: Incompatible types in assignment (expression has type "str", variable has type "int") [case testDataclassUpdate1] -# flags: --python-version 3.7 [file a.py] from dataclasses import dataclass @@ -696,7 +690,6 @@ b.py:8: error: Argument 1 to "B" has incompatible type "int"; expected "str" [builtins fixtures/dataclasses.pyi] [case testDataclassUpdate2] -# flags: --python-version 3.7 [file c.py] Foo = int @@ -727,7 +720,6 @@ b.py:8: error: Argument 1 to "B" has incompatible type "int"; expected "str" [builtins fixtures/dataclasses.pyi] [case testDataclassUpdate3] -# flags: --python-version 3.7 from b import B B(1, 2) [file b.py] @@ -751,10 +743,9 @@ class A: [builtins fixtures/dataclasses.pyi] [out] == -main:3: error: Missing positional argument "b" in call to "B" +main:2: error: Missing positional argument "b" in call to "B" [case testDataclassUpdate4] -# flags: --python-version 3.7 from b import B B(1, 2) [file b.py] @@ -778,10 +769,9 @@ class A: [builtins fixtures/dataclasses.pyi] [out] == -main:3: error: Missing positional argument "b" in call to "B" +main:2: error: Missing positional argument "b" in call to "B" [case testDataclassUpdate5] -# flags: --python-version 3.7 from b import B B(1, 2) [file b.py] @@ -812,11 +802,10 @@ class A: [builtins fixtures/dataclasses.pyi] [out] == -main:3: error: Missing positional argument "b" in call to "B" +main:2: error: Missing positional argument "b" in call to "B" == [case testDataclassUpdate6] -# flags: --python-version 3.7 from b import B B(1, 2) < B(1, 2) [file b.py] @@ -839,10 +828,9 @@ class A: [builtins fixtures/dataclasses.pyi] [out] == -main:3: error: Unsupported left operand type for < ("B") +main:2: error: Unsupported left operand type for < ("B") [case testDataclassUpdate8] -# flags: --python-version 3.7 from c import C C(1, 2, 3) [file c.py] @@ -872,10 +860,9 @@ class A: [builtins fixtures/dataclasses.pyi] [out] == -main:3: error: Missing positional argument "c" in call to "C" +main:2: error: Missing positional argument "c" in call to "C" [case testDataclassUpdate9] -# flags: --python-version 3.7 from c import C C(1, 2, 3) [file c.py] @@ -912,7 +899,7 @@ class A: [builtins fixtures/dataclasses.pyi] [out] == -main:3: error: Missing positional argument "c" in call to "C" +main:2: error: Missing positional argument "c" in call to "C" == [case testAttrsUpdate1] @@ -989,7 +976,6 @@ import attr class A: a: int other: int -[builtins fixtures/list.pyi] [file a.py.3] import attr @@ -1048,7 +1034,7 @@ import attr @attr.s(kw_only=True) class A: a = attr.ib(15) # type: int -[builtins fixtures/attr.pyi] +[builtins fixtures/plugin_attrs.pyi] [out] == main:2: error: Too many positional arguments for "B" @@ -1730,15 +1716,15 @@ f = 1 main:1: error: Module "a" has no attribute "f" [case testDecoratedMethodRefresh] -from typing import Iterator, Callable, List +from typing import Iterator, Callable, List, Optional from a import f import a -def dec(f: Callable[['A'], Iterator[int]]) -> Callable[[int], int]: pass +def dec(f: Callable[['A'], Optional[Iterator[int]]]) -> Callable[[int], int]: pass class A: @dec - def f(self) -> Iterator[int]: + def f(self) -> Optional[Iterator[int]]: self.x = a.g() # type: int return None [builtins fixtures/list.pyi] @@ -2089,7 +2075,6 @@ a.py:5: error: "list" expects 1 type argument, but 2 given == [case testPreviousErrorInOverloadedFunction] -# flags: --strict-optional import a [file a.py] from typing import overload @@ -3500,7 +3485,6 @@ def foo() -> None: b.py:4: error: Incompatible types in assignment (expression has type "str", variable has type "int") [case testNamedTupleUpdateNonRecursiveToRecursiveFine] -# flags: --strict-optional import c [file a.py] from b import M @@ -3543,7 +3527,6 @@ c.py:5: error: Incompatible types in assignment (expression has type "Optional[N c.py:7: note: Revealed type is "Tuple[Union[Tuple[Union[..., None], builtins.int, fallback=b.M], None], builtins.int, fallback=a.N]" [case testTupleTypeUpdateNonRecursiveToRecursiveFine] -# flags: --strict-optional import c [file a.py] from b import M @@ -3576,7 +3559,6 @@ c.py:4: note: Revealed type is "Tuple[Union[Tuple[Union[..., None], builtins.int c.py:5: error: Incompatible types in assignment (expression has type "Optional[N]", variable has type "int") [case testTypeAliasUpdateNonRecursiveToRecursiveFine] -# flags: --strict-optional import c [file a.py] from b import M @@ -4042,7 +4024,7 @@ def f(x: a.A[str]): [builtins fixtures/dict.pyi] [out] == -b.py:2: error: Bad number of arguments for type alias, expected: 2, given: 1 +b.py:2: error: Bad number of arguments for type alias, expected 2, given 1 [case testAliasFineAdded] import b @@ -4632,6 +4614,7 @@ class User: == [case testNoStrictOptionalModule] +# flags: --no-strict-optional import a a.y = a.x [file a.py] @@ -4649,9 +4632,10 @@ y: int [out] == == -main:2: error: Incompatible types in assignment (expression has type "Optional[str]", variable has type "int") +main:3: error: Incompatible types in assignment (expression has type "Optional[str]", variable has type "int") [case testNoStrictOptionalFunction] +# flags: --no-strict-optional import a from typing import Optional def f() -> None: @@ -4672,9 +4656,10 @@ def g(x: str) -> None: [out] == == -main:5: error: Argument 1 to "g" has incompatible type "Optional[int]"; expected "str" +main:6: error: Argument 1 to "g" has incompatible type "Optional[int]"; expected "str" [case testNoStrictOptionalMethod] +# flags: --no-strict-optional import a from typing import Optional class C: @@ -4699,10 +4684,9 @@ class B: [out] == == -main:6: error: Argument 1 to "g" of "B" has incompatible type "Optional[int]"; expected "str" +main:7: error: Argument 1 to "g" of "B" has incompatible type "Optional[int]"; expected "str" [case testStrictOptionalModule] -# flags: --strict-optional import a a.y = a.x [file a.py] @@ -4715,10 +4699,9 @@ x: Optional[int] y: int [out] == -main:3: error: Incompatible types in assignment (expression has type "Optional[int]", variable has type "int") +main:2: error: Incompatible types in assignment (expression has type "Optional[int]", variable has type "int") [case testStrictOptionalFunction] -# flags: --strict-optional import a from typing import Optional def f() -> None: @@ -4734,10 +4717,9 @@ def g(x: int) -> None: pass [out] == -main:6: error: Argument 1 to "g" has incompatible type "Optional[int]"; expected "int" +main:5: error: Argument 1 to "g" has incompatible type "Optional[int]"; expected "int" [case testStrictOptionalMethod] -# flags: --strict-optional import a from typing import Optional class C: @@ -4756,7 +4738,7 @@ class B: pass [out] == -main:7: error: Argument 1 to "g" of "B" has incompatible type "Optional[int]"; expected "int" +main:6: error: Argument 1 to "g" of "B" has incompatible type "Optional[int]"; expected "int" [case testPerFileStrictOptionalModule] import a @@ -5282,10 +5264,11 @@ class I(metaclass=ABCMeta): @abstractmethod def f(self) -> None: pass [file b.py] +from typing import Optional from z import I class Foo(I): pass -def x() -> Foo: return None +def x() -> Optional[Foo]: return None [file z.py.2] from abc import abstractmethod, ABCMeta class I(metaclass=ABCMeta): @@ -5307,10 +5290,11 @@ class I(metaclass=ABCMeta): @abstractmethod def f(self) -> None: pass [file b.py] +from typing import Optional from a import I class Foo(I): pass -def x() -> Foo: return None +def x() -> Optional[Foo]: return None [file a.py.2] from abc import abstractmethod, ABCMeta class I(metaclass=ABCMeta): @@ -5550,7 +5534,7 @@ main:9: error: Function "a.T" is not valid as a type main:9: note: Perhaps you need "Callable[...]" or a callback protocol? main:12: error: Function "a.T" is not valid as a type main:12: note: Perhaps you need "Callable[...]" or a callback protocol? -main:12: error: Bad number of arguments for type alias, expected: 0, given: 1 +main:12: error: Bad number of arguments for type alias, expected 0, given 1 [case testChangeTypeVarToModule] @@ -5584,7 +5568,7 @@ main:9: error: Module "T" is not valid as a type main:9: note: Perhaps you meant to use a protocol matching the module structure? main:12: error: Module "T" is not valid as a type main:12: note: Perhaps you meant to use a protocol matching the module structure? -main:12: error: Bad number of arguments for type alias, expected: 0, given: 1 +main:12: error: Bad number of arguments for type alias, expected 0, given 1 [case testChangeClassToModule] @@ -5636,7 +5620,7 @@ T = int == main:5: error: Free type variable expected in Generic[...] main:9: error: "C" expects no type arguments, but 1 given -main:12: error: Bad number of arguments for type alias, expected: 0, given: 1 +main:12: error: Bad number of arguments for type alias, expected 0, given 1 [case testChangeTypeAliasToModule] @@ -7547,7 +7531,7 @@ def d() -> Dict[int, int]: pass [builtins fixtures/dict.pyi] [out] == -main:5: error: Argument 1 to "update" of "dict" has incompatible type "Dict[int, int]"; expected "Mapping[int, str]" +main:5: error: Unpacked dict entry 1 has incompatible type "Dict[int, int]"; expected "SupportsKeysAndGetItem[int, str]" [case testAwaitAndAsyncDef-only_when_nocache] from a import g @@ -7775,7 +7759,8 @@ from typing import List import b class A(b.B): def meth(self) -> None: - self.x, *self.y = None, None # type: str, List[str] + self.x: str + self.y: List[str] [file b.py] from typing import List class B: @@ -7787,7 +7772,7 @@ class B: [builtins fixtures/list.pyi] [out] == -main:5: error: Incompatible types in assignment (expression has type "List[str]", base class "B" defined the type as "List[int]") +main:6: error: Incompatible types in assignment (expression has type "List[str]", base class "B" defined the type as "List[int]") [case testLiskovFineVariableCleanDefInMethodNested-only_when_nocache] from b import B @@ -7953,7 +7938,7 @@ class Foo(a.I): == [case testImplicitOptionalRefresh1] -# flags: --strict-optional --implicit-optional +# flags: --implicit-optional from x import f def foo(x: int = None) -> None: f() @@ -8033,7 +8018,7 @@ A = NamedTuple('A', F) # type: ignore [builtins fixtures/list.pyi] [out] == -b.py:3: note: Revealed type is "Tuple[, fallback=a.A]" +b.py:3: note: Revealed type is "Tuple[(), fallback=a.A]" [case testImportOnTopOfAlias1] from a import A @@ -9115,27 +9100,27 @@ import a [file a.py] # mypy: no-warn-no-return -from typing import List -def foo() -> List: +from typing import List, Optional +def foo() -> Optional[List]: 20 [file a.py.2] # mypy: disallow-any-generics, no-warn-no-return -from typing import List -def foo() -> List: +from typing import List, Optional +def foo() -> Optional[List]: 20 [file a.py.3] # mypy: no-warn-no-return -from typing import List -def foo() -> List: +from typing import List, Optional +def foo() -> Optional[List]: 20 [file a.py.4] -from typing import List -def foo() -> List: +from typing import List, Optional +def foo() -> Optional[List]: 20 [out] == @@ -9667,7 +9652,8 @@ reveal_type(z) [out] c.py:2: note: Revealed type is "a." == -c.py:2: note: Revealed type is "a.A" +c.py:2: note: Revealed type is "Any" +b.py:2: error: Cannot determine type of "y" [case testIsInstanceAdHocIntersectionFineGrainedIncrementalUnreachaableToIntersection] import c @@ -9698,7 +9684,8 @@ from b import z reveal_type(z) [builtins fixtures/isinstance.pyi] [out] -c.py:2: note: Revealed type is "a.A" +b.py:2: error: Cannot determine type of "y" +c.py:2: note: Revealed type is "Any" == c.py:2: note: Revealed type is "a." @@ -9791,7 +9778,6 @@ class ExampleClass(Generic[T]): [out] == [case testStrictNoneAttribute] -# flags: --strict-optional from typing import Generic, TypeVar T = TypeVar('T', int, str) @@ -9805,7 +9791,6 @@ class ExampleClass(Generic[T]): == [case testDataclassCheckTypeVarBoundsInReprocess] -# flags: --python-version 3.7 from dataclasses import dataclass from typing import ClassVar, Protocol, Dict, TypeVar, Generic from m import x @@ -9873,7 +9858,7 @@ x = 0 # Arbitrary change to trigger reprocessing [builtins fixtures/dict.pyi] [out] == -a.py:5: note: Revealed type is "def (x: builtins.int) -> builtins.str" +a.py:5: note: Revealed type is "def (x: builtins.int) -> Union[builtins.str, None]" [case testTypeVarTupleCached] import a @@ -9914,6 +9899,128 @@ x = 0 # Arbitrary change to trigger reprocessing == a.py:3: note: Revealed type is "Tuple[Literal[1]?, Literal['x']?]" +[case testVariadicClassFineUpdateRegularToVariadic] +from typing import Any +from lib import C + +x: C[int, str] + +[file lib.py] +from typing import Generic, TypeVar + +T = TypeVar("T") +S = TypeVar("S") +class C(Generic[T, S]): ... + +[file lib.py.2] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Unpack[Ts]]): ... +[builtins fixtures/tuple.pyi] +[out] +== + +[case testVariadicClassFineUpdateVariadicToRegular] +from typing import Any +from lib import C + +x: C[int, str, int] + +[file lib.py] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Unpack[Ts]]): ... +[file lib.py.2] +from typing import Generic, TypeVar + +T = TypeVar("T") +S = TypeVar("S") +class C(Generic[T, S]): ... +[builtins fixtures/tuple.pyi] +[out] +== +main:4: error: "C" expects 2 type arguments, but 3 given + +-- Order of error messages is different, so we repeat the test twice. +[case testVariadicClassFineUpdateValidToInvalidCached-only_when_cache] +from typing import Any +from lib import C + +x: C[int, str] + +[file lib.py] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Unpack[Ts]]): ... + +[file lib.py.2] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Ts]): ... +[builtins fixtures/tuple.pyi] +[out] +== +main:4: error: "C" expects no type arguments, but 2 given +lib.py:5: error: Free type variable expected in Generic[...] + +[case testVariadicClassFineUpdateValidToInvalid-only_when_nocache] +from typing import Any +from lib import C + +x: C[int, str] + +[file lib.py] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Unpack[Ts]]): ... + +[file lib.py.2] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Ts]): ... +[builtins fixtures/tuple.pyi] +[out] +== +lib.py:5: error: Free type variable expected in Generic[...] +main:4: error: "C" expects no type arguments, but 2 given + +[case testVariadicClassFineUpdateInvalidToValid] +from typing import Any +from lib import C + +x: C[int, str] + +[file lib.py] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Ts]): ... + +[file lib.py.2] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Unpack[Ts]]): ... +[builtins fixtures/tuple.pyi] +[out] +lib.py:5: error: Free type variable expected in Generic[...] +main:4: error: "C" expects no type arguments, but 2 given +== + [case testUnpackKwargsUpdateFine] import m [file shared.py] @@ -10044,7 +10151,6 @@ class C(B): ... main.py:4: note: Revealed type is "def () -> builtins.str" [case testAbstractBodyTurnsEmpty] -# flags: --strict-optional from b import Base class Sub(Base): @@ -10064,10 +10170,9 @@ class Base: def meth(self) -> int: ... [out] == -main:6: error: Call to abstract method "meth" of "Base" with trivial body via super() is unsafe +main:5: error: Call to abstract method "meth" of "Base" with trivial body via super() is unsafe [case testAbstractBodyTurnsEmptyProtocol] -# flags: --strict-optional from b import Base class Sub(Base): @@ -10084,7 +10189,7 @@ class Base(Protocol): def meth(self) -> int: ... [out] == -main:6: error: Call to abstract method "meth" of "Base" with trivial body via super() is unsafe +main:5: error: Call to abstract method "meth" of "Base" with trivial body via super() is unsafe [case testPrettyMessageSorting] # flags: --pretty @@ -10102,23 +10207,6 @@ object + 1 1() [out] -b.py:1: error: Unsupported left operand type for + ("Type[object]") - object + 1 - ^ -a.py:1: error: Unsupported operand types for + ("int" and "str") - 1 + '' - ^ -== -b.py:1: error: Unsupported left operand type for + ("Type[object]") - object + 1 - ^ -b.py:2: error: "int" not callable - 1() - ^ -a.py:1: error: Unsupported operand types for + ("int" and "str") - 1 + '' - ^ -[out version>=3.8] b.py:1: error: Unsupported left operand type for + ("Type[object]") object + 1 ^~~~~~~~~~ @@ -10341,3 +10429,70 @@ reveal_type(x) [out] == a.py:3: note: Revealed type is "Union[def (x: builtins.int) -> builtins.int, def (*x: builtins.int) -> builtins.int]" + +[case testErrorInReAddedModule] +# flags: --disallow-untyped-defs --follow-imports=error +# cmd: mypy a.py +# cmd2: mypy b.py +# cmd3: mypy a.py + +[file a.py] +def f(): pass +[file b.py] +def f(): pass +[file unrelated.txt.3] +[out] +a.py:1: error: Function is missing a return type annotation +a.py:1: note: Use "-> None" if function does not return a value +== +b.py:1: error: Function is missing a return type annotation +b.py:1: note: Use "-> None" if function does not return a value +== +a.py:1: error: Function is missing a return type annotation +a.py:1: note: Use "-> None" if function does not return a value + +[case testModuleLevelGetAttrInStub] +import stub +import a +import b + +[file stub/__init__.pyi] +s: str +def __getattr__(self): pass + +[file a.py] + +[file a.py.2] +from stub import x +from stub.pkg import y +from stub.pkg.sub import z + +[file b.py] + +[file b.py.3] +from stub import s +reveal_type(s) + +[out] +== +== +b.py:2: note: Revealed type is "builtins.str" + +[case testRenameSubModule] +import a + +[file a.py] +import pkg.sub + +[file pkg/__init__.py] +[file pkg/sub/__init__.py] +from pkg.sub import mod +[file pkg/sub/mod.py] + +[file pkg/sub/__init__.py.2] +from pkg.sub import modb +[delete pkg/sub/mod.py.2] +[file pkg/sub/modb.py.2] + +[out] +== diff --git a/test-data/unit/fixtures/args.pyi b/test-data/unit/fixtures/args.pyi index 9985ccf84817..0020d9ceff46 100644 --- a/test-data/unit/fixtures/args.pyi +++ b/test-data/unit/fixtures/args.pyi @@ -1,5 +1,6 @@ # Builtins stub used to support *args, **kwargs. +import _typeshed from typing import TypeVar, Generic, Iterable, Sequence, Tuple, Dict, Any, overload, Mapping Tco = TypeVar('Tco', covariant=True) diff --git a/test-data/unit/fixtures/dataclasses.pyi b/test-data/unit/fixtures/dataclasses.pyi index e9394c84ba7d..29f87ae97e62 100644 --- a/test-data/unit/fixtures/dataclasses.pyi +++ b/test-data/unit/fixtures/dataclasses.pyi @@ -1,7 +1,9 @@ +import _typeshed from typing import ( Generic, Iterator, Iterable, Mapping, Optional, Sequence, Tuple, TypeVar, Union, overload, ) +from typing_extensions import override _T = TypeVar('_T') _U = TypeVar('_U') @@ -28,8 +30,10 @@ class dict(Mapping[KT, VT]): def __init__(self, **kwargs: VT) -> None: pass @overload def __init__(self, arg: Iterable[Tuple[KT, VT]], **kwargs: VT) -> None: pass + @override def __getitem__(self, key: KT) -> VT: pass def __setitem__(self, k: KT, v: VT) -> None: pass + @override def __iter__(self) -> Iterator[KT]: pass def __contains__(self, item: object) -> int: pass def update(self, a: Mapping[KT, VT]) -> None: pass @@ -41,9 +45,12 @@ class dict(Mapping[KT, VT]): class list(Generic[_T], Sequence[_T]): def __contains__(self, item: object) -> int: pass + @override def __getitem__(self, key: int) -> _T: pass + @override def __iter__(self) -> Iterator[_T]: pass class function: pass class classmethod: pass +class staticmethod: pass property = object() diff --git a/test-data/unit/fixtures/dict-full.pyi b/test-data/unit/fixtures/dict-full.pyi new file mode 100644 index 000000000000..f20369ce9332 --- /dev/null +++ b/test-data/unit/fixtures/dict-full.pyi @@ -0,0 +1,83 @@ +# Builtins stub used in dictionary-related test cases (more complete). + +from _typeshed import SupportsKeysAndGetItem +import _typeshed +from typing import ( + TypeVar, Generic, Iterable, Iterator, Mapping, Tuple, overload, Optional, Union, Sequence, + Self, +) + +T = TypeVar('T') +T2 = TypeVar('T2') +KT = TypeVar('KT') +VT = TypeVar('VT') + +class object: + def __init__(self) -> None: pass + def __init_subclass__(cls) -> None: pass + def __eq__(self, other: object) -> bool: pass + +class type: + __annotations__: Mapping[str, object] + +class dict(Mapping[KT, VT]): + @overload + def __init__(self, **kwargs: VT) -> None: pass + @overload + def __init__(self, arg: Iterable[Tuple[KT, VT]], **kwargs: VT) -> None: pass + def __getitem__(self, key: KT) -> VT: pass + def __setitem__(self, k: KT, v: VT) -> None: pass + def __iter__(self) -> Iterator[KT]: pass + def __contains__(self, item: object) -> int: pass + def update(self, a: SupportsKeysAndGetItem[KT, VT]) -> None: pass + @overload + def get(self, k: KT) -> Optional[VT]: pass + @overload + def get(self, k: KT, default: Union[VT, T]) -> Union[VT, T]: pass + def __len__(self) -> int: ... + + # This was actually added in 3.9: + @overload + def __or__(self, __value: dict[KT, VT]) -> dict[KT, VT]: ... + @overload + def __or__(self, __value: dict[T, T2]) -> dict[Union[KT, T], Union[VT, T2]]: ... + @overload + def __ror__(self, __value: dict[KT, VT]) -> dict[KT, VT]: ... + @overload + def __ror__(self, __value: dict[T, T2]) -> dict[Union[KT, T], Union[VT, T2]]: ... + # dict.__ior__ should be kept roughly in line with MutableMapping.update() + @overload # type: ignore[misc] + def __ior__(self, __value: _typeshed.SupportsKeysAndGetItem[KT, VT]) -> Self: ... + @overload + def __ior__(self, __value: Iterable[Tuple[KT, VT]]) -> Self: ... + +class int: # for convenience + def __add__(self, x: Union[int, complex]) -> int: pass + def __radd__(self, x: int) -> int: pass + def __sub__(self, x: Union[int, complex]) -> int: pass + def __neg__(self) -> int: pass + real: int + imag: int + +class str: pass # for keyword argument key type +class bytes: pass + +class list(Sequence[T]): # needed by some test cases + def __getitem__(self, x: int) -> T: pass + def __iter__(self) -> Iterator[T]: pass + def __mul__(self, x: int) -> list[T]: pass + def __contains__(self, item: object) -> bool: pass + def append(self, item: T) -> None: pass + +class tuple(Generic[T]): pass +class function: pass +class float: pass +class complex: pass +class bool(int): pass + +class ellipsis: + __class__: object +def isinstance(x: object, t: Union[type, Tuple[type, ...]]) -> bool: pass +class BaseException: pass + +def iter(__iterable: Iterable[T]) -> Iterator[T]: pass diff --git a/test-data/unit/fixtures/dict.pyi b/test-data/unit/fixtures/dict.pyi index 153832411f50..ed2287511161 100644 --- a/test-data/unit/fixtures/dict.pyi +++ b/test-data/unit/fixtures/dict.pyi @@ -1,20 +1,25 @@ -# Builtins stub used in dictionary-related test cases. +# Builtins stub used in dictionary-related test cases (stripped down). +# +# NOTE: Use dict-full.pyi if you need more builtins instead of adding here, +# if feasible. +from _typeshed import SupportsKeysAndGetItem +import _typeshed from typing import ( - TypeVar, Generic, Iterable, Iterator, Mapping, Tuple, overload, Optional, Union, Sequence + TypeVar, Generic, Iterable, Iterator, Mapping, Tuple, overload, Optional, Union, Sequence, + Self, ) T = TypeVar('T') +T2 = TypeVar('T2') KT = TypeVar('KT') VT = TypeVar('VT') class object: def __init__(self) -> None: pass - def __init_subclass__(cls) -> None: pass def __eq__(self, other: object) -> bool: pass -class type: - __annotations__: Mapping[str, object] +class type: pass class dict(Mapping[KT, VT]): @overload @@ -25,7 +30,7 @@ class dict(Mapping[KT, VT]): def __setitem__(self, k: KT, v: VT) -> None: pass def __iter__(self) -> Iterator[KT]: pass def __contains__(self, item: object) -> int: pass - def update(self, a: Mapping[KT, VT]) -> None: pass + def update(self, a: SupportsKeysAndGetItem[KT, VT]) -> None: pass @overload def get(self, k: KT) -> Optional[VT]: pass @overload @@ -36,9 +41,6 @@ class int: # for convenience def __add__(self, x: Union[int, complex]) -> int: pass def __radd__(self, x: int) -> int: pass def __sub__(self, x: Union[int, complex]) -> int: pass - def __neg__(self) -> int: pass - real: int - imag: int class str: pass # for keyword argument key type class bytes: pass @@ -55,10 +57,8 @@ class function: pass class float: pass class complex: pass class bool(int): pass - -class ellipsis: - __class__: object -def isinstance(x: object, t: Union[type, Tuple[type, ...]]) -> bool: pass +class ellipsis: pass class BaseException: pass +def isinstance(x: object, t: Union[type, Tuple[type, ...]]) -> bool: pass def iter(__iterable: Iterable[T]) -> Iterator[T]: pass diff --git a/test-data/unit/fixtures/len.pyi b/test-data/unit/fixtures/len.pyi new file mode 100644 index 000000000000..ee39d952701f --- /dev/null +++ b/test-data/unit/fixtures/len.pyi @@ -0,0 +1,39 @@ +from typing import Tuple, TypeVar, Generic, Union, Type, Sequence, Mapping +from typing_extensions import Protocol + +T = TypeVar("T") +V = TypeVar("V") + +class object: + def __init__(self) -> None: pass + +class type: + def __init__(self, x) -> None: pass + +class tuple(Sequence[T]): + def __len__(self) -> int: pass + +class list(Sequence[T]): pass +class dict(Mapping[T, V]): pass + +class function: pass + +class Sized(Protocol): + def __len__(self) -> int: pass + +def len(__obj: Sized) -> int: ... +def isinstance(x: object, t: Union[Type[object], Tuple[Type[object], ...]]) -> bool: pass + +class int: + def __add__(self, other: int) -> int: pass + def __eq__(self, other: int) -> bool: pass + def __ne__(self, other: int) -> bool: pass + def __lt__(self, n: int) -> bool: pass + def __gt__(self, n: int) -> bool: pass + def __le__(self, n: int) -> bool: pass + def __ge__(self, n: int) -> bool: pass + def __neg__(self) -> int: pass +class float: pass +class bool(int): pass +class str(Sequence[str]): pass +class ellipsis: pass diff --git a/test-data/unit/fixtures/list.pyi b/test-data/unit/fixtures/list.pyi index 90fbabe8bc92..3dcdf18b2faa 100644 --- a/test-data/unit/fixtures/list.pyi +++ b/test-data/unit/fixtures/list.pyi @@ -6,6 +6,7 @@ T = TypeVar('T') class object: def __init__(self) -> None: pass + def __eq__(self, other: object) -> bool: pass class type: pass class ellipsis: pass diff --git a/test-data/unit/fixtures/module_all.pyi b/test-data/unit/fixtures/module_all.pyi index b14152c7e98f..d6060583b20e 100644 --- a/test-data/unit/fixtures/module_all.pyi +++ b/test-data/unit/fixtures/module_all.pyi @@ -13,6 +13,7 @@ class bool: pass class list(Generic[_T], Sequence[_T]): def append(self, x: _T): pass def extend(self, x: Sequence[_T]): pass + def remove(self, x: _T): pass def __add__(self, rhs: Sequence[_T]) -> list[_T]: pass class tuple(Generic[_T]): pass class ellipsis: pass diff --git a/test-data/unit/fixtures/ops.pyi b/test-data/unit/fixtures/ops.pyi index 9cc4d22eb0a7..df3b163166ad 100644 --- a/test-data/unit/fixtures/ops.pyi +++ b/test-data/unit/fixtures/ops.pyi @@ -24,8 +24,6 @@ class tuple(Sequence[Tco]): class function: pass -class bool: pass - class str: def __init__(self, x: 'int') -> None: pass def __add__(self, x: 'str') -> 'str': pass @@ -54,6 +52,8 @@ class int: def __gt__(self, x: 'int') -> bool: pass def __ge__(self, x: 'int') -> bool: pass +class bool(int): pass + class float: def __add__(self, x: 'float') -> 'float': pass def __radd__(self, x: 'float') -> 'float': pass diff --git a/test-data/unit/fixtures/paramspec.pyi b/test-data/unit/fixtures/paramspec.pyi index 0686924aad6f..dfb5e126f242 100644 --- a/test-data/unit/fixtures/paramspec.pyi +++ b/test-data/unit/fixtures/paramspec.pyi @@ -1,5 +1,6 @@ # builtins stub for paramspec-related test cases +import _typeshed from typing import ( Sequence, Generic, TypeVar, Iterable, Iterator, Tuple, Mapping, Optional, Union, Type, overload, Protocol @@ -15,6 +16,7 @@ class object: class function: ... class ellipsis: ... +class classmethod: ... class type: def __init__(self, *a: object) -> None: ... @@ -29,7 +31,8 @@ class list(Sequence[T], Generic[T]): def __iter__(self) -> Iterator[T]: ... class int: - def __neg__(self) -> 'int': ... + def __neg__(self) -> int: ... + def __add__(self, other: int) -> int: ... class bool(int): ... class float: ... diff --git a/test-data/unit/fixtures/attr.pyi b/test-data/unit/fixtures/plugin_attrs.pyi similarity index 53% rename from test-data/unit/fixtures/attr.pyi rename to test-data/unit/fixtures/plugin_attrs.pyi index 3bd4f0ec7cbe..7fd641727253 100644 --- a/test-data/unit/fixtures/attr.pyi +++ b/test-data/unit/fixtures/plugin_attrs.pyi @@ -1,21 +1,22 @@ -# Builtins stub used to support @attr.s tests. -from typing import Union, overload +# Builtins stub used to support attrs plugin tests. +from typing import Union, overload, Generic, Sequence, TypeVar, Type, Iterable, Iterator class object: def __init__(self) -> None: pass def __eq__(self, o: object) -> bool: pass def __ne__(self, o: object) -> bool: pass + def __hash__(self) -> int: ... class type: pass class bytes: pass class function: pass -class bool: pass class float: pass class int: @overload def __init__(self, x: Union[str, bytes, int] = ...) -> None: ... @overload def __init__(self, x: Union[str, bytes], base: int) -> None: ... +class bool(int): pass class complex: @overload def __init__(self, real: float = ..., im: float = ...) -> None: ... @@ -24,6 +25,15 @@ class complex: class str: pass class ellipsis: pass -class tuple: pass class list: pass class dict: pass + +T = TypeVar("T") +Tco = TypeVar('Tco', covariant=True) +class tuple(Sequence[Tco], Generic[Tco]): + def __new__(cls: Type[T], iterable: Iterable[Tco] = ...) -> T: ... + def __iter__(self) -> Iterator[Tco]: pass + def __contains__(self, item: object) -> bool: pass + def __getitem__(self, x: int) -> Tco: pass + +property = object() # Dummy definition diff --git a/test-data/unit/fixtures/primitives.pyi b/test-data/unit/fixtures/primitives.pyi index 90d76b9d76dd..63128a8ae03d 100644 --- a/test-data/unit/fixtures/primitives.pyi +++ b/test-data/unit/fixtures/primitives.pyi @@ -1,4 +1,5 @@ # builtins stub with non-generic primitive types +import _typeshed from typing import Generic, TypeVar, Sequence, Iterator, Mapping, Iterable, Tuple, Union T = TypeVar('T') @@ -11,7 +12,7 @@ class object: def __ne__(self, other: object) -> bool: pass class type: - def __init__(self, x) -> None: pass + def __init__(self, x: object) -> None: pass class int: # Note: this is a simplification of the actual signature @@ -29,7 +30,7 @@ class str(Sequence[str]): def __iter__(self) -> Iterator[str]: pass def __contains__(self, other: object) -> bool: pass def __getitem__(self, item: int) -> str: pass - def format(self, *args, **kwargs) -> str: pass + def format(self, *args: object, **kwargs: object) -> str: pass class bytes(Sequence[int]): def __iter__(self) -> Iterator[int]: pass def __contains__(self, other: object) -> bool: pass @@ -44,7 +45,8 @@ class memoryview(Sequence[int]): def __iter__(self) -> Iterator[int]: pass def __contains__(self, other: object) -> bool: pass def __getitem__(self, item: int) -> int: pass -class tuple(Generic[T]): pass +class tuple(Generic[T]): + def __contains__(self, other: object) -> bool: pass class list(Sequence[T]): def __iter__(self) -> Iterator[T]: pass def __contains__(self, other: object) -> bool: pass diff --git a/test-data/unit/fixtures/property.pyi b/test-data/unit/fixtures/property.pyi index 2397c05c78d5..667bdc02d0f5 100644 --- a/test-data/unit/fixtures/property.pyi +++ b/test-data/unit/fixtures/property.pyi @@ -16,6 +16,7 @@ class classmethod: pass class list: pass class dict: pass class int: pass +class float: pass class str: pass class bytes: pass class bool: pass diff --git a/test-data/unit/fixtures/slice.pyi b/test-data/unit/fixtures/slice.pyi index b5a4549da068..b22a12b5213f 100644 --- a/test-data/unit/fixtures/slice.pyi +++ b/test-data/unit/fixtures/slice.pyi @@ -15,3 +15,5 @@ class str: pass class slice: pass class ellipsis: pass class dict: pass +class list(Generic[T]): + def __getitem__(self, x: slice) -> list[T]: pass diff --git a/test-data/unit/fixtures/tuple.pyi b/test-data/unit/fixtures/tuple.pyi index 0261731304b1..eb89de8c86ef 100644 --- a/test-data/unit/fixtures/tuple.pyi +++ b/test-data/unit/fixtures/tuple.pyi @@ -1,5 +1,6 @@ # Builtins stub used in tuple-related test cases. +import _typeshed from typing import Iterable, Iterator, TypeVar, Generic, Sequence, Optional, overload, Tuple, Type T = TypeVar("T") @@ -31,6 +32,7 @@ class classmethod: pass # We need int and slice for indexing tuples. class int: def __neg__(self) -> 'int': pass + def __pos__(self) -> 'int': pass class float: pass class slice: pass class bool(int): pass @@ -48,8 +50,6 @@ class list(Sequence[T], Generic[T]): def isinstance(x: object, t: type) -> bool: pass -def sum(iterable: Iterable[T], start: Optional[T] = None) -> T: pass - class BaseException: pass class dict: pass diff --git a/test-data/unit/fixtures/typing-async.pyi b/test-data/unit/fixtures/typing-async.pyi index b061337845c2..9897dfd0b270 100644 --- a/test-data/unit/fixtures/typing-async.pyi +++ b/test-data/unit/fixtures/typing-async.pyi @@ -24,6 +24,7 @@ ClassVar = 0 Final = 0 Literal = 0 NoReturn = 0 +Self = 0 T = TypeVar('T') T_co = TypeVar('T_co', covariant=True) @@ -108,6 +109,7 @@ class Sequence(Iterable[T_co], Container[T_co]): def __getitem__(self, n: Any) -> T_co: pass class Mapping(Iterable[T], Generic[T, T_co], metaclass=ABCMeta): + def keys(self) -> Iterable[T]: pass # Approximate return type def __getitem__(self, key: T) -> T_co: pass @overload def get(self, k: T) -> Optional[T_co]: pass diff --git a/test-data/unit/fixtures/typing-full.pyi b/test-data/unit/fixtures/typing-full.pyi index 1471473249dc..f7da75fa4cd0 100644 --- a/test-data/unit/fixtures/typing-full.pyi +++ b/test-data/unit/fixtures/typing-full.pyi @@ -10,13 +10,17 @@ from abc import abstractmethod, ABCMeta class GenericMeta(type): pass +class _SpecialForm: ... +class TypeVar: ... +class ParamSpec: ... +class TypeVarTuple: ... + def cast(t, o): ... def assert_type(o, t): ... overload = 0 Any = 0 Union = 0 Optional = 0 -TypeVar = 0 Generic = 0 Protocol = 0 Tuple = 0 @@ -30,6 +34,7 @@ Literal = 0 TypedDict = 0 NoReturn = 0 NewType = 0 +Self = 0 T = TypeVar('T') T_co = TypeVar('T_co', covariant=True) @@ -38,6 +43,8 @@ U = TypeVar('U') V = TypeVar('V') S = TypeVar('S') +def final(x: T) -> T: ... + class NamedTuple(tuple[Any, ...]): ... # Note: definitions below are different from typeshed, variances are declared @@ -136,6 +143,7 @@ class MutableSequence(Sequence[T]): def __setitem__(self, n: Any, o: T) -> None: pass class Mapping(Iterable[T], Generic[T, T_co], metaclass=ABCMeta): + def keys(self) -> Iterable[T]: pass # Approximate return type def __getitem__(self, key: T) -> T_co: pass @overload def get(self, k: T) -> Optional[T_co]: pass @@ -180,8 +188,6 @@ class _TypedDict(Mapping[str, object]): def update(self: T, __m: T) -> None: ... def __delitem__(self, k: NoReturn) -> None: ... -class _SpecialForm: pass - def dataclass_transform( *, eq_default: bool = ..., @@ -190,3 +196,17 @@ def dataclass_transform( field_specifiers: tuple[type[Any] | Callable[..., Any], ...] = ..., **kwargs: Any, ) -> Callable[[T], T]: ... +def override(__arg: T) -> T: ... + +# Was added in 3.11 +def reveal_type(__obj: T) -> T: ... + +# Only exists in type checking time: +def type_check_only(__func_or_class: T) -> T: ... + +# Was added in 3.12 +@final +class TypeAliasType: + def __init__( + self, name: str, value: Any, *, type_params: Tuple[Union[TypeVar, ParamSpec, TypeVarTuple], ...] = () + ) -> None: ... diff --git a/test-data/unit/fixtures/typing-medium.pyi b/test-data/unit/fixtures/typing-medium.pyi index 863b0703989d..c19c5d5d96e2 100644 --- a/test-data/unit/fixtures/typing-medium.pyi +++ b/test-data/unit/fixtures/typing-medium.pyi @@ -28,6 +28,7 @@ NoReturn = 0 NewType = 0 TypeAlias = 0 LiteralString = 0 +Self = 0 T = TypeVar('T') T_co = TypeVar('T_co', covariant=True) @@ -55,6 +56,7 @@ class Sequence(Iterable[T_co]): def __getitem__(self, n: Any) -> T_co: pass class Mapping(Iterable[T], Generic[T, T_co]): + def keys(self) -> Iterable[T]: pass # Approximate return type def __getitem__(self, key: T) -> T_co: pass class SupportsInt(Protocol): diff --git a/test-data/unit/fixtures/typing-namedtuple.pyi b/test-data/unit/fixtures/typing-namedtuple.pyi index 1a31549463b6..f4744575fc09 100644 --- a/test-data/unit/fixtures/typing-namedtuple.pyi +++ b/test-data/unit/fixtures/typing-namedtuple.pyi @@ -6,15 +6,23 @@ Type = 0 Literal = 0 Optional = 0 Self = 0 +Tuple = 0 +ClassVar = 0 +T = TypeVar('T') T_co = TypeVar('T_co', covariant=True) KT = TypeVar('KT') class Iterable(Generic[T_co]): pass class Iterator(Iterable[T_co]): pass class Sequence(Iterable[T_co]): pass -class Mapping(Iterable[KT], Generic[KT, T_co]): pass +class Mapping(Iterable[KT], Generic[KT, T_co]): + def keys(self) -> Iterable[T]: pass # Approximate return type + def __getitem__(self, key: T) -> T_co: pass -class Tuple(Sequence): pass -class NamedTuple(Tuple): - name: str +class NamedTuple(tuple[Any, ...]): + _fields: ClassVar[tuple[str, ...]] + @overload + def __init__(self, typename: str, fields: Iterable[tuple[str, Any]] = ...) -> None: ... + @overload + def __init__(self, typename: str, fields: None = None, **kwargs: Any) -> None: ... diff --git a/test-data/unit/fixtures/typing-override.pyi b/test-data/unit/fixtures/typing-override.pyi new file mode 100644 index 000000000000..606ca63d4f0d --- /dev/null +++ b/test-data/unit/fixtures/typing-override.pyi @@ -0,0 +1,25 @@ +TypeVar = 0 +Generic = 0 +Any = 0 +overload = 0 +Type = 0 +Literal = 0 +Optional = 0 +Self = 0 +Tuple = 0 +ClassVar = 0 +Callable = 0 + +T = TypeVar('T') +T_co = TypeVar('T_co', covariant=True) +KT = TypeVar('KT') + +class Iterable(Generic[T_co]): pass +class Iterator(Iterable[T_co]): pass +class Sequence(Iterable[T_co]): pass +class Mapping(Iterable[KT], Generic[KT, T_co]): + def keys(self) -> Iterable[T]: pass # Approximate return type + def __getitem__(self, key: T) -> T_co: pass + + +def override(__arg: T) -> T: ... diff --git a/test-data/unit/fixtures/typing-typeddict-iror.pyi b/test-data/unit/fixtures/typing-typeddict-iror.pyi new file mode 100644 index 000000000000..e452c8497109 --- /dev/null +++ b/test-data/unit/fixtures/typing-typeddict-iror.pyi @@ -0,0 +1,66 @@ +# Test stub for typing module that includes TypedDict `|` operator. +# It only covers `__or__`, `__ror__`, and `__ior__`. +# +# We cannot define these methods in `typing-typeddict.pyi`, +# because they need `dict` with two type args, +# and not all tests using `[typing typing-typeddict.pyi]` have the proper +# `dict` stub. +# +# Keep in sync with `typeshed`'s definition. +from abc import ABCMeta + +cast = 0 +assert_type = 0 +overload = 0 +Any = 0 +Union = 0 +Optional = 0 +TypeVar = 0 +Generic = 0 +Protocol = 0 +Tuple = 0 +Callable = 0 +NamedTuple = 0 +Final = 0 +Literal = 0 +TypedDict = 0 +NoReturn = 0 +Required = 0 +NotRequired = 0 +Self = 0 + +T = TypeVar('T') +T_co = TypeVar('T_co', covariant=True) +V = TypeVar('V') + +# Note: definitions below are different from typeshed, variances are declared +# to silence the protocol variance checks. Maybe it is better to use type: ignore? + +class Sized(Protocol): + def __len__(self) -> int: pass + +class Iterable(Protocol[T_co]): + def __iter__(self) -> 'Iterator[T_co]': pass + +class Iterator(Iterable[T_co], Protocol): + def __next__(self) -> T_co: pass + +class Sequence(Iterable[T_co]): + # misc is for explicit Any. + def __getitem__(self, n: Any) -> T_co: pass # type: ignore[misc] + +class Mapping(Iterable[T], Generic[T, T_co], metaclass=ABCMeta): + pass + +# Fallback type for all typed dicts (does not exist at runtime). +class _TypedDict(Mapping[str, object]): + @overload + def __or__(self, __value: Self) -> Self: ... + @overload + def __or__(self, __value: dict[str, Any]) -> dict[str, object]: ... + @overload + def __ror__(self, __value: Self) -> Self: ... + @overload + def __ror__(self, __value: dict[str, Any]) -> dict[str, object]: ... + # supposedly incompatible definitions of __or__ and __ior__ + def __ior__(self, __value: Self) -> Self: ... # type: ignore[misc] diff --git a/test-data/unit/fixtures/typing-typeddict.pyi b/test-data/unit/fixtures/typing-typeddict.pyi index 92ae402b9ea5..24a2f1328981 100644 --- a/test-data/unit/fixtures/typing-typeddict.pyi +++ b/test-data/unit/fixtures/typing-typeddict.pyi @@ -49,6 +49,7 @@ class Sequence(Iterable[T_co]): def __getitem__(self, n: Any) -> T_co: pass # type: ignore[misc] class Mapping(Iterable[T], Generic[T, T_co], metaclass=ABCMeta): + def keys(self) -> Iterable[T]: pass # Approximate return type def __getitem__(self, key: T) -> T_co: pass @overload def get(self, k: T) -> Optional[T_co]: pass diff --git a/test-data/unit/hacks.txt b/test-data/unit/hacks.txt index 43114a8af004..15b1065cb7a9 100644 --- a/test-data/unit/hacks.txt +++ b/test-data/unit/hacks.txt @@ -5,17 +5,6 @@ Due to historical reasons, test cases contain things that may appear baffling without extra context. This file attempts to describe most of them. -Strict optional is disabled be default --------------------------------------- - -Strict optional checking is enabled in mypy by default, but test cases -must enable it explicitly, either through `# flags: --strict-optional` -or by including `optional` as a substring in your test file name. - -The reason for this is that many test cases written before strict -optional was implemented use the idiom `x = None # type: t`, and -updating all of these test cases would take a lot of work. - Dummy if statements to prevent redefinition ------------------------------------------- diff --git a/test-data/unit/lib-stub/_typeshed.pyi b/test-data/unit/lib-stub/_typeshed.pyi new file mode 100644 index 000000000000..054ad0ec0c46 --- /dev/null +++ b/test-data/unit/lib-stub/_typeshed.pyi @@ -0,0 +1,8 @@ +from typing import Protocol, TypeVar, Iterable + +_KT = TypeVar("_KT") +_VT_co = TypeVar("_VT_co", covariant=True) + +class SupportsKeysAndGetItem(Protocol[_KT, _VT_co]): + def keys(self) -> Iterable[_KT]: pass + def __getitem__(self, __key: _KT) -> _VT_co: pass diff --git a/test-data/unit/lib-stub/attr/__init__.pyi b/test-data/unit/lib-stub/attr/__init__.pyi index 795e5d3f4f69..466c6913062d 100644 --- a/test-data/unit/lib-stub/attr/__init__.pyi +++ b/test-data/unit/lib-stub/attr/__init__.pyi @@ -131,6 +131,7 @@ def define( *, these: Optional[Mapping[str, Any]] = ..., repr: bool = ..., + unsafe_hash: Optional[bool]=None, hash: Optional[bool] = ..., init: bool = ..., slots: bool = ..., @@ -153,6 +154,7 @@ def define( *, these: Optional[Mapping[str, Any]] = ..., repr: bool = ..., + unsafe_hash: Optional[bool]=None, hash: Optional[bool] = ..., init: bool = ..., slots: bool = ..., @@ -244,3 +246,8 @@ def field( order: Optional[bool] = ..., on_setattr: Optional[object] = ..., ) -> Any: ... + +def evolve(inst: _T, **changes: Any) -> _T: ... +def assoc(inst: _T, **changes: Any) -> _T: ... + +def fields(cls: type) -> Any: ... diff --git a/test-data/unit/lib-stub/attrs/__init__.pyi b/test-data/unit/lib-stub/attrs/__init__.pyi index d25774045132..d0a65c84d9d8 100644 --- a/test-data/unit/lib-stub/attrs/__init__.pyi +++ b/test-data/unit/lib-stub/attrs/__init__.pyi @@ -1,4 +1,13 @@ -from typing import TypeVar, overload, Callable, Any, Optional, Union, Sequence, Mapping +from typing import TypeVar, overload, Callable, Any, Optional, Union, Sequence, Mapping, \ + Protocol, ClassVar, Type +from typing_extensions import TypeGuard + +from attr import Attribute as Attribute + + +class AttrsInstance(Protocol): + __attrs_attrs__: ClassVar[Any] + _T = TypeVar('_T') _C = TypeVar('_C', bound=type) @@ -13,6 +22,7 @@ def define( *, these: Optional[Mapping[str, Any]] = ..., repr: bool = ..., + unsafe_hash: Optional[bool]=None, hash: Optional[bool] = ..., init: bool = ..., slots: bool = ..., @@ -35,6 +45,7 @@ def define( *, these: Optional[Mapping[str, Any]] = ..., repr: bool = ..., + unsafe_hash: Optional[bool]=None, hash: Optional[bool] = ..., init: bool = ..., slots: bool = ..., @@ -70,6 +81,7 @@ def field( eq: Optional[bool] = ..., order: Optional[bool] = ..., on_setattr: Optional[_OnSetAttrArgType] = ..., + alias: Optional[str] = ..., ) -> Any: ... # This form catches an explicit None or no default and infers the type from the @@ -89,6 +101,7 @@ def field( eq: Optional[bool] = ..., order: Optional[bool] = ..., on_setattr: Optional[object] = ..., + alias: Optional[str] = ..., ) -> _T: ... # This form catches an explicit default argument. @@ -107,6 +120,7 @@ def field( eq: Optional[bool] = ..., order: Optional[bool] = ..., on_setattr: Optional[object] = ..., + alias: Optional[str] = ..., ) -> _T: ... # This form covers type=non-Type: e.g. forward references (str), Any @@ -125,4 +139,10 @@ def field( eq: Optional[bool] = ..., order: Optional[bool] = ..., on_setattr: Optional[object] = ..., + alias: Optional[str] = ..., ) -> Any: ... + +def evolve(inst: _T, **changes: Any) -> _T: ... +def assoc(inst: _T, **changes: Any) -> _T: ... +def has(cls: type) -> TypeGuard[Type[AttrsInstance]]: ... +def fields(cls: Type[AttrsInstance]) -> Any: ... diff --git a/test-data/unit/lib-stub/builtins.pyi b/test-data/unit/lib-stub/builtins.pyi index c2ac78c41661..17d519cc8eea 100644 --- a/test-data/unit/lib-stub/builtins.pyi +++ b/test-data/unit/lib-stub/builtins.pyi @@ -2,6 +2,8 @@ # # Use [builtins fixtures/...pyi] if you need more features. +import _typeshed + class object: def __init__(self) -> None: pass diff --git a/test-data/unit/lib-stub/contextlib.pyi b/test-data/unit/lib-stub/contextlib.pyi index e2a0cccd562a..ca9e91cf4d65 100644 --- a/test-data/unit/lib-stub/contextlib.pyi +++ b/test-data/unit/lib-stub/contextlib.pyi @@ -1,6 +1,5 @@ -import sys -from typing import Generic, TypeVar, Callable, Iterator -from typing import ContextManager as ContextManager +from typing import AsyncIterator, Generic, TypeVar, Callable, Iterator +from typing import ContextManager as ContextManager, AsyncContextManager as AsyncContextManager _T = TypeVar('_T') @@ -11,7 +10,4 @@ class GeneratorContextManager(ContextManager[_T], Generic[_T]): def contextmanager(func: Callable[..., Iterator[_T]]) -> Callable[..., GeneratorContextManager[_T]]: ... -if sys.version_info >= (3, 7): - from typing import AsyncIterator - from typing import AsyncContextManager as AsyncContextManager - def asynccontextmanager(func: Callable[..., AsyncIterator[_T]]) -> Callable[..., AsyncContextManager[_T]]: ... +def asynccontextmanager(func: Callable[..., AsyncIterator[_T]]) -> Callable[..., AsyncContextManager[_T]]: ... diff --git a/test-data/unit/lib-stub/dataclasses.pyi b/test-data/unit/lib-stub/dataclasses.pyi index bd33b459266c..cf43747757bd 100644 --- a/test-data/unit/lib-stub/dataclasses.pyi +++ b/test-data/unit/lib-stub/dataclasses.pyi @@ -1,6 +1,14 @@ -from typing import Any, Callable, Generic, Mapping, Optional, TypeVar, overload, Type +from typing import Any, Callable, Generic, Literal, Mapping, Optional, TypeVar, overload, Type, \ + Protocol, ClassVar +from typing_extensions import TypeGuard + +# DataclassInstance is in _typeshed.pyi normally, but alas we can't do the same for lib-stub +# due to test-data/unit/lib-stub/builtins.pyi not having 'tuple'. +class DataclassInstance(Protocol): + __dataclass_fields__: ClassVar[dict[str, Field[Any]]] _T = TypeVar('_T') +_DataclassT = TypeVar("_DataclassT", bound=DataclassInstance) class InitVar(Generic[_T]): ... @@ -32,3 +40,13 @@ def field(*, class Field(Generic[_T]): pass + +@overload +def is_dataclass(obj: DataclassInstance) -> Literal[True]: ... +@overload +def is_dataclass(obj: type) -> TypeGuard[type[DataclassInstance]]: ... +@overload +def is_dataclass(obj: object) -> TypeGuard[DataclassInstance | type[DataclassInstance]]: ... + + +def replace(__obj: _DataclassT, **changes: Any) -> _DataclassT: ... diff --git a/test-data/unit/lib-stub/functools.pyi b/test-data/unit/lib-stub/functools.pyi index 9e62a14c2f34..e665b2bad0c2 100644 --- a/test-data/unit/lib-stub/functools.pyi +++ b/test-data/unit/lib-stub/functools.pyi @@ -1,4 +1,4 @@ -from typing import Generic, TypeVar, Callable, Any, Mapping +from typing import Generic, TypeVar, Callable, Any, Mapping, overload _T = TypeVar("_T") diff --git a/test-data/unit/lib-stub/math.pyi b/test-data/unit/lib-stub/math.pyi new file mode 100644 index 000000000000..06f8878a563e --- /dev/null +++ b/test-data/unit/lib-stub/math.pyi @@ -0,0 +1,20 @@ +pi: float +e: float +tau: float +inf: float +nan: float +def sqrt(__x: float) -> float: ... +def sin(__x: float) -> float: ... +def cos(__x: float) -> float: ... +def tan(__x: float) -> float: ... +def exp(__x: float) -> float: ... +def log(__x: float) -> float: ... +def floor(__x: float) -> int: ... +def ceil(__x: float) -> int: ... +def fabs(__x: float) -> float: ... +def pow(__x: float, __y: float) -> float: ... +def copysign(__x: float, __y: float) -> float: ... +def isinf(__x: float) -> bool: ... +def isnan(__x: float) -> bool: ... +def isfinite(__x: float) -> bool: ... +def nextafter(__x: float, __y: float) -> float: ... diff --git a/test-data/unit/lib-stub/mypy_extensions.pyi b/test-data/unit/lib-stub/mypy_extensions.pyi index d79be8719417..4295c33f81ad 100644 --- a/test-data/unit/lib-stub/mypy_extensions.pyi +++ b/test-data/unit/lib-stub/mypy_extensions.pyi @@ -3,7 +3,6 @@ from typing import ( Any, Dict, Type, TypeVar, Optional, Any, Generic, Mapping, NoReturn as NoReturn, Iterator, Union, Protocol ) -import sys _T = TypeVar('_T') _U = TypeVar('_U') @@ -33,8 +32,6 @@ class _TypedDict(Mapping[str, object]): # Mypy expects that 'default' has a type variable type. def pop(self, k: NoReturn, default: _T = ...) -> object: ... def update(self: _T, __m: _T) -> None: ... - if sys.version_info < (3, 0): - def has_key(self, k: str) -> bool: ... def __delitem__(self, k: NoReturn) -> None: ... def TypedDict(typename: str, fields: Dict[str, Type[_T]], *, total: Any = ...) -> Type[dict]: ... @@ -50,68 +47,127 @@ mypyc_attr: Any class FlexibleAlias(Generic[_T, _U]): ... -if sys.version_info >= (3, 0): - class __SupportsInt(Protocol[T_co]): - def __int__(self) -> int: pass - - _Int = Union[int, i32, i64] - - class i32: - def __init__(self, x: Union[_Int, str, bytes, SupportsInt], base: int = 10) -> None: ... - def __add__(self, x: i32) -> i32: ... - def __radd__(self, x: i32) -> i32: ... - def __sub__(self, x: i32) -> i32: ... - def __rsub__(self, x: i32) -> i32: ... - def __mul__(self, x: i32) -> i32: ... - def __rmul__(self, x: i32) -> i32: ... - def __floordiv__(self, x: i32) -> i32: ... - def __rfloordiv__(self, x: i32) -> i32: ... - def __mod__(self, x: i32) -> i32: ... - def __rmod__(self, x: i32) -> i32: ... - def __and__(self, x: i32) -> i32: ... - def __rand__(self, x: i32) -> i32: ... - def __or__(self, x: i32) -> i32: ... - def __ror__(self, x: i32) -> i32: ... - def __xor__(self, x: i32) -> i32: ... - def __rxor__(self, x: i32) -> i32: ... - def __lshift__(self, x: i32) -> i32: ... - def __rlshift__(self, x: i32) -> i32: ... - def __rshift__(self, x: i32) -> i32: ... - def __rrshift__(self, x: i32) -> i32: ... - def __neg__(self) -> i32: ... - def __invert__(self) -> i32: ... - def __pos__(self) -> i32: ... - def __lt__(self, x: i32) -> bool: ... - def __le__(self, x: i32) -> bool: ... - def __ge__(self, x: i32) -> bool: ... - def __gt__(self, x: i32) -> bool: ... - - class i64: - def __init__(self, x: Union[_Int, str, bytes, SupportsInt], base: int = 10) -> None: ... - def __add__(self, x: i64) -> i64: ... - def __radd__(self, x: i64) -> i64: ... - def __sub__(self, x: i64) -> i64: ... - def __rsub__(self, x: i64) -> i64: ... - def __mul__(self, x: i64) -> i64: ... - def __rmul__(self, x: i64) -> i64: ... - def __floordiv__(self, x: i64) -> i64: ... - def __rfloordiv__(self, x: i64) -> i64: ... - def __mod__(self, x: i64) -> i64: ... - def __rmod__(self, x: i64) -> i64: ... - def __and__(self, x: i64) -> i64: ... - def __rand__(self, x: i64) -> i64: ... - def __or__(self, x: i64) -> i64: ... - def __ror__(self, x: i64) -> i64: ... - def __xor__(self, x: i64) -> i64: ... - def __rxor__(self, x: i64) -> i64: ... - def __lshift__(self, x: i64) -> i64: ... - def __rlshift__(self, x: i64) -> i64: ... - def __rshift__(self, x: i64) -> i64: ... - def __rrshift__(self, x: i64) -> i64: ... - def __neg__(self) -> i64: ... - def __invert__(self) -> i64: ... - def __pos__(self) -> i64: ... - def __lt__(self, x: i64) -> bool: ... - def __le__(self, x: i64) -> bool: ... - def __ge__(self, x: i64) -> bool: ... - def __gt__(self, x: i64) -> bool: ... +class __SupportsInt(Protocol[T_co]): + def __int__(self) -> int: pass + +_Int = Union[int, u8, i16, i32, i64] + +class u8: + def __init__(self, x: Union[_Int, str, bytes, SupportsInt], base: int = 10) -> None: ... + def __add__(self, x: u8) -> u8: ... + def __radd__(self, x: u8) -> u8: ... + def __sub__(self, x: u8) -> u8: ... + def __rsub__(self, x: u8) -> u8: ... + def __mul__(self, x: u8) -> u8: ... + def __rmul__(self, x: u8) -> u8: ... + def __floordiv__(self, x: u8) -> u8: ... + def __rfloordiv__(self, x: u8) -> u8: ... + def __mod__(self, x: u8) -> u8: ... + def __rmod__(self, x: u8) -> u8: ... + def __and__(self, x: u8) -> u8: ... + def __rand__(self, x: u8) -> u8: ... + def __or__(self, x: u8) -> u8: ... + def __ror__(self, x: u8) -> u8: ... + def __xor__(self, x: u8) -> u8: ... + def __rxor__(self, x: u8) -> u8: ... + def __lshift__(self, x: u8) -> u8: ... + def __rlshift__(self, x: u8) -> u8: ... + def __rshift__(self, x: u8) -> u8: ... + def __rrshift__(self, x: u8) -> u8: ... + def __neg__(self) -> u8: ... + def __invert__(self) -> u8: ... + def __pos__(self) -> u8: ... + def __lt__(self, x: u8) -> bool: ... + def __le__(self, x: u8) -> bool: ... + def __ge__(self, x: u8) -> bool: ... + def __gt__(self, x: u8) -> bool: ... + +class i16: + def __init__(self, x: Union[_Int, str, bytes, SupportsInt], base: int = 10) -> None: ... + def __add__(self, x: i16) -> i16: ... + def __radd__(self, x: i16) -> i16: ... + def __sub__(self, x: i16) -> i16: ... + def __rsub__(self, x: i16) -> i16: ... + def __mul__(self, x: i16) -> i16: ... + def __rmul__(self, x: i16) -> i16: ... + def __floordiv__(self, x: i16) -> i16: ... + def __rfloordiv__(self, x: i16) -> i16: ... + def __mod__(self, x: i16) -> i16: ... + def __rmod__(self, x: i16) -> i16: ... + def __and__(self, x: i16) -> i16: ... + def __rand__(self, x: i16) -> i16: ... + def __or__(self, x: i16) -> i16: ... + def __ror__(self, x: i16) -> i16: ... + def __xor__(self, x: i16) -> i16: ... + def __rxor__(self, x: i16) -> i16: ... + def __lshift__(self, x: i16) -> i16: ... + def __rlshift__(self, x: i16) -> i16: ... + def __rshift__(self, x: i16) -> i16: ... + def __rrshift__(self, x: i16) -> i16: ... + def __neg__(self) -> i16: ... + def __invert__(self) -> i16: ... + def __pos__(self) -> i16: ... + def __lt__(self, x: i16) -> bool: ... + def __le__(self, x: i16) -> bool: ... + def __ge__(self, x: i16) -> bool: ... + def __gt__(self, x: i16) -> bool: ... + +class i32: + def __init__(self, x: Union[_Int, str, bytes, SupportsInt], base: int = 10) -> None: ... + def __add__(self, x: i32) -> i32: ... + def __radd__(self, x: i32) -> i32: ... + def __sub__(self, x: i32) -> i32: ... + def __rsub__(self, x: i32) -> i32: ... + def __mul__(self, x: i32) -> i32: ... + def __rmul__(self, x: i32) -> i32: ... + def __floordiv__(self, x: i32) -> i32: ... + def __rfloordiv__(self, x: i32) -> i32: ... + def __mod__(self, x: i32) -> i32: ... + def __rmod__(self, x: i32) -> i32: ... + def __and__(self, x: i32) -> i32: ... + def __rand__(self, x: i32) -> i32: ... + def __or__(self, x: i32) -> i32: ... + def __ror__(self, x: i32) -> i32: ... + def __xor__(self, x: i32) -> i32: ... + def __rxor__(self, x: i32) -> i32: ... + def __lshift__(self, x: i32) -> i32: ... + def __rlshift__(self, x: i32) -> i32: ... + def __rshift__(self, x: i32) -> i32: ... + def __rrshift__(self, x: i32) -> i32: ... + def __neg__(self) -> i32: ... + def __invert__(self) -> i32: ... + def __pos__(self) -> i32: ... + def __lt__(self, x: i32) -> bool: ... + def __le__(self, x: i32) -> bool: ... + def __ge__(self, x: i32) -> bool: ... + def __gt__(self, x: i32) -> bool: ... + +class i64: + def __init__(self, x: Union[_Int, str, bytes, SupportsInt], base: int = 10) -> None: ... + def __add__(self, x: i64) -> i64: ... + def __radd__(self, x: i64) -> i64: ... + def __sub__(self, x: i64) -> i64: ... + def __rsub__(self, x: i64) -> i64: ... + def __mul__(self, x: i64) -> i64: ... + def __rmul__(self, x: i64) -> i64: ... + def __floordiv__(self, x: i64) -> i64: ... + def __rfloordiv__(self, x: i64) -> i64: ... + def __mod__(self, x: i64) -> i64: ... + def __rmod__(self, x: i64) -> i64: ... + def __and__(self, x: i64) -> i64: ... + def __rand__(self, x: i64) -> i64: ... + def __or__(self, x: i64) -> i64: ... + def __ror__(self, x: i64) -> i64: ... + def __xor__(self, x: i64) -> i64: ... + def __rxor__(self, x: i64) -> i64: ... + def __lshift__(self, x: i64) -> i64: ... + def __rlshift__(self, x: i64) -> i64: ... + def __rshift__(self, x: i64) -> i64: ... + def __rrshift__(self, x: i64) -> i64: ... + def __neg__(self) -> i64: ... + def __invert__(self) -> i64: ... + def __pos__(self) -> i64: ... + def __lt__(self, x: i64) -> bool: ... + def __le__(self, x: i64) -> bool: ... + def __ge__(self, x: i64) -> bool: ... + def __gt__(self, x: i64) -> bool: ... diff --git a/test-data/unit/lib-stub/numbers.pyi b/test-data/unit/lib-stub/numbers.pyi new file mode 100644 index 000000000000..fad173c9a8b6 --- /dev/null +++ b/test-data/unit/lib-stub/numbers.pyi @@ -0,0 +1,10 @@ +# Test fixture for numbers +# +# The numbers module isn't properly supported, but we want to test that mypy +# can tell that it doesn't work as expected. + +class Number: pass +class Complex: pass +class Real: pass +class Rational: pass +class Integral: pass diff --git a/test-data/unit/lib-stub/typing.pyi b/test-data/unit/lib-stub/typing.pyi index a306b70f74d7..5f458ca687c0 100644 --- a/test-data/unit/lib-stub/typing.pyi +++ b/test-data/unit/lib-stub/typing.pyi @@ -48,9 +48,12 @@ class Generator(Iterator[T], Generic[T, U, V]): class Sequence(Iterable[T_co]): def __getitem__(self, n: Any) -> T_co: pass + def __len__(self) -> int: pass # Mapping type is oversimplified intentionally. -class Mapping(Iterable[T], Generic[T, T_co]): pass +class Mapping(Iterable[T], Generic[T, T_co]): + def keys(self) -> Iterable[T]: pass # Approximate return type + def __getitem__(self, key: T) -> T_co: pass class Awaitable(Protocol[T]): def __await__(self) -> Generator[Any, Any, T]: pass diff --git a/test-data/unit/lib-stub/typing_extensions.pyi b/test-data/unit/lib-stub/typing_extensions.pyi index 22b895971521..b5bfc1ab3f20 100644 --- a/test-data/unit/lib-stub/typing_extensions.pyi +++ b/test-data/unit/lib-stub/typing_extensions.pyi @@ -1,5 +1,5 @@ import typing -from typing import Any, Mapping, Iterator, NoReturn as NoReturn, Dict, Type +from typing import Any, Callable, Mapping, Iterable, Iterator, NoReturn as NoReturn, Dict, Tuple, Type, Union from typing import TYPE_CHECKING as TYPE_CHECKING from typing import NewType as NewType, overload as overload @@ -34,10 +34,19 @@ Concatenate: _SpecialForm TypeAlias: _SpecialForm TypeGuard: _SpecialForm +TypeIs: _SpecialForm Never: _SpecialForm TypeVarTuple: _SpecialForm Unpack: _SpecialForm +Required: _SpecialForm +NotRequired: _SpecialForm + +@final +class TypeAliasType: + def __init__( + self, name: str, value: Any, *, type_params: Tuple[Union[TypeVar, ParamSpec, TypeVarTuple], ...] = () + ) -> None: ... # Fallback type for all typed dicts (does not exist at runtime). class _TypedDict(Mapping[str, object]): @@ -50,13 +59,25 @@ class _TypedDict(Mapping[str, object]): # Mypy expects that 'default' has a type variable type. def pop(self, k: NoReturn, default: _T = ...) -> object: ... def update(self: _T, __m: _T) -> None: ... + def items(self) -> Iterable[Tuple[str, object]]: ... + def keys(self) -> Iterable[str]: ... + def values(self) -> Iterable[object]: ... if sys.version_info < (3, 0): def has_key(self, k: str) -> bool: ... def __delitem__(self, k: NoReturn) -> None: ... + # Stubtest's tests need the following items: + __required_keys__: frozenset[str] + __optional_keys__: frozenset[str] + __readonly_keys__: frozenset[str] + __mutable_keys__: frozenset[str] + __closed__: bool + __extra_items__: Any + __total__: bool def TypedDict(typename: str, fields: Dict[str, Type[_T]], *, total: Any = ...) -> Type[dict]: ... -def reveal_type(__obj: T) -> T: pass +def reveal_type(__obj: _T) -> _T: pass +def assert_type(__val: _T, __typ: Any) -> _T: pass def dataclass_transform( *, @@ -65,6 +86,9 @@ def dataclass_transform( kw_only_default: bool = ..., field_specifiers: tuple[type[Any] | Callable[..., Any], ...] = ..., **kwargs: Any, -) -> Callable[[T], T]: ... +) -> Callable[[_T], _T]: ... + +def override(__arg: _T) -> _T: ... +def deprecated(__msg: str) -> Callable[[_T], _T]: ... _FutureFeatureFixture = 0 diff --git a/test-data/unit/merge.test b/test-data/unit/merge.test index 144a095440f2..19b1839f86c0 100644 --- a/test-data/unit/merge.test +++ b/test-data/unit/merge.test @@ -39,7 +39,7 @@ MypyFile:1<1>( FuncDef:1<2>( f def () -> builtins.int<3> - Block:1<4>( + Block:2<4>( PassStmt:2<5>()))) ==> MypyFile:1<0>( @@ -50,7 +50,7 @@ MypyFile:1<1>( FuncDef:1<2>( f def () -> builtins.int<3> - Block:1<6>( + Block:2<6>( PassStmt:2<7>()))) [case testClass] @@ -77,7 +77,7 @@ MypyFile:1<1>( Var(self) Var(x)) def (self: target.A<4>, x: builtins.str<5>) -> builtins.int<6> - Block:2<7>( + Block:3<7>( PassStmt:3<8>())))) ==> MypyFile:1<0>( @@ -93,7 +93,7 @@ MypyFile:1<1>( Var(self) Var(x)) def (self: target.A<4>, x: builtins.int<6>) -> builtins.str<5> - Block:2<10>( + Block:3<10>( PassStmt:3<11>())))) [case testClass_typeinfo] @@ -149,7 +149,7 @@ MypyFile:1<1>( Args( Var(self)) def (self: target.A<4>) -> target.B<5> - Block:2<6>( + Block:3<6>( ReturnStmt:3<7>( CallExpr:3<8>( NameExpr(B [target.B<5>]) @@ -173,7 +173,7 @@ MypyFile:1<1>( Args( Var(self)) def (self: target.A<4>) -> target.B<5> - Block:3<14>( + Block:4<14>( ExpressionStmt:4<15>( IntExpr(1)) ReturnStmt:5<16>( @@ -204,7 +204,7 @@ MypyFile:1<1>( Args( Var(self)) def (self: target.A<4>) - Block:2<5>( + Block:3<5>( ExpressionStmt:3<6>( CallExpr:3<7>( MemberExpr:3<8>( @@ -224,7 +224,7 @@ MypyFile:1<1>( Args( Var(self)) def (self: target.A<4>) - Block:2<11>( + Block:3<11>( ExpressionStmt:3<12>( CallExpr:3<13>( MemberExpr:3<14>( @@ -257,7 +257,7 @@ MypyFile:1<1>( Args( Var(self)) def (self: target.A<4>) - Block:2<5>( + Block:3<5>( AssignmentStmt:3<6>( MemberExpr:3<8>( NameExpr(self [l<9>]) @@ -280,7 +280,7 @@ MypyFile:1<1>( Args( Var(self)) def (self: target.A<4>) - Block:2<13>( + Block:3<13>( AssignmentStmt:3<14>( MemberExpr:3<15>( NameExpr(self [l<16>]) @@ -1086,7 +1086,7 @@ a: A [file target.py.next] from _x import A a: A -[file _x.pyi] +[fixture _x.pyi] from typing import Generic, TypeVar, overload T = TypeVar('T') diff --git a/test-data/unit/outputjson.test b/test-data/unit/outputjson.test new file mode 100644 index 000000000000..43649b7b781d --- /dev/null +++ b/test-data/unit/outputjson.test @@ -0,0 +1,44 @@ +-- Test cases for `--output=json`. +-- These cannot be run by the usual unit test runner because of the backslashes +-- in the output, which get normalized to forward slashes by the test suite on +-- Windows. + +[case testOutputJsonNoIssues] +# flags: --output=json +def foo() -> None: + pass + +foo() +[out] + +[case testOutputJsonSimple] +# flags: --output=json +def foo() -> None: + pass + +foo(1) +[out] +{"file": "main", "line": 5, "column": 0, "message": "Too many arguments for \"foo\"", "hint": null, "code": "call-arg", "severity": "error"} + +[case testOutputJsonWithHint] +# flags: --output=json +from typing import Optional, overload + +@overload +def foo() -> None: ... +@overload +def foo(x: int) -> None: ... + +def foo(x: Optional[int] = None) -> None: + ... + +reveal_type(foo) + +foo('42') + +def bar() -> None: ... +bar('42') +[out] +{"file": "main", "line": 12, "column": 12, "message": "Revealed type is \"Overload(def (), def (x: builtins.int))\"", "hint": null, "code": "misc", "severity": "note"} +{"file": "main", "line": 14, "column": 0, "message": "No overload variant of \"foo\" matches argument type \"str\"", "hint": "Possible overload variants:\n def foo() -> None\n def foo(x: int) -> None", "code": "call-overload", "severity": "error"} +{"file": "main", "line": 17, "column": 0, "message": "Too many arguments for \"bar\"", "hint": null, "code": "call-arg", "severity": "error"} diff --git a/test-data/unit/parse-errors.test b/test-data/unit/parse-errors.test index 9123ce0cf509..c6b1c00a6169 100644 --- a/test-data/unit/parse-errors.test +++ b/test-data/unit/parse-errors.test @@ -113,116 +113,116 @@ file:1: error: invalid syntax 0 x = 0 # type: A A [out] -file:2: error: syntax error in type comment "A A" +file:2: error: Syntax error in type comment "A A" [case testInvalidTypeComment2] 0 x = 0 # type: A[ [out] -file:2: error: syntax error in type comment "A[" +file:2: error: Syntax error in type comment "A[" [case testInvalidTypeComment3] 0 x = 0 # type: [out] -file:2: error: syntax error in type comment "" +file:2: error: Syntax error in type comment "" [case testInvalidTypeComment4] 0 x = 0 # type: * [out] -file:2: error: syntax error in type comment "*" +file:2: error: Syntax error in type comment "*" [case testInvalidTypeComment5] 0 x = 0 # type:# some comment [out] -file:2: error: syntax error in type comment "" +file:2: error: Syntax error in type comment "" [case testInvalidTypeComment6] 0 x = 0 # type: *# comment #6 [out] -file:2: error: syntax error in type comment "*" +file:2: error: Syntax error in type comment "*" [case testInvalidTypeComment7] 0 x = 0 # type: A B #comment #7 [out] -file:2: error: syntax error in type comment "A B" +file:2: error: Syntax error in type comment "A B" [case testInvalidSignatureInComment1] def f(): # type: x pass [out] -file:1: error: syntax error in type comment "x" +file:1: error: Syntax error in type comment "x" file:1: note: Suggestion: wrap argument types in parentheses [case testInvalidSignatureInComment2] def f(): # type: pass [out] -file:1: error: syntax error in type comment "" +file:1: error: Syntax error in type comment "" [case testInvalidSignatureInComment3] def f(): # type: ( pass [out] -file:1: error: syntax error in type comment "(" +file:1: error: Syntax error in type comment "(" [case testInvalidSignatureInComment4] def f(): # type: (. pass [out] -file:1: error: syntax error in type comment "(." +file:1: error: Syntax error in type comment "(." [case testInvalidSignatureInComment5] def f(): # type: (x pass [out] -file:1: error: syntax error in type comment "(x" +file:1: error: Syntax error in type comment "(x" [case testInvalidSignatureInComment6] def f(): # type: (x) pass [out] -file:1: error: syntax error in type comment "(x)" +file:1: error: Syntax error in type comment "(x)" [case testInvalidSignatureInComment7] def f(): # type: (x) - pass [out] -file:1: error: syntax error in type comment "(x) -" +file:1: error: Syntax error in type comment "(x) -" [case testInvalidSignatureInComment8] def f(): # type: (x) -> pass [out] -file:1: error: syntax error in type comment "(x) ->" +file:1: error: Syntax error in type comment "(x) ->" [case testInvalidSignatureInComment9] def f(): # type: (x) -> . pass [out] -file:1: error: syntax error in type comment "(x) -> ." +file:1: error: Syntax error in type comment "(x) -> ." [case testInvalidSignatureInComment10] def f(): # type: (x) -> x x pass [out] -file:1: error: syntax error in type comment "(x) -> x x" +file:1: error: Syntax error in type comment "(x) -> x x" [case testInvalidSignatureInComment11] def f(): # type: # abc comment pass [out] -file:1: error: syntax error in type comment "" +file:1: error: Syntax error in type comment "" [case testInvalidSignatureInComment12] def f(): # type: (x) -> x x # comment #2 pass [out] -file:1: error: syntax error in type comment "(x) -> x x" +file:1: error: Syntax error in type comment "(x) -> x x" [case testDuplicateSignatures1] @@ -269,21 +269,14 @@ def g(*x, **y): # type: (*X, *Y) -> Z pass [out] file:1: error: Inconsistent use of "*" in function signature -file:3: error: syntax error in type comment +file:3: error: Syntax error in type comment file:3: error: Inconsistent use of "*" in function signature file:3: error: Inconsistent use of "**" in function signature -[case testPrintStatementInPython35] -# flags: --python-version 3.5 +[case testPrintStatementInPython3] print 1 [out] -file:2: error: Missing parentheses in call to 'print' - -[case testPrintStatementInPython37] -# flags: --python-version 3.7 -print 1 -[out] -file:2: error: Missing parentheses in call to 'print'. Did you mean print(1)? +file:1: error: Missing parentheses in call to 'print'. Did you mean print(1)? [case testInvalidConditionInConditionalExpression] 1 if 2, 3 else 4 diff --git a/test-data/unit/parse-python312.test b/test-data/unit/parse-python312.test new file mode 100644 index 000000000000..28204ccd647b --- /dev/null +++ b/test-data/unit/parse-python312.test @@ -0,0 +1,87 @@ +[case testPEP695TypeAlias] +# mypy: enable-incomplete-feature=NewGenericSyntax +type A[T] = C[T] +[out] +MypyFile:1( + TypeAliasStmt:2( + NameExpr(A) + TypeParam( + T) + IndexExpr:2( + NameExpr(C) + NameExpr(T)))) + +[case testPEP695GenericFunction] +# mypy: enable-incomplete-feature=NewGenericSyntax + +def f[T](): pass +def g[T: str](): pass +def h[T: (int, str)](): pass +[out] +MypyFile:1( + FuncDef:3( + f + TypeParam( + T) + Block:3( + PassStmt:3())) + FuncDef:4( + g + TypeParam( + T + str?) + Block:4( + PassStmt:4())) + FuncDef:5( + h + TypeParam( + T + Values( + int? + str?)) + Block:5( + PassStmt:5()))) + +[case testPEP695ParamSpec] +# mypy: enable-incomplete-feature=NewGenericSyntax + +def f[**P](): pass +class C[T: int, **P]: pass +[out] +MypyFile:1( + FuncDef:3( + f + TypeParam( + **P) + Block:3( + PassStmt:3())) + ClassDef:4( + C + TypeParam( + T + int?) + TypeParam( + **P) + PassStmt:4())) + +[case testPEP695TypeVarTuple] +# mypy: enable-incomplete-feature=NewGenericSyntax + +def f[*Ts](): pass +class C[T: int, *Ts]: pass +[out] +MypyFile:1( + FuncDef:3( + f + TypeParam( + *Ts) + Block:3( + PassStmt:3())) + ClassDef:4( + C + TypeParam( + T + int?) + TypeParam( + *Ts) + PassStmt:4())) diff --git a/test-data/unit/parse.test b/test-data/unit/parse.test index ff892ce0ce05..10ceaa947fd4 100644 --- a/test-data/unit/parse.test +++ b/test-data/unit/parse.test @@ -95,7 +95,6 @@ MypyFile:1( StrExpr(x\n\')) ExpressionStmt:2( StrExpr(x\n\"))) ---" fix syntax highlight [case testBytes] b'foo' @@ -128,7 +127,6 @@ MypyFile:1( MypyFile:1( ExpressionStmt:1( StrExpr('))) ---' [case testOctalEscapes] '\0\1\177\1234' @@ -203,7 +201,7 @@ def main(): MypyFile:1( FuncDef:1( main - Block:1( + Block:2( ExpressionStmt:2( IntExpr(1))))) @@ -214,7 +212,7 @@ def f(): MypyFile:1( FuncDef:1( f - Block:1( + Block:2( PassStmt:2()))) [case testIf] @@ -288,7 +286,7 @@ while 1: MypyFile:1( WhileStmt:1( IntExpr(1) - Block:1( + Block:2( PassStmt:2()))) [case testReturn] @@ -298,7 +296,7 @@ def f(): MypyFile:1( FuncDef:1( f - Block:1( + Block:2( ReturnStmt:2( IntExpr(1))))) @@ -310,7 +308,7 @@ def f(): MypyFile:1( FuncDef:1( f - Block:1( + Block:2( ReturnStmt:2()))) [case testBreak] @@ -320,7 +318,7 @@ while 1: MypyFile:1( WhileStmt:1( IntExpr(1) - Block:1( + Block:2( BreakStmt:2()))) [case testLargeBlock] @@ -340,7 +338,7 @@ MypyFile:1( IntExpr(1)) WhileStmt:3( IntExpr(2) - Block:3( + Block:4( PassStmt:4())) AssignmentStmt:5( NameExpr(y) @@ -358,7 +356,7 @@ MypyFile:1( f Args( Var(self)) - Block:2( + Block:3( PassStmt:3())))) [case testGlobalVarWithType] @@ -384,7 +382,7 @@ def f(): MypyFile:1( FuncDef:1( f - Block:1( + Block:2( AssignmentStmt:2( NameExpr(x) IntExpr(0) @@ -413,7 +411,7 @@ MypyFile:1( Args( Var(y)) def (y: str?) -> int? - Block:1( + Block:2( ReturnStmt:2())) ClassDef:3( A @@ -424,14 +422,14 @@ MypyFile:1( Var(a) Var(b)) def (self: Any, a: int?, b: Any?) -> x? - Block:4( + Block:5( PassStmt:5())) FuncDef:6( g Args( Var(self)) def (self: Any) -> Any? - Block:6( + Block:7( PassStmt:7())))) [case testFuncWithNoneReturn] @@ -442,7 +440,7 @@ MypyFile:1( FuncDef:1( f def () -> None? - Block:1( + Block:2( PassStmt:2()))) [case testVarDefWithGenericType] @@ -469,7 +467,7 @@ MypyFile:1( Args( Var(y)) def (y: t?[Any?, x?]) -> a?[b?[c?], d?] - Block:1( + Block:2( PassStmt:2()))) [case testParsingExpressionsWithLessAndGreaterThan] @@ -589,7 +587,7 @@ MypyFile:1( __init__ Args( Var(self)) - Block:2( + Block:3( AssignmentStmt:3( MemberExpr:3( NameExpr(self) @@ -785,7 +783,7 @@ MypyFile:1( ForStmt:1( NameExpr(x) NameExpr(y) - Block:1( + Block:2( PassStmt:2())) ForStmt:3( TupleExpr:3( @@ -794,7 +792,7 @@ MypyFile:1( NameExpr(y) NameExpr(w))) NameExpr(z) - Block:3( + Block:4( ExpressionStmt:4( IntExpr(1)))) ForStmt:5( @@ -804,7 +802,7 @@ MypyFile:1( NameExpr(y) NameExpr(w))) NameExpr(z) - Block:5( + Block:6( ExpressionStmt:6( IntExpr(1))))) @@ -818,7 +816,7 @@ MypyFile:1( x) FuncDef:2( f - Block:2( + Block:3( GlobalDecl:3( x y)))) @@ -831,10 +829,10 @@ def f(): MypyFile:1( FuncDef:1( f - Block:1( + Block:2( FuncDef:2( g - Block:2( + Block:3( NonlocalDecl:3( x y)))))) @@ -854,9 +852,9 @@ except: [out] MypyFile:1( TryStmt:1( - Block:1( + Block:2( PassStmt:2()) - Block:3( + Block:4( RaiseStmt:4()))) [case testRaiseFrom] @@ -1051,7 +1049,7 @@ MypyFile:1( Import:2(x) FuncDef:3( f - Block:3( + Block:4( ImportFrom:4(x, [y]) ImportAll:5(z)))) @@ -1074,7 +1072,7 @@ MypyFile:1( default( Var(x) IntExpr(1))) - Block:1( + Block:2( PassStmt:2())) FuncDef:3( g @@ -1091,7 +1089,7 @@ MypyFile:1( TupleExpr:3( IntExpr(1) IntExpr(2)))) - Block:3( + Block:4( PassStmt:4()))) [case testTryFinally] @@ -1102,7 +1100,7 @@ finally: [out] MypyFile:1( TryStmt:1( - Block:1( + Block:2( ExpressionStmt:2( IntExpr(1))) Finally( @@ -1117,11 +1115,11 @@ except x: [out] MypyFile:1( TryStmt:1( - Block:1( + Block:2( ExpressionStmt:2( IntExpr(1))) NameExpr(x) - Block:3( + Block:4( ExpressionStmt:4( IntExpr(2))))) @@ -1135,18 +1133,18 @@ except x.y: [out] MypyFile:1( TryStmt:1( - Block:1( + Block:2( ExpressionStmt:2( IntExpr(1))) NameExpr(x) NameExpr(y) - Block:3( + Block:4( ExpressionStmt:4( IntExpr(2))) MemberExpr:5( NameExpr(x) y) - Block:5( + Block:6( ExpressionStmt:6( IntExpr(3))))) @@ -1300,7 +1298,7 @@ def f(): MypyFile:1( FuncDef:1( f - Block:1( + Block:2( ExpressionStmt:2( YieldExpr:2( OpExpr:2( @@ -1315,7 +1313,7 @@ def f(): MypyFile:1( FuncDef:1( f - Block:1( + Block:2( ExpressionStmt:2( YieldFromExpr:2( CallExpr:2( @@ -1329,7 +1327,7 @@ def f(): MypyFile:1( FuncDef:1( f - Block:1( + Block:2( AssignmentStmt:2( NameExpr(a) YieldFromExpr:2( @@ -1389,7 +1387,7 @@ MypyFile:1( f Args( Var(x)) - Block:1( + Block:2( PassStmt:2()))) [case testLambda] @@ -1460,7 +1458,7 @@ MypyFile:1( NameExpr(i) NameExpr(j)) NameExpr(x) - Block:1( + Block:2( PassStmt:2()))) [case testForAndTrailingCommaAfterIndexVar] @@ -1472,7 +1470,7 @@ MypyFile:1( TupleExpr:1( NameExpr(i)) NameExpr(x) - Block:1( + Block:2( PassStmt:2()))) [case testListComprehensionAndTrailingCommaAfterIndexVar] @@ -1498,7 +1496,7 @@ MypyFile:1( NameExpr(i) NameExpr(j)) NameExpr(x) - Block:1( + Block:2( PassStmt:2()))) [case testGeneratorWithCondition] @@ -1630,7 +1628,7 @@ MypyFile:1( StrExpr(foo)))) Target( NameExpr(f)) - Block:1( + Block:2( PassStmt:2()))) [case testWithStatementWithoutTarget] @@ -1641,7 +1639,7 @@ MypyFile:1( WithStmt:1( Expr( NameExpr(foo)) - Block:1( + Block:2( PassStmt:2()))) [case testHexOctBinLiterals] @@ -1673,7 +1671,7 @@ while 1: MypyFile:1( WhileStmt:1( IntExpr(1) - Block:1( + Block:2( ContinueStmt:2()))) [case testStrLiteralConcatenate] @@ -1702,19 +1700,19 @@ except: [out] MypyFile:1( TryStmt:1( - Block:1( + Block:2( ExpressionStmt:2( IntExpr(1))) - Block:3( + Block:4( PassStmt:4())) TryStmt:5( - Block:5( + Block:6( ExpressionStmt:6( IntExpr(1))) NameExpr(x) - Block:7( + Block:8( PassStmt:8()) - Block:9( + Block:10( ExpressionStmt:10( IntExpr(2))))) @@ -1728,10 +1726,10 @@ else: [out] MypyFile:1( TryStmt:1( - Block:1( + Block:2( PassStmt:2()) NameExpr(x) - Block:3( + Block:4( ExpressionStmt:4( IntExpr(1))) Else( @@ -1748,19 +1746,19 @@ except (a, b, c) as e: [out] MypyFile:1( TryStmt:1( - Block:1( + Block:2( PassStmt:2()) TupleExpr:3( NameExpr(x) NameExpr(y)) - Block:3( + Block:4( PassStmt:4()) TupleExpr:5( NameExpr(a) NameExpr(b) NameExpr(c)) NameExpr(e) - Block:5( + Block:6( PassStmt:6()))) [case testNestedFunctions] @@ -1774,19 +1772,19 @@ def h() -> int: MypyFile:1( FuncDef:1( f - Block:1( + Block:2( FuncDef:2( g - Block:2( + Block:3( PassStmt:3())))) FuncDef:4( h def () -> int? - Block:4( + Block:5( FuncDef:5( g def () -> int? - Block:5( + Block:6( PassStmt:6()))))) [case testStatementsAndDocStringsInClassBody] @@ -1808,7 +1806,7 @@ MypyFile:1( f Args( Var(self)) - Block:4( + Block:5( PassStmt:5())))) [case testSingleLineClass] @@ -1830,7 +1828,7 @@ MypyFile:1( NameExpr(property) FuncDef:2( f - Block:2( + Block:3( PassStmt:3())))) [case testComplexDecorator] @@ -1851,7 +1849,7 @@ MypyFile:1( FuncDef:3( f def () -> int? - Block:3( + Block:4( PassStmt:4())))) [case testKeywordArgInCall] @@ -2036,7 +2034,7 @@ def f(): MypyFile:1( FuncDef:1( f - Block:1( + Block:2( ExpressionStmt:2( YieldExpr:2())))) @@ -2122,7 +2120,7 @@ MypyFile:1( ForStmt:1( NameExpr(x) NameExpr(y) - Block:1( + Block:2( PassStmt:2()) Else( ExpressionStmt:4( @@ -2137,7 +2135,7 @@ else: MypyFile:1( WhileStmt:1( NameExpr(x) - Block:1( + Block:2( PassStmt:2()) Else( ExpressionStmt:4( @@ -2159,7 +2157,7 @@ MypyFile:1( NameExpr(a)) Target( NameExpr(b)) - Block:1( + Block:2( PassStmt:2())) WithStmt:3( Expr( @@ -2170,7 +2168,7 @@ MypyFile:1( CallExpr:3( NameExpr(y) Args())) - Block:3( + Block:4( PassStmt:4()))) [case testOperatorAssignment] @@ -2264,10 +2262,10 @@ finally: [out] MypyFile:1( TryStmt:1( - Block:1( + Block:2( PassStmt:2()) NameExpr(x) - Block:3( + Block:4( ExpressionStmt:4( NameExpr(x))) Finally( @@ -2642,7 +2640,7 @@ def f(): MypyFile:1( FuncDef:1( f - Block:1( + Block:2( OverloadedFuncDef:2( Decorator:2( Var(g) @@ -2670,14 +2668,14 @@ MypyFile:1( FuncDef:1( f def () -> A? - Block:1( + Block:2( PassStmt:2())) FuncDef:3( g Args( Var(x)) def (x: A?) -> B? - Block:3( + Block:4( PassStmt:4()))) [case testCommentMethodAnnotation] @@ -2695,7 +2693,7 @@ MypyFile:1( Args( Var(self)) def (self: Any) -> A? - Block:2( + Block:3( PassStmt:3())) FuncDef:4( g @@ -2703,7 +2701,7 @@ MypyFile:1( Var(xself) Var(x)) def (xself: Any, x: A?) -> B? - Block:4( + Block:5( PassStmt:5())))) [case testCommentMethodAnnotationAndNestedFunction] @@ -2720,13 +2718,13 @@ MypyFile:1( Args( Var(self)) def (self: Any) -> A? - Block:2( + Block:3( FuncDef:3( g Args( Var(x)) def (x: A?) -> B? - Block:3( + Block:4( PassStmt:4())))))) [case testCommentFunctionAnnotationOnSeparateLine] @@ -2740,7 +2738,7 @@ MypyFile:1( Args( Var(x)) def (x: X?) -> Y? - Block:1( + Block:3( PassStmt:3()))) [case testCommentFunctionAnnotationOnSeparateLine2] @@ -2756,7 +2754,7 @@ MypyFile:1( Args( Var(x)) def (x: X?) -> Y? - Block:1( + Block:5( PassStmt:5()))) [case testCommentFunctionAnnotationAndVarArg] @@ -2771,7 +2769,7 @@ MypyFile:1( def (x: X?, *y: Y?) -> Z? VarArg( Var(y)) - Block:1( + Block:2( PassStmt:2()))) [case testCommentFunctionAnnotationAndAllVarArgs] @@ -2788,7 +2786,7 @@ MypyFile:1( Var(y)) DictVarArg( Var(z)) - Block:1( + Block:2( PassStmt:2()))) [case testClassDecorator] @@ -2826,11 +2824,11 @@ def y(): MypyFile:1( FuncDef:1( x - Block:1( + Block:2( PassStmt:2())) FuncDef:4( y - Block:4( + Block:5( PassStmt:5()))) [case testEmptySuperClass] @@ -2907,7 +2905,7 @@ MypyFile:1( StarExpr:1( NameExpr(a)) NameExpr(b) - Block:1( + Block:2( PassStmt:2())) ForStmt:4( TupleExpr:4( @@ -2915,7 +2913,7 @@ MypyFile:1( StarExpr:4( NameExpr(b))) NameExpr(c) - Block:4( + Block:5( PassStmt:5())) ForStmt:7( TupleExpr:7( @@ -2923,7 +2921,7 @@ MypyFile:1( NameExpr(a)) NameExpr(b)) NameExpr(c) - Block:7( + Block:8( PassStmt:8()))) [case testStarExprInGeneratorExpr] @@ -3032,7 +3030,7 @@ while 2: MypyFile:1( WhileStmt:1( IntExpr(2) - Block:1( + Block:2( IfStmt:2( If( IntExpr(1)) @@ -3075,7 +3073,7 @@ while 2: MypyFile:1( WhileStmt:1( IntExpr(2) - Block:1( + Block:2( IfStmt:2( If( IntExpr(1)) @@ -3300,7 +3298,7 @@ def f(): MypyFile:1( FuncDef:1( f - Block:1( + Block:2( AssignmentStmt:2( NameExpr(x) YieldExpr:2( @@ -3341,7 +3339,7 @@ def f(): MypyFile:1( FuncDef:1( f - Block:1( + Block:2( ExpressionStmt:2( YieldExpr:2())))) @@ -3484,3 +3482,358 @@ MypyFile:1( NameExpr(y) NameExpr(y)) StrExpr())))))))))))) + +[case testStripFunctionBodiesIfIgnoringErrors] +# mypy: ignore-errors=True +def f(self): + self.x = 1 # Cannot define an attribute + return 1 +[out] +MypyFile:1( + FuncDef:2( + f + Args( + Var(self)) + Block:3())) + +[case testStripMethodBodiesIfIgnoringErrors] +# mypy: ignore-errors=True +class C: + def f(self): + x = self.x + for x in y: + pass + with a as y: + pass + while self.foo(): + self.bah() + a[self.x] = 1 +[out] +MypyFile:1( + ClassDef:2( + C + FuncDef:3( + f + Args( + Var(self)) + Block:4()))) + +[case testDoNotStripModuleTopLevelOrClassBody] +# mypy: ignore-errors=True +f() +class C: + x = 5 +[out] +MypyFile:1( + ExpressionStmt:2( + CallExpr:2( + NameExpr(f) + Args())) + ClassDef:3( + C + AssignmentStmt:4( + NameExpr(x) + IntExpr(5)))) + +[case testDoNotStripMethodThatAssignsToAttribute] +# mypy: ignore-errors=True +class C: + def m1(self): + self.x = 0 + def m2(self): + a, self.y = 0 +[out] +MypyFile:1( + ClassDef:2( + C + FuncDef:3( + m1 + Args( + Var(self)) + Block:4( + AssignmentStmt:4( + MemberExpr:4( + NameExpr(self) + x) + IntExpr(0)))) + FuncDef:5( + m2 + Args( + Var(self)) + Block:6( + AssignmentStmt:6( + TupleExpr:6( + NameExpr(a) + MemberExpr:6( + NameExpr(self) + y)) + IntExpr(0)))))) + +[case testDoNotStripMethodThatAssignsToAttributeWithinStatement] +# mypy: ignore-errors=True +class C: + def m1(self): + for x in y: + self.x = 0 + def m2(self): + with x: + self.y = 0 + def m3(self): + if x: + self.y = 0 + else: + x = 4 +[out] +MypyFile:1( + ClassDef:2( + C + FuncDef:3( + m1 + Args( + Var(self)) + Block:4( + ForStmt:4( + NameExpr(x) + NameExpr(y) + Block:5( + AssignmentStmt:5( + MemberExpr:5( + NameExpr(self) + x) + IntExpr(0)))))) + FuncDef:6( + m2 + Args( + Var(self)) + Block:7( + WithStmt:7( + Expr( + NameExpr(x)) + Block:8( + AssignmentStmt:8( + MemberExpr:8( + NameExpr(self) + y) + IntExpr(0)))))) + FuncDef:9( + m3 + Args( + Var(self)) + Block:10( + IfStmt:10( + If( + NameExpr(x)) + Then( + AssignmentStmt:11( + MemberExpr:11( + NameExpr(self) + y) + IntExpr(0))) + Else( + AssignmentStmt:13( + NameExpr(x) + IntExpr(4)))))))) + +[case testDoNotStripMethodThatDefinesAttributeWithoutAssignment] +# mypy: ignore-errors=True +class C: + def m1(self): + with y as self.x: + pass + def m2(self): + for self.y in x: + pass +[out] +MypyFile:1( + ClassDef:2( + C + FuncDef:3( + m1 + Args( + Var(self)) + Block:4( + WithStmt:4( + Expr( + NameExpr(y)) + Target( + MemberExpr:4( + NameExpr(self) + x)) + Block:5( + PassStmt:5())))) + FuncDef:6( + m2 + Args( + Var(self)) + Block:7( + ForStmt:7( + MemberExpr:7( + NameExpr(self) + y) + NameExpr(x) + Block:8( + PassStmt:8())))))) + +[case testStripDecoratedFunctionOrMethod] +# mypy: ignore-errors=True +@deco +def f(): + x = 0 + +class C: + @deco + def m1(self): + x = 0 + + @deco + def m2(self): + self.x = 0 +[out] +MypyFile:1( + Decorator:2( + Var(f) + NameExpr(deco) + FuncDef:3( + f + Block:4())) + ClassDef:6( + C + Decorator:7( + Var(m1) + NameExpr(deco) + FuncDef:8( + m1 + Args( + Var(self)) + Block:9())) + Decorator:11( + Var(m2) + NameExpr(deco) + FuncDef:12( + m2 + Args( + Var(self)) + Block:13( + AssignmentStmt:13( + MemberExpr:13( + NameExpr(self) + x) + IntExpr(0))))))) + +[case testStripOverloadedMethod] +# mypy: ignore-errors=True +class C: + @overload + def m1(self, x: int) -> None: ... + @overload + def m1(self, x: str) -> None: ... + def m1(self, x): + x = 0 + + @overload + def m2(self, x: int) -> None: ... + @overload + def m2(self, x: str) -> None: ... + def m2(self, x): + self.x = 0 +[out] +MypyFile:1( + ClassDef:2( + C + OverloadedFuncDef:3( + Decorator:3( + Var(m1) + NameExpr(overload) + FuncDef:4( + m1 + Args( + Var(self) + Var(x)) + def (self: Any, x: int?) -> None? + Block:4( + ExpressionStmt:4( + Ellipsis)))) + Decorator:5( + Var(m1) + NameExpr(overload) + FuncDef:6( + m1 + Args( + Var(self) + Var(x)) + def (self: Any, x: str?) -> None? + Block:6( + ExpressionStmt:6( + Ellipsis)))) + FuncDef:7( + m1 + Args( + Var(self) + Var(x)) + Block:8())) + OverloadedFuncDef:10( + Decorator:10( + Var(m2) + NameExpr(overload) + FuncDef:11( + m2 + Args( + Var(self) + Var(x)) + def (self: Any, x: int?) -> None? + Block:11( + ExpressionStmt:11( + Ellipsis)))) + Decorator:12( + Var(m2) + NameExpr(overload) + FuncDef:13( + m2 + Args( + Var(self) + Var(x)) + def (self: Any, x: str?) -> None? + Block:13( + ExpressionStmt:13( + Ellipsis)))) + FuncDef:14( + m2 + Args( + Var(self) + Var(x)) + Block:15( + AssignmentStmt:15( + MemberExpr:15( + NameExpr(self) + x) + IntExpr(0))))))) + +[case testStripMethodInNestedClass] +# mypy: ignore-errors=True +class C: + class D: + def m1(self): + self.x = 1 + def m2(self): + return self.x +[out] +MypyFile:1( + ClassDef:2( + C + ClassDef:3( + D + FuncDef:4( + m1 + Args( + Var(self)) + Block:5( + AssignmentStmt:5( + MemberExpr:5( + NameExpr(self) + x) + IntExpr(1)))) + FuncDef:6( + m2 + Args( + Var(self)) + Block:7())))) diff --git a/test-data/unit/pep561.test b/test-data/unit/pep561.test index 7167d97487c1..9969c2894c36 100644 --- a/test-data/unit/pep561.test +++ b/test-data/unit/pep561.test @@ -72,15 +72,6 @@ reveal_type(a) [out] testStubPrecedence.py:5: note: Revealed type is "builtins.list[builtins.str]" -[case testTypedPkgSimpleEgg] -# pkgs: typedpkg; no-pip -from typedpkg.sample import ex -from typedpkg import dne -a = ex(['']) -reveal_type(a) -[out] -testTypedPkgSimpleEgg.py:5: note: Revealed type is "builtins.tuple[builtins.str, ...]" - [case testTypedPkgSimpleEditable] # pkgs: typedpkg; editable from typedpkg.sample import ex @@ -90,15 +81,6 @@ reveal_type(a) [out] testTypedPkgSimpleEditable.py:5: note: Revealed type is "builtins.tuple[builtins.str, ...]" -[case testTypedPkgSimpleEditableEgg] -# pkgs: typedpkg; editable; no-pip -from typedpkg.sample import ex -from typedpkg import dne -a = ex(['']) -reveal_type(a) -[out] -testTypedPkgSimpleEditableEgg.py:5: note: Revealed type is "builtins.tuple[builtins.str, ...]" - [case testTypedPkgNamespaceImportFrom] # pkgs: typedpkg, typedpkg_ns_a from typedpkg.pkg.aaa import af @@ -185,6 +167,7 @@ a.bf(False) b.bf(False) a.bf(1) b.bf(1) +import typedpkg_ns.whatever as c # type: ignore[import-untyped] [out] testNamespacePkgWStubs.py:4: error: Skipping analyzing "typedpkg_ns.b.bbb": module is installed, but missing library stubs or py.typed marker testNamespacePkgWStubs.py:4: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports @@ -222,4 +205,4 @@ from typedpkg_ns.a.bbb import bf -- dummy should trigger a second iteration [file dummy.py.2] [out] -[out2] \ No newline at end of file +[out2] diff --git a/test-data/unit/plugins/add_classmethod.py b/test-data/unit/plugins/add_classmethod.py index 5aacc69a8f01..9bc2c4e079dd 100644 --- a/test-data/unit/plugins/add_classmethod.py +++ b/test-data/unit/plugins/add_classmethod.py @@ -1,4 +1,6 @@ -from typing import Callable, Optional +from __future__ import annotations + +from typing import Callable from mypy.nodes import ARG_POS, Argument, Var from mypy.plugin import ClassDefContext, Plugin @@ -7,7 +9,7 @@ class ClassMethodPlugin(Plugin): - def get_base_class_hook(self, fullname: str) -> Optional[Callable[[ClassDefContext], None]]: + def get_base_class_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None: if "BaseAddMethod" in fullname: return add_extra_methods_hook return None @@ -24,5 +26,5 @@ def add_extra_methods_hook(ctx: ClassDefContext) -> None: ) -def plugin(version): +def plugin(version: str) -> type[ClassMethodPlugin]: return ClassMethodPlugin diff --git a/test-data/unit/plugins/add_overloaded_method.py b/test-data/unit/plugins/add_overloaded_method.py new file mode 100644 index 000000000000..efda848f790c --- /dev/null +++ b/test-data/unit/plugins/add_overloaded_method.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +from typing import Callable + +from mypy.nodes import ARG_POS, Argument, Var +from mypy.plugin import ClassDefContext, Plugin +from mypy.plugins.common import MethodSpec, add_overloaded_method_to_class + + +class OverloadedMethodPlugin(Plugin): + def get_base_class_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None: + if "AddOverloadedMethod" in fullname: + return add_overloaded_method_hook + return None + + +def add_overloaded_method_hook(ctx: ClassDefContext) -> None: + add_overloaded_method_to_class(ctx.api, ctx.cls, "method", _generate_method_specs(ctx)) + add_overloaded_method_to_class( + ctx.api, ctx.cls, "clsmethod", _generate_method_specs(ctx), is_classmethod=True + ) + add_overloaded_method_to_class( + ctx.api, ctx.cls, "stmethod", _generate_method_specs(ctx), is_staticmethod=True + ) + + +def _generate_method_specs(ctx: ClassDefContext) -> list[MethodSpec]: + return [ + MethodSpec( + args=[Argument(Var("arg"), ctx.api.named_type("builtins.int"), None, ARG_POS)], + return_type=ctx.api.named_type("builtins.str"), + ), + MethodSpec( + args=[Argument(Var("arg"), ctx.api.named_type("builtins.str"), None, ARG_POS)], + return_type=ctx.api.named_type("builtins.int"), + ), + ] + + +def plugin(version: str) -> type[OverloadedMethodPlugin]: + return OverloadedMethodPlugin diff --git a/test-data/unit/plugins/arg_kinds.py b/test-data/unit/plugins/arg_kinds.py index 5392e64c4f11..388a3c738b62 100644 --- a/test-data/unit/plugins/arg_kinds.py +++ b/test-data/unit/plugins/arg_kinds.py @@ -1,18 +1,19 @@ -from typing import Optional, Callable -from mypy.plugin import Plugin, MethodContext, FunctionContext +from __future__ import annotations + +from typing import Callable + +from mypy.plugin import FunctionContext, MethodContext, Plugin from mypy.types import Type class ArgKindsPlugin(Plugin): - def get_function_hook(self, fullname: str - ) -> Optional[Callable[[FunctionContext], Type]]: - if 'func' in fullname: + def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None: + if "func" in fullname: return extract_arg_kinds_from_function return None - def get_method_hook(self, fullname: str - ) -> Optional[Callable[[MethodContext], Type]]: - if 'Class.method' in fullname: + def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | None: + if "Class.method" in fullname: return extract_arg_kinds_from_method return None @@ -27,5 +28,5 @@ def extract_arg_kinds_from_method(ctx: MethodContext) -> Type: return ctx.default_return_type -def plugin(version): +def plugin(version: str) -> type[ArgKindsPlugin]: return ArgKindsPlugin diff --git a/test-data/unit/plugins/arg_names.py b/test-data/unit/plugins/arg_names.py index 6c1cbb9415cc..981c1a2eb12d 100644 --- a/test-data/unit/plugins/arg_names.py +++ b/test-data/unit/plugins/arg_names.py @@ -1,35 +1,51 @@ -from typing import Optional, Callable +from __future__ import annotations -from mypy.plugin import Plugin, MethodContext, FunctionContext +from typing import Callable + +from mypy.nodes import StrExpr +from mypy.plugin import FunctionContext, MethodContext, Plugin from mypy.types import Type class ArgNamesPlugin(Plugin): - def get_function_hook(self, fullname: str - ) -> Optional[Callable[[FunctionContext], Type]]: - if fullname in {'mod.func', 'mod.func_unfilled', 'mod.func_star_expr', - 'mod.ClassInit', 'mod.Outer.NestedClassInit'}: + def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None: + if fullname in { + "mod.func", + "mod.func_unfilled", + "mod.func_star_expr", + "mod.ClassInit", + "mod.Outer.NestedClassInit", + }: return extract_classname_and_set_as_return_type_function return None - def get_method_hook(self, fullname: str - ) -> Optional[Callable[[MethodContext], Type]]: - if fullname in {'mod.Class.method', 'mod.Class.myclassmethod', 'mod.Class.mystaticmethod', - 'mod.ClassUnfilled.method', 'mod.ClassStarExpr.method', - 'mod.ClassChild.method', 'mod.ClassChild.myclassmethod'}: + def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | None: + if fullname in { + "mod.Class.method", + "mod.Class.myclassmethod", + "mod.Class.mystaticmethod", + "mod.ClassUnfilled.method", + "mod.ClassStarExpr.method", + "mod.ClassChild.method", + "mod.ClassChild.myclassmethod", + }: return extract_classname_and_set_as_return_type_method return None def extract_classname_and_set_as_return_type_function(ctx: FunctionContext) -> Type: - classname = ctx.args[ctx.callee_arg_names.index('classname')][0].value - return ctx.api.named_generic_type(classname, []) + arg = ctx.args[ctx.callee_arg_names.index("classname")][0] + if not isinstance(arg, StrExpr): + return ctx.default_return_type + return ctx.api.named_generic_type(arg.value, []) def extract_classname_and_set_as_return_type_method(ctx: MethodContext) -> Type: - classname = ctx.args[ctx.callee_arg_names.index('classname')][0].value - return ctx.api.named_generic_type(classname, []) + arg = ctx.args[ctx.callee_arg_names.index("classname")][0] + if not isinstance(arg, StrExpr): + return ctx.default_return_type + return ctx.api.named_generic_type(arg.value, []) -def plugin(version): +def plugin(version: str) -> type[ArgNamesPlugin]: return ArgNamesPlugin diff --git a/test-data/unit/plugins/attrhook.py b/test-data/unit/plugins/attrhook.py index c177072aa47f..9500734daa6c 100644 --- a/test-data/unit/plugins/attrhook.py +++ b/test-data/unit/plugins/attrhook.py @@ -1,12 +1,14 @@ -from typing import Optional, Callable +from __future__ import annotations -from mypy.plugin import Plugin, AttributeContext -from mypy.types import Type, Instance +from typing import Callable + +from mypy.plugin import AttributeContext, Plugin +from mypy.types import Instance, Type class AttrPlugin(Plugin): - def get_attribute_hook(self, fullname: str) -> Optional[Callable[[AttributeContext], Type]]: - if fullname == 'm.Signal.__call__': + def get_attribute_hook(self, fullname: str) -> Callable[[AttributeContext], Type] | None: + if fullname == "m.Signal.__call__": return signal_call_callback return None @@ -17,5 +19,5 @@ def signal_call_callback(ctx: AttributeContext) -> Type: return ctx.default_attr_type -def plugin(version): +def plugin(version: str) -> type[AttrPlugin]: return AttrPlugin diff --git a/test-data/unit/plugins/attrhook2.py b/test-data/unit/plugins/attrhook2.py index cc14341a6f97..2d41a0fdf52f 100644 --- a/test-data/unit/plugins/attrhook2.py +++ b/test-data/unit/plugins/attrhook2.py @@ -1,14 +1,16 @@ -from typing import Optional, Callable +from __future__ import annotations -from mypy.plugin import Plugin, AttributeContext -from mypy.types import Type, AnyType, TypeOfAny +from typing import Callable + +from mypy.plugin import AttributeContext, Plugin +from mypy.types import AnyType, Type, TypeOfAny class AttrPlugin(Plugin): - def get_attribute_hook(self, fullname: str) -> Optional[Callable[[AttributeContext], Type]]: - if fullname == 'm.Magic.magic_field': + def get_attribute_hook(self, fullname: str) -> Callable[[AttributeContext], Type] | None: + if fullname == "m.Magic.magic_field": return magic_field_callback - if fullname == 'm.Magic.nonexistent_field': + if fullname == "m.Magic.nonexistent_field": return nonexistent_field_callback return None @@ -22,5 +24,5 @@ def nonexistent_field_callback(ctx: AttributeContext) -> Type: return AnyType(TypeOfAny.from_error) -def plugin(version): +def plugin(version: str) -> type[AttrPlugin]: return AttrPlugin diff --git a/test-data/unit/plugins/badreturn.py b/test-data/unit/plugins/badreturn.py index fd7430606dd6..9dce3b3e99c2 100644 --- a/test-data/unit/plugins/badreturn.py +++ b/test-data/unit/plugins/badreturn.py @@ -1,2 +1,2 @@ -def plugin(version): +def plugin(version: str) -> None: pass diff --git a/test-data/unit/plugins/badreturn2.py b/test-data/unit/plugins/badreturn2.py index c7e0447841c1..1ae551ecbf20 100644 --- a/test-data/unit/plugins/badreturn2.py +++ b/test-data/unit/plugins/badreturn2.py @@ -1,5 +1,9 @@ +from __future__ import annotations + + class MyPlugin: pass -def plugin(version): + +def plugin(version: str) -> type[MyPlugin]: return MyPlugin diff --git a/test-data/unit/plugins/callable_instance.py b/test-data/unit/plugins/callable_instance.py index 40e7df418539..a9f562effb34 100644 --- a/test-data/unit/plugins/callable_instance.py +++ b/test-data/unit/plugins/callable_instance.py @@ -1,23 +1,30 @@ +from __future__ import annotations + +from typing import Callable + from mypy.plugin import MethodContext, Plugin from mypy.types import Instance, Type + class CallableInstancePlugin(Plugin): - def get_function_hook(self, fullname): - assert not fullname.endswith(' of Foo') + def get_function_hook(self, fullname: str) -> None: + assert not fullname.endswith(" of Foo") - def get_method_hook(self, fullname): + def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | None: # Ensure that all names are fully qualified - assert not fullname.endswith(' of Foo') + assert not fullname.endswith(" of Foo") - if fullname == '__main__.Class.__call__': + if fullname == "__main__.Class.__call__": return my_hook return None + def my_hook(ctx: MethodContext) -> Type: if isinstance(ctx.type, Instance) and len(ctx.type.args) == 1: return ctx.type.args[0] return ctx.default_return_type -def plugin(version): + +def plugin(version: str) -> type[CallableInstancePlugin]: return CallableInstancePlugin diff --git a/test-data/unit/plugins/class_attr_hook.py b/test-data/unit/plugins/class_attr_hook.py index 348e5df0ee03..5d6a87df48bb 100644 --- a/test-data/unit/plugins/class_attr_hook.py +++ b/test-data/unit/plugins/class_attr_hook.py @@ -1,20 +1,23 @@ -from typing import Callable, Optional +from __future__ import annotations + +from typing import Callable from mypy.plugin import AttributeContext, Plugin from mypy.types import Type as MypyType class ClassAttrPlugin(Plugin): - def get_class_attribute_hook(self, fullname: str - ) -> Optional[Callable[[AttributeContext], MypyType]]: - if fullname == '__main__.Cls.attr': + def get_class_attribute_hook( + self, fullname: str + ) -> Callable[[AttributeContext], MypyType] | None: + if fullname == "__main__.Cls.attr": return my_hook return None def my_hook(ctx: AttributeContext) -> MypyType: - return ctx.api.named_generic_type('builtins.int', []) + return ctx.api.named_generic_type("builtins.int", []) -def plugin(_version: str): +def plugin(_version: str) -> type[ClassAttrPlugin]: return ClassAttrPlugin diff --git a/test-data/unit/plugins/class_callable.py b/test-data/unit/plugins/class_callable.py index 07f75ec80ac1..9fab30e60458 100644 --- a/test-data/unit/plugins/class_callable.py +++ b/test-data/unit/plugins/class_callable.py @@ -1,32 +1,43 @@ -from mypy.plugin import Plugin +from __future__ import annotations + +from typing import Callable + from mypy.nodes import NameExpr -from mypy.types import UnionType, NoneType, Instance +from mypy.plugin import FunctionContext, Plugin +from mypy.types import Instance, NoneType, Type, UnionType, get_proper_type + class AttrPlugin(Plugin): - def get_function_hook(self, fullname): - if fullname.startswith('mod.Attr'): + def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None: + if fullname.startswith("mod.Attr"): return attr_hook return None -def attr_hook(ctx): - assert isinstance(ctx.default_return_type, Instance) - if ctx.default_return_type.type.fullname == 'mod.Attr': - attr_base = ctx.default_return_type + +def attr_hook(ctx: FunctionContext) -> Type: + default = get_proper_type(ctx.default_return_type) + assert isinstance(default, Instance) + if default.type.fullname == "mod.Attr": + attr_base = default else: attr_base = None - for base in ctx.default_return_type.type.bases: - if base.type.fullname == 'mod.Attr': + for base in default.type.bases: + if base.type.fullname == "mod.Attr": attr_base = base break assert attr_base is not None last_arg_exprs = ctx.args[-1] - if any(isinstance(expr, NameExpr) and expr.name == 'True' for expr in last_arg_exprs): + if any(isinstance(expr, NameExpr) and expr.name == "True" for expr in last_arg_exprs): return attr_base assert len(attr_base.args) == 1 arg_type = attr_base.args[0] - return Instance(attr_base.type, [UnionType([arg_type, NoneType()])], - line=ctx.default_return_type.line, - column=ctx.default_return_type.column) + return Instance( + attr_base.type, + [UnionType([arg_type, NoneType()])], + line=default.line, + column=default.column, + ) + -def plugin(version): +def plugin(version: str) -> type[AttrPlugin]: return AttrPlugin diff --git a/test-data/unit/plugins/common_api_incremental.py b/test-data/unit/plugins/common_api_incremental.py index 2dcd559777ec..b14b2f92073e 100644 --- a/test-data/unit/plugins/common_api_incremental.py +++ b/test-data/unit/plugins/common_api_incremental.py @@ -1,44 +1,48 @@ -from mypy.plugin import Plugin -from mypy.nodes import ( - ClassDef, Block, TypeInfo, SymbolTable, SymbolTableNode, MDEF, GDEF, Var -) +from __future__ import annotations + +from typing import Callable + +from mypy.nodes import GDEF, MDEF, Block, ClassDef, SymbolTable, SymbolTableNode, TypeInfo, Var +from mypy.plugin import ClassDefContext, DynamicClassDefContext, Plugin class DynPlugin(Plugin): - def get_dynamic_class_hook(self, fullname): - if fullname == 'lib.declarative_base': + def get_dynamic_class_hook( + self, fullname: str + ) -> Callable[[DynamicClassDefContext], None] | None: + if fullname == "lib.declarative_base": return add_info_hook return None - def get_base_class_hook(self, fullname: str): + def get_base_class_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None: sym = self.lookup_fully_qualified(fullname) if sym and isinstance(sym.node, TypeInfo): - if sym.node.metadata.get('magic'): + if sym.node.metadata.get("magic"): return add_magic_hook return None -def add_info_hook(ctx) -> None: +def add_info_hook(ctx: DynamicClassDefContext) -> None: class_def = ClassDef(ctx.name, Block([])) class_def.fullname = ctx.api.qualified_name(ctx.name) info = TypeInfo(SymbolTable(), class_def, ctx.api.cur_mod_id) class_def.info = info - obj = ctx.api.named_type('builtins.object') + obj = ctx.api.named_type("builtins.object", []) info.mro = [info, obj.type] info.bases = [obj] ctx.api.add_symbol_table_node(ctx.name, SymbolTableNode(GDEF, info)) - info.metadata['magic'] = True + info.metadata["magic"] = {"value": True} -def add_magic_hook(ctx) -> None: +def add_magic_hook(ctx: ClassDefContext) -> None: info = ctx.cls.info - str_type = ctx.api.named_type_or_none('builtins.str', []) + str_type = ctx.api.named_type_or_none("builtins.str", []) assert str_type is not None - var = Var('__magic__', str_type) + var = Var("__magic__", str_type) var.info = info - info.names['__magic__'] = SymbolTableNode(MDEF, var) + info.names["__magic__"] = SymbolTableNode(MDEF, var) -def plugin(version): +def plugin(version: str) -> type[DynPlugin]: return DynPlugin diff --git a/test-data/unit/plugins/config_data.py b/test-data/unit/plugins/config_data.py index 059e036d5e32..9b828bc9ac0a 100644 --- a/test-data/unit/plugins/config_data.py +++ b/test-data/unit/plugins/config_data.py @@ -1,6 +1,7 @@ -import os -import json +from __future__ import annotations +import json +import os from typing import Any from mypy.plugin import Plugin, ReportConfigContext @@ -8,11 +9,11 @@ class ConfigDataPlugin(Plugin): def report_config_data(self, ctx: ReportConfigContext) -> Any: - path = os.path.join('tmp/test.json') + path = os.path.join("tmp/test.json") with open(path) as f: data = json.load(f) return data.get(ctx.id) -def plugin(version): +def plugin(version: str) -> type[ConfigDataPlugin]: return ConfigDataPlugin diff --git a/test-data/unit/plugins/custom_errorcode.py b/test-data/unit/plugins/custom_errorcode.py new file mode 100644 index 000000000000..0af87658e59f --- /dev/null +++ b/test-data/unit/plugins/custom_errorcode.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import Callable + +from mypy.errorcodes import ErrorCode +from mypy.plugin import FunctionContext, Plugin +from mypy.types import AnyType, Type, TypeOfAny + +CUSTOM_ERROR = ErrorCode(code="custom", description="", category="Custom") + + +class CustomErrorCodePlugin(Plugin): + def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None: + if fullname.endswith(".main"): + return self.emit_error + return None + + def emit_error(self, ctx: FunctionContext) -> Type: + ctx.api.fail("Custom error", ctx.context, code=CUSTOM_ERROR) + return AnyType(TypeOfAny.from_error) + + +def plugin(version: str) -> type[CustomErrorCodePlugin]: + return CustomErrorCodePlugin diff --git a/test-data/unit/plugins/customentry.py b/test-data/unit/plugins/customentry.py index b3dacfd4cf44..1a7ed3348e12 100644 --- a/test-data/unit/plugins/customentry.py +++ b/test-data/unit/plugins/customentry.py @@ -1,14 +1,22 @@ -from mypy.plugin import Plugin +from __future__ import annotations + +from typing import Callable + +from mypy.plugin import FunctionContext, Plugin +from mypy.types import Type + class MyPlugin(Plugin): - def get_function_hook(self, fullname): - if fullname == '__main__.f': + def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None: + if fullname == "__main__.f": return my_hook assert fullname return None -def my_hook(ctx): - return ctx.api.named_generic_type('builtins.int', []) -def register(version): +def my_hook(ctx: FunctionContext) -> Type: + return ctx.api.named_generic_type("builtins.int", []) + + +def register(version: str) -> type[MyPlugin]: return MyPlugin diff --git a/test-data/unit/plugins/customize_mro.py b/test-data/unit/plugins/customize_mro.py index 0f2396d98965..3b13b2e9d998 100644 --- a/test-data/unit/plugins/customize_mro.py +++ b/test-data/unit/plugins/customize_mro.py @@ -1,10 +1,17 @@ -from mypy.plugin import Plugin +from __future__ import annotations + +from typing import Callable + +from mypy.plugin import ClassDefContext, Plugin + class DummyPlugin(Plugin): - def get_customize_class_mro_hook(self, fullname): - def analyze(classdef_ctx): + def get_customize_class_mro_hook(self, fullname: str) -> Callable[[ClassDefContext], None]: + def analyze(classdef_ctx: ClassDefContext) -> None: pass + return analyze -def plugin(version): + +def plugin(version: str) -> type[DummyPlugin]: return DummyPlugin diff --git a/test-data/unit/plugins/decimal_to_int.py b/test-data/unit/plugins/decimal_to_int.py index 94aa33ef6df1..2318b2367d33 100644 --- a/test-data/unit/plugins/decimal_to_int.py +++ b/test-data/unit/plugins/decimal_to_int.py @@ -1,14 +1,21 @@ -from mypy.plugin import Plugin +from __future__ import annotations + +from typing import Callable + +from mypy.plugin import AnalyzeTypeContext, Plugin +from mypy.types import Type class MyPlugin(Plugin): - def get_type_analyze_hook(self, fullname): + def get_type_analyze_hook(self, fullname: str) -> Callable[[AnalyzeTypeContext], Type] | None: if fullname in ("decimal.Decimal", "_decimal.Decimal"): return decimal_to_int_hook return None -def plugin(version): - return MyPlugin -def decimal_to_int_hook(ctx): - return ctx.api.named_type('builtins.int', []) +def decimal_to_int_hook(ctx: AnalyzeTypeContext) -> Type: + return ctx.api.named_type("builtins.int", []) + + +def plugin(version: str) -> type[MyPlugin]: + return MyPlugin diff --git a/test-data/unit/plugins/depshook.py b/test-data/unit/plugins/depshook.py index 76277f3cb82b..bb2460de1196 100644 --- a/test-data/unit/plugins/depshook.py +++ b/test-data/unit/plugins/depshook.py @@ -1,15 +1,15 @@ -from typing import List, Tuple +from __future__ import annotations -from mypy.plugin import Plugin from mypy.nodes import MypyFile +from mypy.plugin import Plugin class DepsPlugin(Plugin): - def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]: - if file.fullname == '__main__': - return [(10, 'err', -1)] + def get_additional_deps(self, file: MypyFile) -> list[tuple[int, str, int]]: + if file.fullname == "__main__": + return [(10, "err", -1)] return [] -def plugin(version): +def plugin(version: str) -> type[DepsPlugin]: return DepsPlugin diff --git a/test-data/unit/plugins/descriptor.py b/test-data/unit/plugins/descriptor.py index afbadcdfb671..d38853367906 100644 --- a/test-data/unit/plugins/descriptor.py +++ b/test-data/unit/plugins/descriptor.py @@ -1,28 +1,38 @@ -from mypy.plugin import Plugin -from mypy.types import NoneType, CallableType +from __future__ import annotations + +from typing import Callable + +from mypy.plugin import MethodContext, MethodSigContext, Plugin +from mypy.types import CallableType, NoneType, Type, get_proper_type class DescriptorPlugin(Plugin): - def get_method_hook(self, fullname): + def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | None: if fullname == "__main__.Desc.__get__": return get_hook return None - def get_method_signature_hook(self, fullname): + def get_method_signature_hook( + self, fullname: str + ) -> Callable[[MethodSigContext], CallableType] | None: if fullname == "__main__.Desc.__set__": return set_hook return None -def get_hook(ctx): - if isinstance(ctx.arg_types[0][0], NoneType): - return ctx.api.named_type("builtins.str") - return ctx.api.named_type("builtins.int") +def get_hook(ctx: MethodContext) -> Type: + arg = get_proper_type(ctx.arg_types[0][0]) + if isinstance(arg, NoneType): + return ctx.api.named_generic_type("builtins.str", []) + return ctx.api.named_generic_type("builtins.int", []) -def set_hook(ctx): +def set_hook(ctx: MethodSigContext) -> CallableType: return CallableType( - [ctx.api.named_type("__main__.Cls"), ctx.api.named_type("builtins.int")], + [ + ctx.api.named_generic_type("__main__.Cls", []), + ctx.api.named_generic_type("builtins.int", []), + ], ctx.default_signature.arg_kinds, ctx.default_signature.arg_names, ctx.default_signature.ret_type, @@ -30,5 +40,5 @@ def set_hook(ctx): ) -def plugin(version): +def plugin(version: str) -> type[DescriptorPlugin]: return DescriptorPlugin diff --git a/test-data/unit/plugins/dyn_class.py b/test-data/unit/plugins/dyn_class.py index 54bf377aa8ef..18e948e3dd2a 100644 --- a/test-data/unit/plugins/dyn_class.py +++ b/test-data/unit/plugins/dyn_class.py @@ -1,47 +1,57 @@ -from mypy.plugin import Plugin -from mypy.nodes import ( - ClassDef, Block, TypeInfo, SymbolTable, SymbolTableNode, GDEF, Var -) -from mypy.types import Instance +from __future__ import annotations + +from typing import Callable + +from mypy.nodes import GDEF, Block, ClassDef, SymbolTable, SymbolTableNode, TypeInfo, Var +from mypy.plugin import ClassDefContext, DynamicClassDefContext, Plugin +from mypy.types import Instance, get_proper_type DECL_BASES = set() + class DynPlugin(Plugin): - def get_dynamic_class_hook(self, fullname): - if fullname == 'mod.declarative_base': + def get_dynamic_class_hook( + self, fullname: str + ) -> Callable[[DynamicClassDefContext], None] | None: + if fullname == "mod.declarative_base": return add_info_hook return None - def get_base_class_hook(self, fullname: str): + def get_base_class_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None: if fullname in DECL_BASES: return replace_col_hook return None -def add_info_hook(ctx): + +def add_info_hook(ctx: DynamicClassDefContext) -> None: class_def = ClassDef(ctx.name, Block([])) class_def.fullname = ctx.api.qualified_name(ctx.name) info = TypeInfo(SymbolTable(), class_def, ctx.api.cur_mod_id) class_def.info = info - obj = ctx.api.named_type('builtins.object') + obj = ctx.api.named_type("builtins.object") info.mro = [info, obj.type] info.bases = [obj] ctx.api.add_symbol_table_node(ctx.name, SymbolTableNode(GDEF, info)) DECL_BASES.add(class_def.fullname) -def replace_col_hook(ctx): + +def replace_col_hook(ctx: ClassDefContext) -> None: info = ctx.cls.info for sym in info.names.values(): node = sym.node - if isinstance(node, Var) and isinstance(node.type, Instance): - if node.type.type.fullname == 'mod.Column': - new_sym = ctx.api.lookup_fully_qualified_or_none('mod.Instr') + if isinstance(node, Var) and isinstance( + (node_type := get_proper_type(node.type)), Instance + ): + if node_type.type.fullname == "mod.Column": + new_sym = ctx.api.lookup_fully_qualified_or_none("mod.Instr") if new_sym: new_info = new_sym.node assert isinstance(new_info, TypeInfo) - node.type = Instance(new_info, node.type.args, - node.type.line, - node.type.column) + node.type = Instance( + new_info, node_type.args, node_type.line, node_type.column + ) + -def plugin(version): +def plugin(version: str) -> type[DynPlugin]: return DynPlugin diff --git a/test-data/unit/plugins/dyn_class_from_method.py b/test-data/unit/plugins/dyn_class_from_method.py index 4c3904907750..2630b16be66e 100644 --- a/test-data/unit/plugins/dyn_class_from_method.py +++ b/test-data/unit/plugins/dyn_class_from_method.py @@ -1,28 +1,76 @@ -from mypy.nodes import (Block, ClassDef, GDEF, SymbolTable, SymbolTableNode, TypeInfo) +from __future__ import annotations + +from typing import Callable + +from mypy.nodes import ( + GDEF, + Block, + ClassDef, + IndexExpr, + MemberExpr, + NameExpr, + RefExpr, + SymbolTable, + SymbolTableNode, + TypeApplication, + TypeInfo, +) from mypy.plugin import DynamicClassDefContext, Plugin from mypy.types import Instance class DynPlugin(Plugin): - def get_dynamic_class_hook(self, fullname): - if 'from_queryset' in fullname: + def get_dynamic_class_hook( + self, fullname: str + ) -> Callable[[DynamicClassDefContext], None] | None: + if "from_queryset" in fullname: return add_info_hook + if "as_manager" in fullname: + return as_manager_hook return None -def add_info_hook(ctx: DynamicClassDefContext): +def add_info_hook(ctx: DynamicClassDefContext) -> None: class_def = ClassDef(ctx.name, Block([])) class_def.fullname = ctx.api.qualified_name(ctx.name) info = TypeInfo(SymbolTable(), class_def, ctx.api.cur_mod_id) class_def.info = info + assert isinstance(ctx.call.args[0], RefExpr) queryset_type_fullname = ctx.call.args[0].fullname - queryset_info = ctx.api.lookup_fully_qualified_or_none(queryset_type_fullname).node # type: TypeInfo - obj = ctx.api.named_type('builtins.object') + queryset_node = ctx.api.lookup_fully_qualified_or_none(queryset_type_fullname) + assert queryset_node is not None + queryset_info = queryset_node.node + assert isinstance(queryset_info, TypeInfo) + obj = ctx.api.named_type("builtins.object") info.mro = [info, queryset_info, obj.type] info.bases = [Instance(queryset_info, [])] ctx.api.add_symbol_table_node(ctx.name, SymbolTableNode(GDEF, info)) -def plugin(version): +def as_manager_hook(ctx: DynamicClassDefContext) -> None: + class_def = ClassDef(ctx.name, Block([])) + class_def.fullname = ctx.api.qualified_name(ctx.name) + + info = TypeInfo(SymbolTable(), class_def, ctx.api.cur_mod_id) + class_def.info = info + assert isinstance(ctx.call.callee, MemberExpr) + assert isinstance(ctx.call.callee.expr, IndexExpr) + assert isinstance(ctx.call.callee.expr.analyzed, TypeApplication) + assert isinstance(ctx.call.callee.expr.analyzed.expr, NameExpr) + + queryset_type_fullname = ctx.call.callee.expr.analyzed.expr.fullname + queryset_node = ctx.api.lookup_fully_qualified_or_none(queryset_type_fullname) + assert queryset_node is not None + queryset_info = queryset_node.node + assert isinstance(queryset_info, TypeInfo) + parameter_type = ctx.call.callee.expr.analyzed.types[0] + + obj = ctx.api.named_type("builtins.object") + info.mro = [info, queryset_info, obj.type] + info.bases = [Instance(queryset_info, [parameter_type])] + ctx.api.add_symbol_table_node(ctx.name, SymbolTableNode(GDEF, info)) + + +def plugin(version: str) -> type[DynPlugin]: return DynPlugin diff --git a/test-data/unit/plugins/fnplugin.py b/test-data/unit/plugins/fnplugin.py index 684d6343458e..a5a7e57101c2 100644 --- a/test-data/unit/plugins/fnplugin.py +++ b/test-data/unit/plugins/fnplugin.py @@ -1,14 +1,22 @@ -from mypy.plugin import Plugin +from __future__ import annotations + +from typing import Callable + +from mypy.plugin import FunctionContext, Plugin +from mypy.types import Type + class MyPlugin(Plugin): - def get_function_hook(self, fullname): - if fullname == '__main__.f': + def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None: + if fullname == "__main__.f": return my_hook assert fullname is not None return None -def my_hook(ctx): - return ctx.api.named_generic_type('builtins.int', []) -def plugin(version): +def my_hook(ctx: FunctionContext) -> Type: + return ctx.api.named_generic_type("builtins.int", []) + + +def plugin(version: str) -> type[MyPlugin]: return MyPlugin diff --git a/test-data/unit/plugins/fully_qualified_test_hook.py b/test-data/unit/plugins/fully_qualified_test_hook.py index df42d50be265..9230091bba1a 100644 --- a/test-data/unit/plugins/fully_qualified_test_hook.py +++ b/test-data/unit/plugins/fully_qualified_test_hook.py @@ -1,16 +1,28 @@ -from mypy.plugin import CallableType, MethodSigContext, Plugin +from __future__ import annotations + +from typing import Callable + +from mypy.plugin import MethodSigContext, Plugin +from mypy.types import CallableType + class FullyQualifiedTestPlugin(Plugin): - def get_method_signature_hook(self, fullname): + def get_method_signature_hook( + self, fullname: str + ) -> Callable[[MethodSigContext], CallableType] | None: # Ensure that all names are fully qualified - if 'FullyQualifiedTest' in fullname: - assert fullname.startswith('__main__.') and not ' of ' in fullname, fullname + if "FullyQualifiedTest" in fullname: + assert fullname.startswith("__main__.") and " of " not in fullname, fullname return my_hook - + return None + def my_hook(ctx: MethodSigContext) -> CallableType: - return ctx.default_signature.copy_modified(ret_type=ctx.api.named_generic_type('builtins.int', [])) + return ctx.default_signature.copy_modified( + ret_type=ctx.api.named_generic_type("builtins.int", []) + ) + -def plugin(version): +def plugin(version: str) -> type[FullyQualifiedTestPlugin]: return FullyQualifiedTestPlugin diff --git a/test-data/unit/plugins/function_sig_hook.py b/test-data/unit/plugins/function_sig_hook.py index d83c7df26209..a8d3cf058062 100644 --- a/test-data/unit/plugins/function_sig_hook.py +++ b/test-data/unit/plugins/function_sig_hook.py @@ -1,26 +1,27 @@ -from mypy.plugin import CallableType, CheckerPluginInterface, FunctionSigContext, Plugin -from mypy.types import Instance, Type +from __future__ import annotations + +from typing import Callable + +from mypy.plugin import FunctionSigContext, Plugin +from mypy.types import CallableType + class FunctionSigPlugin(Plugin): - def get_function_signature_hook(self, fullname): - if fullname == '__main__.dynamic_signature': + def get_function_signature_hook( + self, fullname: str + ) -> Callable[[FunctionSigContext], CallableType] | None: + if fullname == "__main__.dynamic_signature": return my_hook return None -def _str_to_int(api: CheckerPluginInterface, typ: Type) -> Type: - if isinstance(typ, Instance): - if typ.type.fullname == 'builtins.str': - return api.named_generic_type('builtins.int', []) - elif typ.args: - return typ.copy_modified(args=[_str_to_int(api, t) for t in typ.args]) - - return typ def my_hook(ctx: FunctionSigContext) -> CallableType: - return ctx.default_signature.copy_modified( - arg_types=[_str_to_int(ctx.api, t) for t in ctx.default_signature.arg_types], - ret_type=_str_to_int(ctx.api, ctx.default_signature.ret_type), - ) + arg1_args = ctx.args[0] + if len(arg1_args) != 1: + return ctx.default_signature + arg1_type = ctx.api.get_expression_type(arg1_args[0]) + return ctx.default_signature.copy_modified(arg_types=[arg1_type], ret_type=arg1_type) + -def plugin(version): +def plugin(version: str) -> type[FunctionSigPlugin]: return FunctionSigPlugin diff --git a/test-data/unit/plugins/method_in_decorator.py b/test-data/unit/plugins/method_in_decorator.py index 99774dfcc7ef..3fba7692266c 100644 --- a/test-data/unit/plugins/method_in_decorator.py +++ b/test-data/unit/plugins/method_in_decorator.py @@ -1,19 +1,25 @@ -from mypy.types import CallableType, Type -from typing import Callable, Optional +from __future__ import annotations + +from typing import Callable + from mypy.plugin import MethodContext, Plugin +from mypy.types import CallableType, Type, get_proper_type class MethodDecoratorPlugin(Plugin): - def get_method_hook(self, fullname: str) -> Optional[Callable[[MethodContext], Type]]: - if 'Foo.a' in fullname: + def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | None: + if "Foo.a" in fullname: return method_decorator_callback return None + def method_decorator_callback(ctx: MethodContext) -> Type: - if isinstance(ctx.default_return_type, CallableType): - str_type = ctx.api.named_generic_type('builtins.str', []) - return ctx.default_return_type.copy_modified(ret_type=str_type) + default = get_proper_type(ctx.default_return_type) + if isinstance(default, CallableType): + str_type = ctx.api.named_generic_type("builtins.str", []) + return default.copy_modified(ret_type=str_type) return ctx.default_return_type -def plugin(version): + +def plugin(version: str) -> type[MethodDecoratorPlugin]: return MethodDecoratorPlugin diff --git a/test-data/unit/plugins/method_sig_hook.py b/test-data/unit/plugins/method_sig_hook.py index 25c2842e6620..b78831cc45d5 100644 --- a/test-data/unit/plugins/method_sig_hook.py +++ b/test-data/unit/plugins/method_sig_hook.py @@ -1,30 +1,41 @@ -from mypy.plugin import CallableType, CheckerPluginInterface, MethodSigContext, Plugin -from mypy.types import Instance, Type +from __future__ import annotations + +from typing import Callable + +from mypy.plugin import CheckerPluginInterface, MethodSigContext, Plugin +from mypy.types import CallableType, Instance, Type, get_proper_type + class MethodSigPlugin(Plugin): - def get_method_signature_hook(self, fullname): + def get_method_signature_hook( + self, fullname: str + ) -> Callable[[MethodSigContext], CallableType] | None: # Ensure that all names are fully qualified - assert not fullname.endswith(' of Foo') + assert not fullname.endswith(" of Foo") - if fullname.startswith('__main__.Foo.'): + if fullname.startswith("__main__.Foo."): return my_hook return None + def _str_to_int(api: CheckerPluginInterface, typ: Type) -> Type: + typ = get_proper_type(typ) if isinstance(typ, Instance): - if typ.type.fullname == 'builtins.str': - return api.named_generic_type('builtins.int', []) + if typ.type.fullname == "builtins.str": + return api.named_generic_type("builtins.int", []) elif typ.args: return typ.copy_modified(args=[_str_to_int(api, t) for t in typ.args]) return typ + def my_hook(ctx: MethodSigContext) -> CallableType: return ctx.default_signature.copy_modified( arg_types=[_str_to_int(ctx.api, t) for t in ctx.default_signature.arg_types], ret_type=_str_to_int(ctx.api, ctx.default_signature.ret_type), ) -def plugin(version): + +def plugin(version: str) -> type[MethodSigPlugin]: return MethodSigPlugin diff --git a/test-data/unit/plugins/named_callable.py b/test-data/unit/plugins/named_callable.py index e40d181d2bad..c37e11c32125 100644 --- a/test-data/unit/plugins/named_callable.py +++ b/test-data/unit/plugins/named_callable.py @@ -1,28 +1,33 @@ -from mypy.plugin import Plugin -from mypy.types import CallableType +from __future__ import annotations + +from typing import Callable + +from mypy.plugin import FunctionContext, Plugin +from mypy.types import CallableType, Type, get_proper_type class MyPlugin(Plugin): - def get_function_hook(self, fullname): - if fullname == 'm.decorator1': + def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None: + if fullname == "m.decorator1": return decorator_call_hook - if fullname == 'm._decorated': # This is a dummy name generated by the plugin + if fullname == "m._decorated": # This is a dummy name generated by the plugin return decorate_hook return None -def decorator_call_hook(ctx): - if isinstance(ctx.default_return_type, CallableType): - return ctx.default_return_type.copy_modified(name='m._decorated') +def decorator_call_hook(ctx: FunctionContext) -> Type: + default = get_proper_type(ctx.default_return_type) + if isinstance(default, CallableType): + return default.copy_modified(name="m._decorated") return ctx.default_return_type -def decorate_hook(ctx): - if isinstance(ctx.default_return_type, CallableType): - return ctx.default_return_type.copy_modified( - ret_type=ctx.api.named_generic_type('builtins.str', [])) +def decorate_hook(ctx: FunctionContext) -> Type: + default = get_proper_type(ctx.default_return_type) + if isinstance(default, CallableType): + return default.copy_modified(ret_type=ctx.api.named_generic_type("builtins.str", [])) return ctx.default_return_type -def plugin(version): +def plugin(version: str) -> type[MyPlugin]: return MyPlugin diff --git a/test-data/unit/plugins/plugin2.py b/test-data/unit/plugins/plugin2.py index b530a62d23aa..e486d96ea8bf 100644 --- a/test-data/unit/plugins/plugin2.py +++ b/test-data/unit/plugins/plugin2.py @@ -1,13 +1,21 @@ -from mypy.plugin import Plugin +from __future__ import annotations + +from typing import Callable + +from mypy.plugin import FunctionContext, Plugin +from mypy.types import Type + class Plugin2(Plugin): - def get_function_hook(self, fullname): - if fullname in ('__main__.f', '__main__.g'): + def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None: + if fullname in ("__main__.f", "__main__.g"): return str_hook return None -def str_hook(ctx): - return ctx.api.named_generic_type('builtins.str', []) -def plugin(version): +def str_hook(ctx: FunctionContext) -> Type: + return ctx.api.named_generic_type("builtins.str", []) + + +def plugin(version: str) -> type[Plugin2]: return Plugin2 diff --git a/test-data/unit/plugins/type_anal_hook.py b/test-data/unit/plugins/type_anal_hook.py index 86d18d8c8611..c380bbe873fe 100644 --- a/test-data/unit/plugins/type_anal_hook.py +++ b/test-data/unit/plugins/type_anal_hook.py @@ -1,22 +1,23 @@ -from typing import Optional, Callable +from __future__ import annotations + +from typing import Callable + +from mypy.plugin import AnalyzeTypeContext, Plugin -from mypy.plugin import Plugin, AnalyzeTypeContext -from mypy.types import Type, TypeList, AnyType, CallableType, TypeOfAny # The official name changed to NoneType but we have an alias for plugin compat reasons # so we'll keep testing that here. -from mypy.types import NoneTyp +from mypy.types import AnyType, CallableType, NoneTyp, Type, TypeList, TypeOfAny + class TypeAnalyzePlugin(Plugin): - def get_type_analyze_hook(self, fullname: str - ) -> Optional[Callable[[AnalyzeTypeContext], Type]]: - if fullname == 'm.Signal': + def get_type_analyze_hook(self, fullname: str) -> Callable[[AnalyzeTypeContext], Type] | None: + if fullname == "m.Signal": return signal_type_analyze_callback return None def signal_type_analyze_callback(ctx: AnalyzeTypeContext) -> Type: - if (len(ctx.type.args) != 1 - or not isinstance(ctx.type.args[0], TypeList)): + if len(ctx.type.args) != 1 or not isinstance(ctx.type.args[0], TypeList): ctx.api.fail('Invalid "Signal" type (expected "Signal[[t, ...]]")', ctx.context) return AnyType(TypeOfAny.from_error) @@ -27,13 +28,11 @@ def signal_type_analyze_callback(ctx: AnalyzeTypeContext) -> Type: return AnyType(TypeOfAny.from_error) # Error generated elsewhere arg_types, arg_kinds, arg_names = analyzed arg_types = [ctx.api.analyze_type(arg) for arg in arg_types] - type_arg = CallableType(arg_types, - arg_kinds, - arg_names, - NoneTyp(), - ctx.api.named_type('builtins.function', [])) - return ctx.api.named_type('m.Signal', [type_arg]) + type_arg = CallableType( + arg_types, arg_kinds, arg_names, NoneTyp(), ctx.api.named_type("builtins.function", []) + ) + return ctx.api.named_type("m.Signal", [type_arg]) -def plugin(version): +def plugin(version: str) -> type[TypeAnalyzePlugin]: return TypeAnalyzePlugin diff --git a/test-data/unit/plugins/union_method.py b/test-data/unit/plugins/union_method.py index a7621553f6ad..7c62ffb8c0cc 100644 --- a/test-data/unit/plugins/union_method.py +++ b/test-data/unit/plugins/union_method.py @@ -1,34 +1,40 @@ -from mypy.plugin import ( - CallableType, CheckerPluginInterface, MethodSigContext, MethodContext, Plugin -) -from mypy.types import Instance, Type +from __future__ import annotations + +from typing import Callable + +from mypy.plugin import CheckerPluginInterface, MethodContext, MethodSigContext, Plugin +from mypy.types import CallableType, Instance, Type, get_proper_type class MethodPlugin(Plugin): - def get_method_signature_hook(self, fullname): - if fullname.startswith('__main__.Foo.'): + def get_method_signature_hook( + self, fullname: str + ) -> Callable[[MethodSigContext], CallableType] | None: + if fullname.startswith("__main__.Foo."): return my_meth_sig_hook return None - def get_method_hook(self, fullname): - if fullname.startswith('__main__.Bar.'): + def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | None: + if fullname.startswith("__main__.Bar."): return my_meth_hook return None def _str_to_int(api: CheckerPluginInterface, typ: Type) -> Type: + typ = get_proper_type(typ) if isinstance(typ, Instance): - if typ.type.fullname == 'builtins.str': - return api.named_generic_type('builtins.int', []) + if typ.type.fullname == "builtins.str": + return api.named_generic_type("builtins.int", []) elif typ.args: return typ.copy_modified(args=[_str_to_int(api, t) for t in typ.args]) return typ def _float_to_int(api: CheckerPluginInterface, typ: Type) -> Type: + typ = get_proper_type(typ) if isinstance(typ, Instance): - if typ.type.fullname == 'builtins.float': - return api.named_generic_type('builtins.int', []) + if typ.type.fullname == "builtins.float": + return api.named_generic_type("builtins.int", []) elif typ.args: return typ.copy_modified(args=[_float_to_int(api, t) for t in typ.args]) return typ @@ -45,5 +51,5 @@ def my_meth_hook(ctx: MethodContext) -> Type: return _float_to_int(ctx.api, ctx.default_return_type) -def plugin(version): +def plugin(version: str) -> type[MethodPlugin]: return MethodPlugin diff --git a/test-data/unit/pythoneval-asyncio.test b/test-data/unit/pythoneval-asyncio.test index 556414cf3252..4a185557495b 100644 --- a/test-data/unit/pythoneval-asyncio.test +++ b/test-data/unit/pythoneval-asyncio.test @@ -261,7 +261,7 @@ try: finally: loop.close() [out] -_program.py:11: error: Function does not return a value +_program.py:11: error: Function does not return a value (it only ever returns None) [case testErrorReturnIsNotTheSameType] from typing import Any @@ -472,7 +472,7 @@ async def bad(arg: P) -> T: pass [out] _program.py:8: note: Revealed type is "def [T] (arg: P?) -> typing.Coroutine[Any, Any, T`-1]" -_program.py:9: error: Value of type "Coroutine[Any, Any, ]" must be used +_program.py:9: error: Value of type "Coroutine[Any, Any, Never]" must be used _program.py:9: note: Are you missing an await? _program.py:11: error: Variable "_testForwardRefToBadAsyncShouldNotCrash_newsemanal.P" is not valid as a type _program.py:11: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 788eedd75a13..2a86250c657a 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -63,8 +63,8 @@ print(abs(A())) 5.5 [case testAbs2] -n = None # type: int -f = None # type: float +n: int +f: float n = abs(1) abs(1) + 'x' # Error f = abs(1.1) @@ -95,7 +95,7 @@ print(list.__add__([1, 2], [3, 4])) import typing class A: x = 1 - def f(self) -> None: print('f') + def f(self: typing.Optional["A"]) -> None: print('f') class B(A): pass B.f(None) @@ -138,7 +138,7 @@ A docstring! [case testFunctionAttributes] import typing ord.__class__ -print(type(ord.__doc__ + '')) +print(type(ord.__doc__ or '' + '')) print(ord.__name__) print(ord.__module__) [out] @@ -276,7 +276,9 @@ def bin(f: IO[bytes]) -> None: txt(sys.stdout) bin(sys.stdout) [out] -_program.py:5: error: Argument 1 to "write" of "IO" has incompatible type "bytes"; expected "str" +_program.py:5: error: No overload variant of "write" of "IO" matches argument type "bytes" +_program.py:5: note: Possible overload variants: +_program.py:5: note: def write(self, str, /) -> int _program.py:10: error: Argument 1 to "bin" has incompatible type "TextIO"; expected "IO[bytes]" [case testBuiltinOpen] @@ -339,25 +341,28 @@ _program.py:6: note: Revealed type is "typing.IO[Any]" [case testGenericPatterns] from typing import Pattern import re -p = None # type: Pattern[str] +p: Pattern[str] p = re.compile('foo*') -b = None # type: Pattern[bytes] +b: Pattern[bytes] b = re.compile(b'foo*') -print(p.match('fooo').group(0)) +m = p.match('fooo') +assert m +print(m.group(0)) [out] fooo [case testGenericMatch] -from typing import Match +from typing import Match, Optional import re -def f(m: Match[bytes]) -> None: +def f(m: Optional[Match[bytes]]) -> None: + assert m print(m.group(0)) f(re.match(b'x*', b'xxy')) [out] b'xx' [case testIntFloatDucktyping] -x = None # type: float +x: float x = 2.2 x = 2 def f(x: float) -> None: pass @@ -380,18 +385,17 @@ math.sin(2) math.sin(2.2) [case testAbsReturnType] - -f = None # type: float -n = None # type: int +f: float +n: int n = abs(2) f = abs(2.2) abs(2.2) + 'x' [out] -_program.py:6: error: Unsupported operand types for + ("float" and "str") +_program.py:5: error: Unsupported operand types for + ("float" and "str") [case testROperatorMethods] -b = None # type: bytes -s = None # type: str +b: bytes +s: str if int(): s = b'foo' * 5 # Error if int(): @@ -440,7 +444,6 @@ True False [case testOverlappingOperatorMethods] - class X: pass class A: def __add__(self, x) -> int: @@ -450,11 +453,11 @@ class A: class B: def __radd__(self, x: A) -> str: return 'x' class C(X, B): pass -b = None # type: B +b: B b = C() print(A() + b) [out] -_program.py:9: error: Signatures of "__radd__" of "B" and "__add__" of "A" are unsafely overlapping +_program.py:8: error: Signatures of "__radd__" of "B" and "__add__" of "A" are unsafely overlapping [case testBytesAndBytearrayComparisons] import typing @@ -839,6 +842,7 @@ _program.py:3: error: Dict entry 1 has incompatible type "str": "str"; expected _program.py:5: error: "Dict[str, int]" has no attribute "xyz" [case testDefaultDict] +# flags: --new-type-inference import typing as t from collections import defaultdict @@ -864,35 +868,11 @@ class MyDDict(t.DefaultDict[int,T], t.Generic[T]): MyDDict(dict)['0'] MyDDict(dict)[0] [out] -_program.py:6: error: Argument 1 to "defaultdict" has incompatible type "Type[List[Any]]"; expected "Callable[[], str]" -_program.py:9: error: Invalid index type "str" for "defaultdict[int, str]"; expected type "int" -_program.py:9: error: Incompatible types in assignment (expression has type "int", target has type "str") -_program.py:19: error: Argument 1 to "tst" has incompatible type "defaultdict[str, List[]]"; expected "defaultdict[int, List[]]" -_program.py:23: error: Invalid index type "str" for "MyDDict[Dict[_KT, _VT]]"; expected type "int" - -[case testNoSubcriptionOfStdlibCollections] -# flags: --python-version 3.6 -import collections -from collections import Counter -from typing import TypeVar - -collections.defaultdict[int, str]() -Counter[int]() - -T = TypeVar('T') -DDint = collections.defaultdict[T, int] - -d = DDint[str]() -d[0] = 1 - -def f(d: collections.defaultdict[int, str]) -> None: - ... -[out] -_program.py:6: error: "defaultdict" is not subscriptable -_program.py:7: error: "Counter" is not subscriptable -_program.py:10: error: "defaultdict" is not subscriptable -_program.py:13: error: Invalid index type "int" for "defaultdict[str, int]"; expected type "str" -_program.py:15: error: "defaultdict" is not subscriptable, use "typing.DefaultDict" instead +_program.py:7: error: Argument 1 to "defaultdict" has incompatible type "Type[List[Any]]"; expected "Optional[Callable[[], str]]" +_program.py:10: error: Invalid index type "str" for "defaultdict[int, str]"; expected type "int" +_program.py:10: error: Incompatible types in assignment (expression has type "int", target has type "str") +_program.py:20: error: Argument 1 to "tst" has incompatible type "defaultdict[str, List[Never]]"; expected "defaultdict[int, List[Never]]" +_program.py:24: error: Invalid index type "str" for "MyDDict[Dict[Never, Never]]"; expected type "int" [case testCollectionsAliases] import typing as t @@ -971,10 +951,10 @@ print(getattr(B(), 'x')) 7 [case testSortedNoError] -from typing import Iterable, Callable, TypeVar, List, Dict +from typing import Iterable, Callable, TypeVar, List, Dict, Optional T = TypeVar('T') -def sorted(x: Iterable[T], *, key: Callable[[T], object] = None) -> None: ... -a = None # type: List[Dict[str, str]] +def sorted(x: Iterable[T], *, key: Optional[Callable[[T], object]] = None) -> None: ... +a = [] # type: List[Dict[str, str]] sorted(a, key=lambda y: y['']) [case testAbstractProperty] @@ -1007,9 +987,13 @@ import re bre = b'a+' bpat = re.compile(bre) bpat = re.compile(bpat) -re.search(bre, b'').groups() +s1 = re.search(bre, b'') +assert s1 +s1.groups() re.search(bre, u'') # Error -re.search(bpat, b'').groups() +s2 = re.search(bpat, b'') +assert s2 +s2.groups() re.search(bpat, u'') # Error # match(), split(), findall(), finditer() are much the same, so skip those. # sub(), subn() have more overloads and we are checking these: @@ -1022,11 +1006,11 @@ re.subn(bpat, b'', b'')[0] + b'' re.subn(bre, lambda m: b'', b'')[0] + b'' re.subn(bpat, lambda m: b'', b'')[0] + b'' [out] -_testReModuleBytes.py:7: error: No overload variant of "search" matches argument types "bytes", "str" -_testReModuleBytes.py:7: note: Possible overload variants: -_testReModuleBytes.py:7: note: def search(pattern: Union[str, Pattern[str]], string: str, flags: Union[int, RegexFlag] = ...) -> Optional[Match[str]] -_testReModuleBytes.py:7: note: def search(pattern: Union[bytes, Pattern[bytes]], string: Union[bytes, Union[bytearray, memoryview, array[Any], mmap, _CData]], flags: Union[int, RegexFlag] = ...) -> Optional[Match[bytes]] -_testReModuleBytes.py:9: error: Argument 1 to "search" has incompatible type "Pattern[bytes]"; expected "Union[str, Pattern[str]]" +_testReModuleBytes.py:9: error: No overload variant of "search" matches argument types "bytes", "str" +_testReModuleBytes.py:9: note: Possible overload variants: +_testReModuleBytes.py:9: note: def search(pattern: Union[str, Pattern[str]], string: str, flags: Union[int, RegexFlag] = ...) -> Optional[Match[str]] +_testReModuleBytes.py:9: note: def search(pattern: Union[bytes, Pattern[bytes]], string: Buffer, flags: Union[int, RegexFlag] = ...) -> Optional[Match[bytes]] +_testReModuleBytes.py:13: error: Argument 1 to "search" has incompatible type "Pattern[bytes]"; expected "Union[str, Pattern[str]]" [case testReModuleString] # Regression tests for various overloads in the re module -- string version @@ -1034,9 +1018,13 @@ import re sre = 'a+' spat = re.compile(sre) spat = re.compile(spat) -re.search(sre, '').groups() +s1 = re.search(sre, '') +assert s1 +s1.groups() re.search(sre, b'') # Error -re.search(spat, '').groups() +s2 = re.search(spat, '') +assert s2 +s2.groups() re.search(spat, b'') # Error # match(), split(), findall(), finditer() are much the same, so skip those. # sus(), susn() have more overloads and we are checking these: @@ -1049,11 +1037,11 @@ re.subn(spat, '', '')[0] + '' re.subn(sre, lambda m: '', '')[0] + '' re.subn(spat, lambda m: '', '')[0] + '' [out] -_testReModuleString.py:7: error: No overload variant of "search" matches argument types "str", "bytes" -_testReModuleString.py:7: note: Possible overload variants: -_testReModuleString.py:7: note: def search(pattern: Union[str, Pattern[str]], string: str, flags: Union[int, RegexFlag] = ...) -> Optional[Match[str]] -_testReModuleString.py:7: note: def search(pattern: Union[bytes, Pattern[bytes]], string: Union[bytes, Union[bytearray, memoryview, array[Any], mmap, _CData]], flags: Union[int, RegexFlag] = ...) -> Optional[Match[bytes]] -_testReModuleString.py:9: error: Argument 1 to "search" has incompatible type "Pattern[str]"; expected "Union[bytes, Pattern[bytes]]" +_testReModuleString.py:9: error: No overload variant of "search" matches argument types "str", "bytes" +_testReModuleString.py:9: note: Possible overload variants: +_testReModuleString.py:9: note: def search(pattern: Union[str, Pattern[str]], string: str, flags: Union[int, RegexFlag] = ...) -> Optional[Match[str]] +_testReModuleString.py:9: note: def search(pattern: Union[bytes, Pattern[bytes]], string: Buffer, flags: Union[int, RegexFlag] = ...) -> Optional[Match[bytes]] +_testReModuleString.py:13: error: Argument 1 to "search" has incompatible type "Pattern[str]"; expected "Union[bytes, Pattern[bytes]]" [case testListSetitemTuple] from typing import List, Tuple @@ -1090,7 +1078,6 @@ _program.py:17: note: Revealed type is "builtins.str" [case testTypedDictGet] # Test that TypedDict get plugin works with typeshed stubs -# TODO: Make it possible to use strict optional here from mypy_extensions import TypedDict class A: pass D = TypedDict('D', {'x': int, 'y': str}) @@ -1102,14 +1089,14 @@ d.get() s = '' reveal_type(d.get(s)) [out] -_testTypedDictGet.py:7: note: Revealed type is "builtins.int" -_testTypedDictGet.py:8: note: Revealed type is "builtins.str" -_testTypedDictGet.py:9: note: Revealed type is "builtins.object" -_testTypedDictGet.py:10: error: All overload variants of "get" of "Mapping" require at least one argument -_testTypedDictGet.py:10: note: Possible overload variants: -_testTypedDictGet.py:10: note: def get(self, str, /) -> object -_testTypedDictGet.py:10: note: def [_T] get(self, str, /, default: object) -> object -_testTypedDictGet.py:12: note: Revealed type is "builtins.object" +_testTypedDictGet.py:6: note: Revealed type is "Union[builtins.int, None]" +_testTypedDictGet.py:7: note: Revealed type is "Union[builtins.str, None]" +_testTypedDictGet.py:8: note: Revealed type is "builtins.object" +_testTypedDictGet.py:9: error: All overload variants of "get" of "Mapping" require at least one argument +_testTypedDictGet.py:9: note: Possible overload variants: +_testTypedDictGet.py:9: note: def get(self, str, /) -> object +_testTypedDictGet.py:9: note: def [_T] get(self, str, /, default: object) -> object +_testTypedDictGet.py:11: note: Revealed type is "builtins.object" [case testTypedDictMappingMethods] from mypy_extensions import TypedDict @@ -1148,10 +1135,10 @@ _testTypedDictMappingMethods.py:16: error: Key "value" of TypedDict "Cell" canno _testTypedDictMappingMethods.py:21: note: Revealed type is "builtins.int" [case testCrashOnComplexCheckWithNamedTupleNext] -from typing import NamedTuple +from typing import NamedTuple, Optional MyNamedTuple = NamedTuple('MyNamedTuple', [('parent', 'MyNamedTuple')]) # type: ignore -def foo(mymap) -> MyNamedTuple: +def foo(mymap) -> Optional[MyNamedTuple]: return next((mymap[key] for key in mymap), None) [out] @@ -1356,7 +1343,7 @@ def f() -> Dict[int, str]: def d() -> Dict[int, int]: return {} [out] -_testDictWithStarStarSpecialCase.py:4: error: Argument 1 to "update" of "MutableMapping" has incompatible type "Dict[int, int]"; expected "SupportsKeysAndGetItem[int, str]" +_testDictWithStarStarSpecialCase.py:4: error: Unpacked dict entry 1 has incompatible type "Dict[int, int]"; expected "SupportsKeysAndGetItem[int, str]" [case testLoadsOfOverloads] from typing import overload, Any, TypeVar, Iterable, List, Dict, Callable, Union @@ -1379,7 +1366,7 @@ JsonBlob = Dict[str, Any] Column = Union[List[str], List[int], List[bool], List[float], List[DateTime], List[JsonBlob]] def print_custom_table() -> None: - a = None # type: Column + a: Column for row in simple_map(format_row, a, a, a, a, a, a, a, a): # 8 columns reveal_type(row) @@ -1438,7 +1425,7 @@ accepts_named_tuple(b) accepts_named_tuple(1) accepts_named_tuple((1, 2)) [out] -_testNamedTupleTypeInheritanceSpecialCase.py:8: note: Revealed type is "collections.OrderedDict[builtins.str, Any]" +_testNamedTupleTypeInheritanceSpecialCase.py:8: note: Revealed type is "builtins.dict[builtins.str, Any]" _testNamedTupleTypeInheritanceSpecialCase.py:9: note: Revealed type is "builtins.tuple[builtins.str, ...]" _testNamedTupleTypeInheritanceSpecialCase.py:10: note: Revealed type is "builtins.dict[builtins.str, Any]" _testNamedTupleTypeInheritanceSpecialCase.py:17: error: Argument 1 to "accepts_named_tuple" has incompatible type "int"; expected "NamedTuple" @@ -1479,14 +1466,12 @@ frozenset({1}) == [1] # Error {1: 2}.keys() == frozenset({1}) {1: 2}.items() == {(1, 2)} -{1: 2}.keys() == {'no'} # Error +{1: 2}.keys() == {'no'} # OK {1: 2}.values() == {2} # Error -{1: 2}.keys() == [1] # Error +{1: 2}.keys() == [1] # OK [out] _testStrictEqualityAllowlist.py:5: error: Non-overlapping equality check (left operand type: "FrozenSet[int]", right operand type: "List[int]") -_testStrictEqualityAllowlist.py:11: error: Non-overlapping equality check (left operand type: "dict_keys[int, int]", right operand type: "Set[str]") _testStrictEqualityAllowlist.py:12: error: Non-overlapping equality check (left operand type: "dict_values[int, int]", right operand type: "Set[int]") -_testStrictEqualityAllowlist.py:13: error: Non-overlapping equality check (left operand type: "dict_keys[int, int]", right operand type: "List[int]") [case testUnreachableWithStdlibContextManagers] # mypy: warn-unreachable, strict-optional @@ -1567,34 +1552,33 @@ note: A user-defined top-level module with name "typing" is not supported # flags: --ignore-missing-imports import scribe # No Python 3 stubs available for scribe from scribe import x -import maxminddb # Python 3 stubs available for maxminddb +import python2 # Python 3 stubs available for python2 import foobar_asdf import jack # This has a stubs package but was never bundled with mypy, so ignoring works [out] -_testIgnoreImportIfNoPython3StubAvailable.py:4: error: Library stubs not installed for "maxminddb" -_testIgnoreImportIfNoPython3StubAvailable.py:4: note: Hint: "python3 -m pip install types-maxminddb" +_testIgnoreImportIfNoPython3StubAvailable.py:4: error: Library stubs not installed for "python2" +_testIgnoreImportIfNoPython3StubAvailable.py:4: note: Hint: "python3 -m pip install types-six" _testIgnoreImportIfNoPython3StubAvailable.py:4: note: (or run "mypy --install-types" to install all missing stub packages) _testIgnoreImportIfNoPython3StubAvailable.py:4: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports [case testNoPython3StubAvailable] import scribe from scribe import x -import maxminddb +import python2 [out] _testNoPython3StubAvailable.py:1: error: Cannot find implementation or library stub for module named "scribe" _testNoPython3StubAvailable.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports -_testNoPython3StubAvailable.py:3: error: Library stubs not installed for "maxminddb" -_testNoPython3StubAvailable.py:3: note: Hint: "python3 -m pip install types-maxminddb" +_testNoPython3StubAvailable.py:3: error: Library stubs not installed for "python2" +_testNoPython3StubAvailable.py:3: note: Hint: "python3 -m pip install types-six" _testNoPython3StubAvailable.py:3: note: (or run "mypy --install-types" to install all missing stub packages) [case testTypingOrderedDictAlias] -# flags: --python-version 3.7 from typing import OrderedDict x: OrderedDict[str, int] = OrderedDict({}) reveal_type(x) [out] -_testTypingOrderedDictAlias.py:4: note: Revealed type is "collections.OrderedDict[builtins.str, builtins.int]" +_testTypingOrderedDictAlias.py:3: note: Revealed type is "collections.OrderedDict[builtins.str, builtins.int]" [case testTypingExtensionsOrderedDictAlias] from typing_extensions import OrderedDict @@ -1646,7 +1630,6 @@ foo(list((list(""), ""))) [out] [case testNarrowTypeForDictKeys] -# flags: --strict-optional from typing import Dict, KeysView, Optional d: Dict[str, int] @@ -1664,10 +1647,10 @@ else: reveal_type(k) [out] -_testNarrowTypeForDictKeys.py:7: note: Revealed type is "builtins.str" -_testNarrowTypeForDictKeys.py:9: note: Revealed type is "Union[builtins.str, None]" -_testNarrowTypeForDictKeys.py:14: note: Revealed type is "builtins.str" -_testNarrowTypeForDictKeys.py:16: note: Revealed type is "Union[builtins.str, None]" +_testNarrowTypeForDictKeys.py:6: note: Revealed type is "builtins.str" +_testNarrowTypeForDictKeys.py:8: note: Revealed type is "Union[builtins.str, None]" +_testNarrowTypeForDictKeys.py:13: note: Revealed type is "builtins.str" +_testNarrowTypeForDictKeys.py:15: note: Revealed type is "Union[builtins.str, None]" [case testTypeAliasWithNewStyleUnion] # flags: --python-version 3.10 @@ -1711,7 +1694,6 @@ _testTypeAliasWithNewStyleUnion.py:25: note: Revealed type is "Union[Type[builti _testTypeAliasWithNewStyleUnion.py:28: note: Revealed type is "Union[Type[builtins.int], builtins.str]" [case testTypeAliasWithNewStyleUnionInStub] -# flags: --python-version 3.7 import m a: m.A reveal_type(a) @@ -1762,12 +1744,12 @@ CU4: TypeAlias = int | Callable[[str | bool], str] [out] m.pyi:5: note: Revealed type is "typing._SpecialForm" m.pyi:22: note: Revealed type is "typing._SpecialForm" -_testTypeAliasWithNewStyleUnionInStub.py:4: note: Revealed type is "Union[Type[builtins.int], builtins.str]" -_testTypeAliasWithNewStyleUnionInStub.py:6: note: Revealed type is "Union[Type[builtins.int], builtins.str]" -_testTypeAliasWithNewStyleUnionInStub.py:8: note: Revealed type is "Union[Type[builtins.int], builtins.str]" -_testTypeAliasWithNewStyleUnionInStub.py:10: note: Revealed type is "Union[Type[builtins.int], builtins.str]" -_testTypeAliasWithNewStyleUnionInStub.py:12: note: Revealed type is "Union[builtins.str, Type[builtins.int]]" -_testTypeAliasWithNewStyleUnionInStub.py:14: note: Revealed type is "Union[builtins.str, Type[builtins.int]]" +_testTypeAliasWithNewStyleUnionInStub.py:3: note: Revealed type is "Union[Type[builtins.int], builtins.str]" +_testTypeAliasWithNewStyleUnionInStub.py:5: note: Revealed type is "Union[Type[builtins.int], builtins.str]" +_testTypeAliasWithNewStyleUnionInStub.py:7: note: Revealed type is "Union[Type[builtins.int], builtins.str]" +_testTypeAliasWithNewStyleUnionInStub.py:9: note: Revealed type is "Union[Type[builtins.int], builtins.str]" +_testTypeAliasWithNewStyleUnionInStub.py:11: note: Revealed type is "Union[builtins.str, Type[builtins.int]]" +_testTypeAliasWithNewStyleUnionInStub.py:13: note: Revealed type is "Union[builtins.str, Type[builtins.int]]" [case testEnumNameWorkCorrectlyOn311] # flags: --python-version 3.11 @@ -1801,9 +1783,9 @@ C = str | int D: TypeAlias = str | int [out] _testTypeAliasNotSupportedWithNewStyleUnion.py:3: error: Invalid type alias: expression is not a valid type -_testTypeAliasNotSupportedWithNewStyleUnion.py:3: error: Value of type "Type[type]" is not indexable +_testTypeAliasNotSupportedWithNewStyleUnion.py:3: error: The type "Type[type]" is not generic and not indexable _testTypeAliasNotSupportedWithNewStyleUnion.py:4: error: Invalid type alias: expression is not a valid type -_testTypeAliasNotSupportedWithNewStyleUnion.py:4: error: Value of type "Type[type]" is not indexable +_testTypeAliasNotSupportedWithNewStyleUnion.py:4: error: The type "Type[type]" is not generic and not indexable _testTypeAliasNotSupportedWithNewStyleUnion.py:5: error: Invalid type alias: expression is not a valid type _testTypeAliasNotSupportedWithNewStyleUnion.py:5: error: Unsupported left operand type for | ("Type[str]") _testTypeAliasNotSupportedWithNewStyleUnion.py:6: error: Invalid type alias: expression is not a valid type @@ -1886,6 +1868,23 @@ _testEnumIterMetaInference.py:8: note: Revealed type is "typing.Iterator[_E`-1]" _testEnumIterMetaInference.py:9: note: Revealed type is "_E`-1" _testEnumIterMetaInference.py:13: note: Revealed type is "socket.SocketKind" +[case testEnumUnpackedViaMetaclass] +from enum import Enum + +class FooEnum(Enum): + A = 1 + B = 2 + C = 3 + +a, b, c = FooEnum +reveal_type(a) +reveal_type(b) +reveal_type(c) +[out] +_testEnumUnpackedViaMetaclass.py:9: note: Revealed type is "_testEnumUnpackedViaMetaclass.FooEnum" +_testEnumUnpackedViaMetaclass.py:10: note: Revealed type is "_testEnumUnpackedViaMetaclass.FooEnum" +_testEnumUnpackedViaMetaclass.py:11: note: Revealed type is "_testEnumUnpackedViaMetaclass.FooEnum" + [case testNativeIntTypes] # Spot check various native int operations with full stubs. from mypy_extensions import i64, i32 @@ -1932,3 +1931,192 @@ _testStarUnpackNestedUnderscore.py:10: error: List item 0 has incompatible type _testStarUnpackNestedUnderscore.py:10: error: List item 1 has incompatible type "int"; expected "str" _testStarUnpackNestedUnderscore.py:11: note: Revealed type is "builtins.list[builtins.str]" _testStarUnpackNestedUnderscore.py:16: note: Revealed type is "builtins.list[builtins.object]" + +[case testStrictEqualitywithParamSpec] +# flags: --strict-equality +from typing import Generic +from typing_extensions import Concatenate, ParamSpec + +P = ParamSpec("P") + +class Foo(Generic[P]): ... +class Bar(Generic[P]): ... + +def bad(foo: Foo[[int]], bar: Bar[[int]]) -> bool: + return foo == bar + +def good1(foo1: Foo[[int]], foo2: Foo[[str]]) -> bool: + return foo1 == foo2 + +def good2(foo1: Foo[[int, str]], foo2: Foo[[int, bytes]]) -> bool: + return foo1 == foo2 + +def good3(foo1: Foo[[int]], foo2: Foo[[int, int]]) -> bool: + return foo1 == foo2 + +def good4(foo1: Foo[[int]], foo2: Foo[[int]]) -> bool: + return foo1 == foo2 + +def good5(foo1: Foo[[int]], foo2: Foo[[bool]]) -> bool: + return foo1 == foo2 + +def good6(foo1: Foo[[int, int]], foo2: Foo[[bool, bool]]) -> bool: + return foo1 == foo2 + +def good7(foo1: Foo[[int]], foo2: Foo[P], *args: P.args, **kwargs: P.kwargs) -> bool: + return foo1 == foo2 + +def good8(foo1: Foo[P], foo2: Foo[[int, str, bytes]], *args: P.args, **kwargs: P.kwargs) -> bool: + return foo1 == foo2 + +def good9(foo1: Foo[Concatenate[int, P]], foo2: Foo[[int, str, bytes]], *args: P.args, **kwargs: P.kwargs) -> bool: + return foo1 == foo2 + +[out] +_testStrictEqualitywithParamSpec.py:11: error: Non-overlapping equality check (left operand type: "Foo[[int]]", right operand type: "Bar[[int]]") + +[case testInferenceOfDunderDictOnClassObjects] +class Foo: ... +reveal_type(Foo.__dict__) +reveal_type(Foo().__dict__) +Foo.__dict__ = {} +Foo().__dict__ = {} + +[out] +_testInferenceOfDunderDictOnClassObjects.py:2: note: Revealed type is "types.MappingProxyType[builtins.str, Any]" +_testInferenceOfDunderDictOnClassObjects.py:3: note: Revealed type is "builtins.dict[builtins.str, Any]" +_testInferenceOfDunderDictOnClassObjects.py:4: error: Property "__dict__" defined in "type" is read-only +_testInferenceOfDunderDictOnClassObjects.py:4: error: Incompatible types in assignment (expression has type "Dict[Never, Never]", variable has type "MappingProxyType[str, Any]") + +[case testTypeVarTuple] +# flags: --python-version=3.11 +from typing import Any, Callable, Unpack, TypeVarTuple + +Ts = TypeVarTuple("Ts") + +def foo(callback: Callable[[], Any]) -> None: + call(callback) + +def call(callback: Callable[[Unpack[Ts]], Any], *args: Unpack[Ts]) -> Any: + ... + +[case testTypeVarTupleTypingExtensions] +from typing_extensions import Unpack, TypeVarTuple +from typing import Any, Callable + +Ts = TypeVarTuple("Ts") + +def foo(callback: Callable[[], Any]) -> None: + call(callback) + +def call(callback: Callable[[Unpack[Ts]], Any], *args: Unpack[Ts]) -> Any: + ... + +[case testDataclassReplace] +from dataclasses import dataclass, replace + +@dataclass +class A: + x: int + +a = A(x=42) +a2 = replace(a, x=42) +reveal_type(a2) +a2 = replace() +a2 = replace(a, x='spam') +a2 = replace(a, x=42, q=42) +[out] +_testDataclassReplace.py:9: note: Revealed type is "_testDataclassReplace.A" +_testDataclassReplace.py:10: error: Too few arguments for "replace" +_testDataclassReplace.py:11: error: Argument "x" to "replace" of "A" has incompatible type "str"; expected "int" +_testDataclassReplace.py:12: error: Unexpected keyword argument "q" for "replace" of "A" + +[case testGenericInferenceWithTuple] +# flags: --new-type-inference +from typing import TypeVar, Callable, Tuple + +T = TypeVar("T") + +def f(x: Callable[..., T]) -> T: + return x() + +x: Tuple[str, ...] = f(tuple) +[out] + +[case testGenericInferenceWithDataclass] +# flags: --new-type-inference +from typing import Any, Collection, List +from dataclasses import dataclass, field + +class Foo: + pass + +@dataclass +class A: + items: Collection[Foo] = field(default_factory=list) +[out] + +[case testGenericInferenceWithItertools] +# flags: --new-type-inference +from typing import TypeVar, Tuple +from itertools import groupby +K = TypeVar("K") +V = TypeVar("V") + +def fst(kv: Tuple[K, V]) -> K: + k, v = kv + return k + +pairs = [(len(s), s) for s in ["one", "two", "three"]] +grouped = groupby(pairs, key=fst) +[out] + +[case testDataclassReplaceOptional] +from dataclasses import dataclass, replace +from typing import Optional + +@dataclass +class A: + x: Optional[int] + +a = A(x=42) +reveal_type(a) +a2 = replace(a, x=None) # OK +reveal_type(a2) +[out] +_testDataclassReplaceOptional.py:9: note: Revealed type is "_testDataclassReplaceOptional.A" +_testDataclassReplaceOptional.py:11: note: Revealed type is "_testDataclassReplaceOptional.A" + +[case testDataclassStrictOptionalAlwaysSet] +from dataclasses import dataclass +from typing import Callable, Optional + +@dataclass +class Description: + name_fn: Callable[[Optional[int]], Optional[str]] + +def f(d: Description) -> None: + reveal_type(d.name_fn) +[out] +_testDataclassStrictOptionalAlwaysSet.py:9: note: Revealed type is "def (Union[builtins.int, None]) -> Union[builtins.str, None]" + +[case testPEP695VarianceInference] +# flags: --python-version=3.12 --enable-incomplete-feature=NewGenericSyntax +from typing import Callable, Final + +class Job[_R_co]: + def __init__(self, target: Callable[[], _R_co]) -> None: + self.target: Final = target + +def func( + action: Job[int | None], + a1: Job[int | None], + a2: Job[int], + a3: Job[None], +) -> None: + action = a1 + action = a2 + action = a3 + a2 = action # Error +[out] +_testPEP695VarianceInference.py:17: error: Incompatible types in assignment (expression has type "Job[None]", variable has type "Job[int]") diff --git a/test-data/unit/ref-info.test b/test-data/unit/ref-info.test new file mode 100644 index 000000000000..05426130d272 --- /dev/null +++ b/test-data/unit/ref-info.test @@ -0,0 +1,83 @@ +[case testCallGlobalFunction] +def f() -> None: + g() + +def g() -> None: + pass +[out] +2:4:__main__.g + +[case testCallMethod] +def f() -> None: + c = C() + if int(): + c.method() + +class C: + def method(self) -> None: pass +[out] +2:8:__main__.C +3:7:builtins.int +4:8:__main__.C.method + +[case testCallStaticMethod] +class C: + def f(self) -> None: + C.static() + self.static() + + @classmethod + def cm(cls) -> None: + cls.static() + + @staticmethod + def static() -> None: pass +[builtins fixtures/classmethod.pyi] +[out] +3:8:__main__.C +3:8:__main__.C.static +4:8:__main__.C.static +8:8:__main__.C.static + +[case testCallClassMethod] +class C: + def f(self) -> None: + C.cm() + self.cm() + + @classmethod + def cm(cls) -> None: + cls.cm() +[builtins fixtures/classmethod.pyi] +[out] +3:8:__main__.C +3:8:__main__.C.cm +4:8:__main__.C.cm +8:8:__main__.C.cm + +[case testTypeVarWithValueRestriction] +from typing import TypeVar + +T = TypeVar("T", "C", "D") + +def f(o: T) -> None: + f(o) + o.m() + o.x + +class C: + x: int + def m(self) -> None: pass + +class D: + x: str + def m(self) -> None: pass +[out] +3:4:typing.TypeVar +3:0:__main__.T +6:4:__main__.f +7:4:__main__.C.m +8:4:__main__.C.x +6:4:__main__.f +7:4:__main__.D.m +8:4:__main__.D.x diff --git a/test-data/unit/reports.test b/test-data/unit/reports.test index 50dabb1fdea9..16061d9c32bf 100644 --- a/test-data/unit/reports.test +++ b/test-data/unit/reports.test @@ -69,6 +69,40 @@ def untyped_function(): +[case testCoberturaStarUnpacking] +# cmd: mypy --cobertura-xml-report build a.py +[file a.py] +from typing import TypedDict + +class MyDict(TypedDict): + a: int + +def foo(a: int) -> MyDict: + return {"a": a} +md: MyDict = MyDict(**foo(42)) +[outfile build/cobertura.xml] + + + $PWD + + + + + + + + + + + + + + + + + + + [case testAnyExprReportDivisionByZero] # cmd: mypy --any-exprs-report=out -c 'pass' @@ -311,7 +345,7 @@ Total 0 14 100.00% [case testAnyExpressionsReportTypesOfAny] -# cmd: mypy --python-version=3.6 --any-exprs-report report n.py +# cmd: mypy --any-exprs-report report n.py [file n.py] from typing import Any, List diff --git a/test-data/unit/semanal-basic.test b/test-data/unit/semanal-basic.test index 20443517e03e..169769f06a00 100644 --- a/test-data/unit/semanal-basic.test +++ b/test-data/unit/semanal-basic.test @@ -82,7 +82,7 @@ MypyFile:1( Args( Var(x) Var(y)) - Block:1( + Block:2( ExpressionStmt:2( TupleExpr:2( NameExpr(x [l]) @@ -96,7 +96,7 @@ def f(): MypyFile:1( FuncDef:1( f - Block:1( + Block:2( AssignmentStmt:2( NameExpr(x* [l]) IntExpr(1)) @@ -113,7 +113,7 @@ def g(): pass MypyFile:1( FuncDef:1( f - Block:1( + Block:2( ExpressionStmt:2( NameExpr(x [__main__.x])) ExpressionStmt:3( @@ -149,7 +149,7 @@ MypyFile:1( f Args( Var(y)) - Block:3( + Block:4( AssignmentStmt:4( NameExpr(y [l]) IntExpr(1)) @@ -174,7 +174,7 @@ MypyFile:1( builtins.int) FuncDef:2( f - Block:2( + Block:3( AssignmentStmt:3( NameExpr(x* [l]) IntExpr(2)) @@ -197,7 +197,7 @@ MypyFile:1( default( Var(y) NameExpr(object [builtins.object]))) - Block:1( + Block:2( ExpressionStmt:2( TupleExpr:2( NameExpr(x [l]) @@ -214,7 +214,7 @@ MypyFile:1( Var(x)) VarArg( Var(y)) - Block:1( + Block:2( ExpressionStmt:2( TupleExpr:2( NameExpr(x [l]) @@ -234,7 +234,7 @@ MypyFile:1( NameExpr(None [builtins.None])) FuncDef:2( f - Block:2( + Block:3( GlobalDecl:3( x) AssignmentStmt:4( @@ -262,7 +262,7 @@ MypyFile:1( NameExpr(None [builtins.None]))) FuncDef:2( f - Block:2( + Block:3( GlobalDecl:3( x y) @@ -283,12 +283,12 @@ MypyFile:1( NameExpr(None [builtins.None])) FuncDef:2( f - Block:2( + Block:3( GlobalDecl:3( x))) FuncDef:4( g - Block:4( + Block:5( AssignmentStmt:5( NameExpr(x* [l]) NameExpr(None [builtins.None]))))) @@ -306,12 +306,12 @@ MypyFile:1( NameExpr(None [builtins.None])) FuncDef:2( f - Block:2( + Block:3( GlobalDecl:3( x))) FuncDef:4( g - Block:4( + Block:5( AssignmentStmt:5( NameExpr(x* [l]) NameExpr(None [builtins.None]))))) @@ -333,7 +333,7 @@ MypyFile:1( f Args( Var(self)) - Block:3( + Block:4( GlobalDecl:4( x) AssignmentStmt:5( @@ -374,13 +374,13 @@ def g(): MypyFile:1( FuncDef:1( g - Block:1( + Block:2( AssignmentStmt:2( NameExpr(x* [l]) NameExpr(None [builtins.None])) FuncDef:3( f - Block:3( + Block:4( NonlocalDecl:4( x) AssignmentStmt:5( @@ -400,7 +400,7 @@ MypyFile:1( FuncDef:1( f def () - Block:1( + Block:2( AssignmentStmt:2( NameExpr(a* [l]) IntExpr(0)) @@ -422,7 +422,7 @@ def g(): MypyFile:1( FuncDef:1( g - Block:1( + Block:2( AssignmentStmt:2( TupleExpr:2( NameExpr(x* [l]) @@ -434,7 +434,7 @@ MypyFile:1( f Args( Var(z)) - Block:3( + Block:4( NonlocalDecl:4( x y) @@ -453,12 +453,12 @@ MypyFile:1( f Args( Var(x)) - Block:1( + Block:2( FuncDef:2( g Args( Var(y)) - Block:2( + Block:3( AssignmentStmt:3( NameExpr(z* [l]) OpExpr:3( @@ -478,10 +478,10 @@ MypyFile:1( f Args( Var(x)) - Block:1( + Block:2( FuncDef:2( g - Block:2( + Block:3( AssignmentStmt:3( NameExpr(x* [l]) IntExpr(1))))))) diff --git a/test-data/unit/semanal-classes.test b/test-data/unit/semanal-classes.test index 86f8b8656fb6..951791e23490 100644 --- a/test-data/unit/semanal-classes.test +++ b/test-data/unit/semanal-classes.test @@ -27,7 +27,7 @@ MypyFile:1( Args( Var(self) Var(x)) - Block:2( + Block:3( AssignmentStmt:3( NameExpr(y* [l]) NameExpr(x [l])))) @@ -35,7 +35,7 @@ MypyFile:1( f Args( Var(self)) - Block:4( + Block:5( AssignmentStmt:5( NameExpr(y* [l]) NameExpr(self [l])))))) @@ -53,7 +53,7 @@ MypyFile:1( __init__ Args( Var(self)) - Block:2( + Block:3( AssignmentStmt:3( MemberExpr:3( NameExpr(self [l]) @@ -79,7 +79,7 @@ MypyFile:1( f Args( Var(self)) - Block:2( + Block:3( AssignmentStmt:3( MemberExpr:3( NameExpr(self [l]) @@ -89,7 +89,7 @@ MypyFile:1( __init__ Args( Var(self)) - Block:4( + Block:5( AssignmentStmt:5( MemberExpr:5( NameExpr(self [l]) @@ -113,7 +113,7 @@ MypyFile:1( Args( Var(x) Var(self)) - Block:2( + Block:3( AssignmentStmt:3( MemberExpr:3( NameExpr(self [l]) @@ -125,7 +125,7 @@ MypyFile:1( __init__ Args( Var(x)) - Block:5( + Block:6( AssignmentStmt:6( NameExpr(self* [l]) NameExpr(x [l])) @@ -147,7 +147,7 @@ MypyFile:1( __init__ Args( Var(x)) - Block:2( + Block:3( AssignmentStmt:3( MemberExpr:3( NameExpr(x [l]) @@ -167,7 +167,7 @@ MypyFile:1( __init__ Args( Var(self)) - Block:2( + Block:3( AssignmentStmt:3( MemberExpr:3( NameExpr(self [l]) @@ -309,7 +309,7 @@ MypyFile:1( ListExpr:2( IntExpr(1) IntExpr(2)) - Block:2( + Block:3( AssignmentStmt:3( NameExpr(y* [m]) NameExpr(x [__main__.A.x])))))) @@ -322,7 +322,7 @@ def f(): MypyFile:1( FuncDef:1( f - Block:1( + Block:2( ClassDef:2( A PassStmt:2()) @@ -369,7 +369,7 @@ MypyFile:1( FuncDef:1( f def () - Block:1( + Block:2( ClassDef:2( A PassStmt:2()) @@ -390,7 +390,7 @@ MypyFile:1( f Args( Var(x)) - Block:1( + Block:2( ClassDef:2( A AssignmentStmt:3( @@ -400,7 +400,7 @@ MypyFile:1( g Args( Var(self)) - Block:4( + Block:5( AssignmentStmt:5( NameExpr(z* [l]) NameExpr(x [l])))))))) diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index a4ed905dcb9f..269536f868a4 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -361,84 +361,84 @@ main:2: error: "yield" outside function [case testInvalidLvalues1] 1 = 1 [out] -main:1: error: can't assign to literal +main:1: error: cannot assign to literal [out version>=3.10] -main:1: error: can't assign to literal here. Maybe you meant '==' instead of '='? +main:1: error: cannot assign to literal here. Maybe you meant '==' instead of '='? [case testInvalidLvalues2] (1) = 1 [out] -main:1: error: can't assign to literal +main:1: error: cannot assign to literal [out version>=3.10] -main:1: error: can't assign to literal here. Maybe you meant '==' instead of '='? +main:1: error: cannot assign to literal here. Maybe you meant '==' instead of '='? [case testInvalidLvalues3] (1, 1) = 1 [out] -main:1: error: can't assign to literal +main:1: error: cannot assign to literal [case testInvalidLvalues4] [1, 1] = 1 [out] -main:1: error: can't assign to literal +main:1: error: cannot assign to literal [case testInvalidLvalues6] x = y = z = 1 # ok x, (y, 1) = 1 [out] -main:2: error: can't assign to literal +main:2: error: cannot assign to literal [case testInvalidLvalues7] x, [y, 1] = 1 [out] -main:1: error: can't assign to literal +main:1: error: cannot assign to literal [case testInvalidLvalues8] x, [y, [z, 1]] = 1 [out] -main:1: error: can't assign to literal +main:1: error: cannot assign to literal [case testInvalidLvalues9] x, (y) = 1 # ok x, (y, (z, z)) = 1 # ok x, (y, (z, 1)) = 1 [out] -main:3: error: can't assign to literal +main:3: error: cannot assign to literal [case testInvalidLvalues10] x + x = 1 [out] -main:1: error: can't assign to operator +main:1: error: cannot assign to operator [out version>=3.10] -main:1: error: can't assign to expression here. Maybe you meant '==' instead of '='? +main:1: error: cannot assign to expression here. Maybe you meant '==' instead of '='? [case testInvalidLvalues11] -x = 1 [out] -main:1: error: can't assign to operator +main:1: error: cannot assign to operator [out version>=3.10] -main:1: error: can't assign to expression here. Maybe you meant '==' instead of '='? +main:1: error: cannot assign to expression here. Maybe you meant '==' instead of '='? [case testInvalidLvalues12] 1.1 = 1 [out] -main:1: error: can't assign to literal +main:1: error: cannot assign to literal [out version>=3.10] -main:1: error: can't assign to literal here. Maybe you meant '==' instead of '='? +main:1: error: cannot assign to literal here. Maybe you meant '==' instead of '='? [case testInvalidLvalues13] 'x' = 1 [out] -main:1: error: can't assign to literal +main:1: error: cannot assign to literal [out version>=3.10] -main:1: error: can't assign to literal here. Maybe you meant '==' instead of '='? +main:1: error: cannot assign to literal here. Maybe you meant '==' instead of '='? [case testInvalidLvalues14] x() = 1 [out] -main:1: error: can't assign to function call +main:1: error: cannot assign to function call [out version>=3.10] -main:1: error: can't assign to function call here. Maybe you meant '==' instead of '='? +main:1: error: cannot assign to function call here. Maybe you meant '==' instead of '='? [case testTwoStarExpressions] a, *b, *c = 1 @@ -480,18 +480,19 @@ c = 1 d = 1 a = *b [out] -main:4: error: Can use starred expression only as assignment target +main:4: error: can't use starred expression here [case testStarExpressionInExp] a = 1 *a + 1 [out] -main:2: error: Can use starred expression only as assignment target +main:2: error: can't use starred expression here [case testInvalidDel1] x = 1 -del x(1) # E: can't delete function call +del x(1) [out] +main:2: error: cannot delete function call [case testInvalidDel2] x = 1 @@ -503,7 +504,6 @@ main:2: error: cannot delete expression [case testInvalidDel3] del z # E: Name "z" is not defined -[out] [case testFunctionTvarScope] @@ -810,8 +810,8 @@ class C(Generic[t]): pass cast(str + str, None) # E: Cast target is not a type cast(C[str][str], None) # E: Cast target is not a type cast(C[str + str], None) # E: Cast target is not a type -cast([int, str], None) # E: Bracketed expression "[...]" is not valid as a type \ - # N: Did you mean "List[...]"? +cast([int], None) # E: Bracketed expression "[...]" is not valid as a type \ + # N: Did you mean "List[...]"? [out] [case testInvalidCastTargetType] @@ -859,8 +859,8 @@ Any(arg=str) # E: Any(...) is no longer supported. Use cast(Any, ...) instead [case testTypeListAsType] -def f(x:[int, str]) -> None: # E: Bracketed expression "[...]" is not valid as a type \ - # N: Did you mean "List[...]"? +def f(x: [int]) -> None: # E: Bracketed expression "[...]" is not valid as a type \ + # N: Did you mean "List[...]"? pass [out] @@ -897,9 +897,9 @@ import typing def f(): pass f() = 1 # type: int [out] -main:3: error: can't assign to function call +main:3: error: cannot assign to function call [out version>=3.10] -main:3: error: can't assign to function call here. Maybe you meant '==' instead of '='? +main:3: error: cannot assign to function call here. Maybe you meant '==' instead of '='? [case testIndexedAssignmentWithTypeDeclaration] import typing @@ -1241,6 +1241,12 @@ class A: @property # OK @dec def g(self) -> int: pass + @dec # type: ignore[misc] + @property + def h(self) -> int: pass + @dec # type: ignore[prop-decorator] + @property + def i(self) -> int: pass [builtins fixtures/property.pyi] [out] @@ -1275,8 +1281,9 @@ main:2: note: Did you forget to import it from "typing"? (Suggestion: "from typi [case testInvalidWithTarget] def f(): pass -with f() as 1: pass # E: can't assign to literal +with f() as 1: pass [out] +main:2: error: cannot assign to literal [case testInvalidTypeAnnotation] import typing @@ -1290,9 +1297,9 @@ import typing def f() -> None: f() = 1 # type: int [out] -main:3: error: can't assign to function call +main:3: error: cannot assign to function call [out version>=3.10] -main:3: error: can't assign to function call here. Maybe you meant '==' instead of '='? +main:3: error: cannot assign to function call here. Maybe you meant '==' instead of '='? [case testInvalidReferenceToAttributeOfOuterClass] class A: @@ -1453,10 +1460,10 @@ from typing import Tuple heterogenous_tuple: Tuple[Unpack[Tuple[int, str]]] homogenous_tuple: Tuple[Unpack[Tuple[int, ...]]] -bad: Tuple[Unpack[int]] # E: builtins.int cannot be unpacked (must be tuple or TypeVarTuple) +bad: Tuple[Unpack[int]] # E: "int" cannot be unpacked (must be tuple or TypeVarTuple) [builtins fixtures/tuple.pyi] -[case testTypeVarTuple] +[case testTypeVarTupleErrors] from typing import Generic from typing_extensions import TypeVarTuple, Unpack @@ -1465,19 +1472,19 @@ TVariadic2 = TypeVarTuple('TVariadic2') TP = TypeVarTuple('?') # E: String argument 1 "?" to TypeVarTuple(...) does not match variable name "TP" TP2: int = TypeVarTuple('TP2') # E: Cannot declare the type of a TypeVar or similar construct TP3 = TypeVarTuple() # E: Too few arguments for TypeVarTuple() -TP4 = TypeVarTuple('TP4', 'TP4') # E: Only the first argument to TypeVarTuple has defined semantics +TP4 = TypeVarTuple('TP4', 'TP4') # E: Too many positional arguments for "TypeVarTuple" TP5 = TypeVarTuple(t='TP5') # E: TypeVarTuple() expects a string literal as first argument +TP6 = TypeVarTuple('TP6', bound=int) # E: Unexpected keyword argument "bound" for "TypeVarTuple" x: TVariadic # E: TypeVarTuple "TVariadic" is unbound -y: Unpack[TVariadic] # E: TypeVarTuple "TVariadic" is unbound +y: Unpack[TVariadic] # E: Unpack is only valid in a variadic position class Variadic(Generic[Unpack[TVariadic], Unpack[TVariadic2]]): # E: Can only use one type var tuple in a class def pass -# TODO: this should generate an error -#def bad_args(*args: TVariadic): -# pass +def bad_args(*args: TVariadic): # E: TypeVarTuple "TVariadic" is only valid with an unpack + pass def bad_kwargs(**kwargs: Unpack[TVariadic]): # E: Unpack item in ** argument must be a TypedDict pass diff --git a/test-data/unit/semanal-expressions.test b/test-data/unit/semanal-expressions.test index fa07e533a842..4c9baf6b1b75 100644 --- a/test-data/unit/semanal-expressions.test +++ b/test-data/unit/semanal-expressions.test @@ -212,7 +212,7 @@ MypyFile:1( Args( Var(a)) def (a: Any) - Block:1( + Block:2( ExpressionStmt:2( ListComprehension:2( GeneratorExpr:2( diff --git a/test-data/unit/semanal-lambda.test b/test-data/unit/semanal-lambda.test index 1cde1a794dc2..cc2307b97217 100644 --- a/test-data/unit/semanal-lambda.test +++ b/test-data/unit/semanal-lambda.test @@ -5,7 +5,7 @@ def g(): MypyFile:1( FuncDef:1( g - Block:1( + Block:2( ReturnStmt:2( LambdaExpr:2( Args( @@ -52,7 +52,7 @@ def g(): MypyFile:1( FuncDef:1( g - Block:1( + Block:2( ReturnStmt:2( LambdaExpr:2( Block:2( diff --git a/test-data/unit/semanal-modules.test b/test-data/unit/semanal-modules.test index bc381293161f..d52dd953aea2 100644 --- a/test-data/unit/semanal-modules.test +++ b/test-data/unit/semanal-modules.test @@ -77,9 +77,9 @@ MypyFile:1( [case testImportMultiple] import _m, _n _m.x, _n.y -[file _m.py] +[fixture _m.py] x = 1 -[file _n.py] +[fixture _n.py] y = 2 [out] MypyFile:1( @@ -96,7 +96,7 @@ MypyFile:1( [case testImportAs] import _m as n n.x -[file _m.py] +[fixture _m.py] x = 1 [out] MypyFile:1( @@ -109,7 +109,7 @@ MypyFile:1( [case testImportFromMultiple] from _m import x, y x, y -[file _m.py] +[fixture _m.py] x = y = 1 [out] MypyFile:1( @@ -122,7 +122,7 @@ MypyFile:1( [case testImportFromAs] from _m import y as z z -[file _m.py] +[fixture _m.py] y = 1 [out] MypyFile:1( @@ -135,7 +135,7 @@ from m import x y = x [file m.py] from _n import x -[file _n.py] +[fixture _n.py] x = 1 [out] MypyFile:1( @@ -150,9 +150,9 @@ MypyFile:1( [case testAccessImportedName2] import _m y = _m.x -[file _m.py] +[fixture _m.py] from _n import x -[file _n.py] +[fixture _n.py] x = 1 [out] MypyFile:1( @@ -166,9 +166,9 @@ MypyFile:1( [case testAccessingImportedNameInType] from _m import c x = None # type: c -[file _m.py] +[fixture _m.py] from _n import c -[file _n.py] +[fixture _n.py] class c: pass [out] MypyFile:1( @@ -181,9 +181,9 @@ MypyFile:1( [case testAccessingImportedNameInType2] import _m x = None # type: _m.c -[file _m.py] +[fixture _m.py] from _n import c -[file _n.py] +[fixture _n.py] class c: pass [out] MypyFile:1( @@ -196,9 +196,9 @@ MypyFile:1( [case testAccessingImportedModule] from _m import _n _n.x -[file _m.py] +[fixture _m.py] import _n -[file _n.py] +[fixture _n.py] x = 1 [out] MypyFile:1( @@ -211,9 +211,9 @@ MypyFile:1( [case testAccessingImportedModule2] import _m _m._n.x -[file _m.py] +[fixture _m.py] import _n -[file _n.py] +[fixture _n.py] x = 1 [out] MypyFile:1( @@ -228,9 +228,9 @@ MypyFile:1( [case testAccessTypeViaDoubleIndirection] from _m import c a = None # type: c -[file _m.py] +[fixture _m.py] from _n import c -[file _n.py] +[fixture _n.py] class c: pass [out] MypyFile:1( @@ -243,9 +243,9 @@ MypyFile:1( [case testAccessTypeViaDoubleIndirection2] import _m a = None # type: _m.c -[file _m.py] +[fixture _m.py] from _n import c -[file _n.py] +[fixture _n.py] class c: pass [out] MypyFile:1( @@ -258,7 +258,7 @@ MypyFile:1( [case testImportAsterisk] from _m import * x, y -[file _m.py] +[fixture _m.py] x = y = 1 [out] MypyFile:1( @@ -271,10 +271,10 @@ MypyFile:1( [case testImportAsteriskAndImportedNames] from _m import * n_.x, y -[file _m.py] +[fixture _m.py] import n_ from n_ import y -[file n_.py] +[fixture n_.py] x = y = 1 [out] MypyFile:1( @@ -290,10 +290,10 @@ MypyFile:1( from _m import * x = None # type: n_.c y = None # type: d -[file _m.py] +[fixture _m.py] import n_ from n_ import d -[file n_.py] +[fixture n_.py] class c: pass class d: pass [out] @@ -311,7 +311,7 @@ MypyFile:1( [case testModuleInSubdir] import _m _m.x -[file _m/__init__.py] +[fixture _m/__init__.py] x = 1 [out] MypyFile:1( @@ -324,7 +324,7 @@ MypyFile:1( [case testNestedModules] import m.n m.n.x, m.y -[file m/__init__.py] +[fixture m/__init__.py] y = 1 [file m/n.py] x = 1 @@ -351,8 +351,8 @@ MypyFile:1( [case testImportFromSubmodule] from m._n import x x -[file m/__init__.py] -[file m/_n.py] +[fixture m/__init__.py] +[fixture m/_n.py] x = 1 [out] MypyFile:1( @@ -363,8 +363,8 @@ MypyFile:1( [case testImportAllFromSubmodule] from m._n import * x, y -[file m/__init__.py] -[file m/_n.py] +[fixture m/__init__.py] +[fixture m/_n.py] x = y = 1 [out] MypyFile:1( @@ -377,8 +377,8 @@ MypyFile:1( [case testSubmodulesAndTypes] import m._n x = None # type: m._n.c -[file m/__init__.py] -[file m/_n.py] +[fixture m/__init__.py] +[fixture m/_n.py] class c: pass [out] MypyFile:1( @@ -391,8 +391,8 @@ MypyFile:1( [case testSubmodulesAndTypes2] from m._n import c x = None # type: c -[file m/__init__.py] -[file m/_n.py] +[fixture m/__init__.py] +[fixture m/_n.py] class c: pass [out] MypyFile:1( @@ -405,8 +405,8 @@ MypyFile:1( [case testFromPackageImportModule] from m import _n _n.x -[file m/__init__.py] -[file m/_n.py] +[fixture m/__init__.py] +[fixture m/_n.py] x = 1 [out] MypyFile:1( @@ -421,9 +421,9 @@ import m.n.k m.n.k.x m.n.b m.a -[file m/__init__.py] +[fixture m/__init__.py] a = 1 -[file m/n/__init__.py] +[fixture m/n/__init__.py] b = 1 [file m/n/k.py] x = 1 @@ -458,10 +458,10 @@ MypyFile:1( [case testImportInSubmodule] import m._n y = m._n.x -[file m/__init__.py] -[file m/_n.py] +[fixture m/__init__.py] +[fixture m/_n.py] from m._k import x -[file m/_k.py] +[fixture m/_k.py] x = 1 [out] MypyFile:1( @@ -494,7 +494,7 @@ MypyFile:1( import _m _m.x = ( _m.x) -[file _m.py] +[fixture _m.py] x = None [out] MypyFile:1( @@ -510,7 +510,7 @@ MypyFile:1( [case testAssignmentThatRefersToModule] import _m _m.x[None] = None -[file _m.py] +[fixture _m.py] x = None [out] MypyFile:1( @@ -527,7 +527,7 @@ MypyFile:1( if 1: import _x _x.y -[file _x.py] +[fixture _x.py] y = 1 [out] MypyFile:1( @@ -545,14 +545,14 @@ MypyFile:1( def f() -> None: import _x _x.y -[file _x.py] +[fixture _x.py] y = 1 [out] MypyFile:1( FuncDef:1( f def () - Block:1( + Block:2( Import:2(_x) ExpressionStmt:3( MemberExpr:3( @@ -563,7 +563,7 @@ MypyFile:1( class A: from _x import y z = y -[file _x.py] +[fixture _x.py] y = 1 [out] MypyFile:1( @@ -578,7 +578,7 @@ MypyFile:1( class A: import _x z = _x.y -[file _x.py] +[fixture _x.py] y = 1 [out] MypyFile:1( @@ -603,7 +603,7 @@ MypyFile:1( FuncDef:1( f def () - Block:1( + Block:2( Import:2(x) Import:3(x) ExpressionStmt:4( @@ -620,7 +620,7 @@ MypyFile:1( [case testRelativeImport0] import m.x m.x.z.y -[file m/__init__.py] +[fixture m/__init__.py] [file m/x.py] from . import z [file m/z.py] @@ -650,12 +650,12 @@ MypyFile:1( import m.t.b as b b.x.y b.z.y -[file m/__init__.py] +[fixture m/__init__.py] [file m/x.py] y = 1 [file m/z.py] y = 3 -[file m/t/__init__.py] +[fixture m/t/__init__.py] [file m/t/b.py] from .. import x, z [out] @@ -693,12 +693,12 @@ MypyFile:1( import m.t.b as b b.xy b.zy -[file m/__init__.py] +[fixture m/__init__.py] [file m/x.py] y = 1 [file m/z.py] y = 3 -[file m/t/__init__.py] +[fixture m/t/__init__.py] [file m/t/b.py] from ..x import y as xy from ..z import y as zy @@ -735,14 +735,14 @@ import m.t m.zy m.xy m.t.y -[file m/__init__.py] +[fixture m/__init__.py] from .x import * from .z import * [file m/x.py] from .z import zy as xy [file m/z.py] zy = 3 -[file m/t/__init__.py] +[fixture m/t/__init__.py] from .b import * [file m/t/b.py] from .. import xy as y @@ -778,7 +778,7 @@ MypyFile:1( [case testRelativeImportFromSameModule] import m.x -[file m/__init__.py] +[fixture m/__init__.py] [file m/x.py] from .x import nonexistent [out] @@ -786,7 +786,7 @@ tmp/m/x.py:1: error: Module "m.x" has no attribute "nonexistent" [case testImportFromSameModule] import m.x -[file m/__init__.py] +[fixture m/__init__.py] [file m/x.py] from m.x import nonexistent [out] @@ -794,7 +794,7 @@ tmp/m/x.py:1: error: Module "m.x" has no attribute "nonexistent" [case testImportMisspellingSingleCandidate] import f -[file m/__init__.py] +[fixture m/__init__.py] [file m/x.py] def some_function(): pass @@ -805,7 +805,7 @@ tmp/f.py:1: error: Module "m.x" has no attribute "somefunction"; maybe "some_fun [case testImportMisspellingMultipleCandidates] import f -[file m/__init__.py] +[fixture m/__init__.py] [file m/x.py] def some_function(): pass @@ -818,7 +818,7 @@ tmp/f.py:1: error: Module "m.x" has no attribute "somefunction"; maybe "some_fun [case testImportMisspellingMultipleCandidatesTruncated] import f -[file m/__init__.py] +[fixture m/__init__.py] [file m/x.py] def some_function(): pass @@ -849,10 +849,10 @@ y = 2 from m_ import * x y -[file m_.py] +[fixture m_.py] from m2_ import x as x from m2_ import y -[file m2_.py] +[fixture m2_.py] x = 1 y = 2 [out] @@ -878,11 +878,11 @@ import m3 from m_ import * m2_ m3_ -[file m_.py] +[fixture m_.py] import m2_ as m2_ import m3_ -[file m2_.py] -[file m3_.py] +[fixture m2_.py] +[fixture m3_.py] [out] MypyFile:1( ImportAll:1(m_) @@ -917,7 +917,7 @@ MypyFile:1( FuncDef:3( f def () - Block:3( + Block:4( ImportFrom:4(x, [a]) ImportFrom:5(x, [a]))) Import:6(x) diff --git a/test-data/unit/semanal-namedtuple.test b/test-data/unit/semanal-namedtuple.test index df1d5679c892..16944391da86 100644 --- a/test-data/unit/semanal-namedtuple.test +++ b/test-data/unit/semanal-namedtuple.test @@ -165,7 +165,7 @@ N = namedtuple('N', ['x', 1]) # E: String literal expected as "namedtuple()" ite [case testNamedTupleWithUnderscoreItemName] from collections import namedtuple -N = namedtuple('N', ['_fallback']) # E: "namedtuple()" field names cannot start with an underscore: _fallback +N = namedtuple('N', ['_fallback']) # E: "namedtuple()" field name "_fallback" starts with an underscore [builtins fixtures/tuple.pyi] -- NOTE: The following code works at runtime but is not yet supported by mypy. @@ -197,7 +197,7 @@ N = NamedTuple('N', 1) # E: List or tuple literal expected as the second argumen [case testTypingNamedTupleWithUnderscoreItemName] from typing import NamedTuple -N = NamedTuple('N', [('_fallback', int)]) # E: "NamedTuple()" field names cannot start with an underscore: _fallback +N = NamedTuple('N', [('_fallback', int)]) # E: "NamedTuple()" field name "_fallback" starts with an underscore [builtins fixtures/tuple.pyi] [case testTypingNamedTupleWithUnexpectedNames] @@ -225,3 +225,23 @@ class B(A): pass [out] main:2: error: Unsupported dynamic base class "NamedTuple" main:2: error: Name "NamedTuple" is not defined + +[case testNamedTupleWithDecorator] +from typing import final, NamedTuple + +@final +class A(NamedTuple("N", [("x", int)])): + pass +[builtins fixtures/tuple.pyi] +[out] +MypyFile:1( + ImportFrom:1(typing, [final, NamedTuple]) + ClassDef:4( + A + TupleType( + Tuple[builtins.int, fallback=__main__.N@4]) + Decorators( + NameExpr(final [typing.final])) + BaseType( + __main__.N@4) + PassStmt:5())) diff --git a/test-data/unit/semanal-python310.test b/test-data/unit/semanal-python310.test index 9418ac2912b2..e96a3ca9d777 100644 --- a/test-data/unit/semanal-python310.test +++ b/test-data/unit/semanal-python310.test @@ -194,7 +194,7 @@ x = 1 match x: case _a.b: pass -[file _a.py] +[fixture _a.py] b = 1 [out] MypyFile:1( diff --git a/test-data/unit/semanal-statements.test b/test-data/unit/semanal-statements.test index 013452068cf1..c143805f4564 100644 --- a/test-data/unit/semanal-statements.test +++ b/test-data/unit/semanal-statements.test @@ -76,7 +76,7 @@ MypyFile:1( IntExpr(1)) WhileStmt:2( NameExpr(x [__main__.x]) - Block:2( + Block:3( ExpressionStmt:3( NameExpr(y [__main__.y]))))) @@ -88,7 +88,7 @@ MypyFile:1( ForStmt:1( NameExpr(x* [__main__.x]) NameExpr(object [builtins.object]) - Block:1( + Block:2( ExpressionStmt:2( NameExpr(x [__main__.x]))))) @@ -100,11 +100,11 @@ def f(): MypyFile:1( FuncDef:1( f - Block:1( + Block:2( ForStmt:2( NameExpr(x* [l]) NameExpr(f [__main__.f]) - Block:2( + Block:3( ExpressionStmt:3( NameExpr(x [l]))))))) @@ -118,7 +118,7 @@ MypyFile:1( NameExpr(x* [__main__.x]) NameExpr(y* [__main__.y])) ListExpr:1() - Block:1( + Block:2( ExpressionStmt:2( TupleExpr:2( NameExpr(x [__main__.x]) @@ -133,7 +133,7 @@ MypyFile:1( ForStmt:1( NameExpr(x* [__main__.x]) ListExpr:1() - Block:1( + Block:2( PassStmt:2())) ExpressionStmt:3( NameExpr(x [__main__.x]))) @@ -147,11 +147,11 @@ def f(): MypyFile:1( FuncDef:1( f - Block:1( + Block:2( ForStmt:2( NameExpr(x* [l]) ListExpr:2() - Block:2( + Block:3( PassStmt:3())) ExpressionStmt:4( NameExpr(x [l]))))) @@ -167,12 +167,12 @@ MypyFile:1( ForStmt:2( NameExpr(x'* [__main__.x']) NameExpr(None [builtins.None]) - Block:2( + Block:3( PassStmt:3())) ForStmt:4( NameExpr(x* [__main__.x]) NameExpr(None [builtins.None]) - Block:4( + Block:5( PassStmt:5()))) [case testReusingForLoopIndexVariable2] @@ -186,16 +186,16 @@ def f(): MypyFile:1( FuncDef:2( f - Block:2( + Block:3( ForStmt:3( NameExpr(x* [l]) NameExpr(None [builtins.None]) - Block:3( + Block:4( PassStmt:4())) ForStmt:5( NameExpr(x'* [l]) NameExpr(None [builtins.None]) - Block:5( + Block:6( PassStmt:6()))))) [case testLoopWithElse] @@ -212,14 +212,14 @@ MypyFile:1( ForStmt:1( NameExpr(x* [__main__.x]) ListExpr:1() - Block:1( + Block:2( PassStmt:2()) Else( ExpressionStmt:4( NameExpr(x [__main__.x])))) WhileStmt:5( IntExpr(1) - Block:5( + Block:6( PassStmt:6()) Else( ExpressionStmt:8( @@ -234,12 +234,12 @@ for x in []: MypyFile:1( WhileStmt:1( IntExpr(1) - Block:1( + Block:2( BreakStmt:2())) ForStmt:3( NameExpr(x* [__main__.x]) ListExpr:3() - Block:3( + Block:4( BreakStmt:4()))) [case testContinue] @@ -251,12 +251,12 @@ for x in []: MypyFile:1( WhileStmt:1( IntExpr(1) - Block:1( + Block:2( ContinueStmt:2())) ForStmt:3( NameExpr(x* [__main__.x]) ListExpr:3() - Block:3( + Block:4( ContinueStmt:4()))) [case testIf] @@ -426,7 +426,7 @@ def f(): MypyFile:1( FuncDef:1( f - Block:1( + Block:2( AssignmentStmt:2( NameExpr(x* [l]) IntExpr(1)) @@ -531,7 +531,7 @@ MypyFile:1( f Args( Var(x)) - Block:1( + Block:2( DelStmt:2( NameExpr(x [l]))))) @@ -545,7 +545,7 @@ MypyFile:1( Args( Var(x) Var(y)) - Block:1( + Block:2( DelStmt:2( TupleExpr:2( NameExpr(x [l]) @@ -557,9 +557,9 @@ MypyFile:1( def f(x, y) -> None: del x, y + 1 [out] -main:2: error: can't delete operator +main:2: error: cannot delete operator [out version>=3.10] -main:2: error: can't delete expression +main:2: error: cannot delete expression [case testTry] class c: pass @@ -579,19 +579,19 @@ MypyFile:1( c PassStmt:1()) TryStmt:2( - Block:2( + Block:3( ExpressionStmt:3( NameExpr(c [__main__.c]))) NameExpr(object [builtins.object]) - Block:4( + Block:5( ExpressionStmt:5( NameExpr(c [__main__.c]))) NameExpr(c [__main__.c]) NameExpr(e* [__main__.e]) - Block:6( + Block:7( ExpressionStmt:7( NameExpr(e [__main__.e]))) - Block:8( + Block:9( ExpressionStmt:9( NameExpr(c [__main__.c]))) Finally( @@ -608,9 +608,9 @@ else: [out] MypyFile:1( TryStmt:1( - Block:1( + Block:2( PassStmt:2()) - Block:3( + Block:4( PassStmt:4()) Else( ExpressionStmt:6( @@ -624,7 +624,7 @@ finally: [out] MypyFile:1( TryStmt:1( - Block:1( + Block:2( PassStmt:2()) Finally( PassStmt:4()))) @@ -641,13 +641,13 @@ MypyFile:1( c PassStmt:1()) TryStmt:2( - Block:2( + Block:3( PassStmt:3()) TupleExpr:4( NameExpr(c [__main__.c]) NameExpr(object [builtins.object])) NameExpr(e* [__main__.e]) - Block:4( + Block:5( ExpressionStmt:5( NameExpr(e [__main__.e]))))) @@ -665,7 +665,7 @@ MypyFile:1( WithStmt:1( Expr( NameExpr(object [builtins.object])) - Block:1( + Block:2( ExpressionStmt:2( NameExpr(object [builtins.object]))))) @@ -679,7 +679,7 @@ MypyFile:1( NameExpr(object [builtins.object])) Target( NameExpr(x* [__main__.x])) - Block:1( + Block:2( ExpressionStmt:2( NameExpr(x [__main__.x]))))) @@ -691,13 +691,13 @@ def f(): MypyFile:1( FuncDef:1( f - Block:1( + Block:2( WithStmt:2( Expr( NameExpr(f [__main__.f])) Target( NameExpr(x* [l])) - Block:2( + Block:3( ExpressionStmt:3( NameExpr(x [l]))))))) @@ -713,7 +713,7 @@ MypyFile:1( NameExpr(object [builtins.object])) Expr( NameExpr(object [builtins.object])) - Block:1( + Block:2( PassStmt:2())) WithStmt:3( Expr( @@ -724,7 +724,7 @@ MypyFile:1( NameExpr(object [builtins.object])) Target( NameExpr(b* [__main__.b])) - Block:3( + Block:4( PassStmt:4()))) [case testVariableInBlock] @@ -736,7 +736,7 @@ while object: MypyFile:1( WhileStmt:1( NameExpr(object [builtins.object]) - Block:1( + Block:2( AssignmentStmt:2( NameExpr(x* [__main__.x]) NameExpr(None [builtins.None])) @@ -757,11 +757,11 @@ except object as o: [out] MypyFile:1( TryStmt:1( - Block:1( + Block:2( PassStmt:2()) NameExpr(object [builtins.object]) NameExpr(o* [__main__.o]) - Block:3( + Block:4( AssignmentStmt:4( NameExpr(x* [__main__.x]) NameExpr(None [builtins.None])) @@ -777,11 +777,11 @@ except object as o: [out] MypyFile:1( TryStmt:1( - Block:1( + Block:2( PassStmt:2()) NameExpr(object [builtins.object]) NameExpr(o* [__main__.o]) - Block:3( + Block:4( AssignmentStmt:4( NameExpr(o [__main__.o]) CallExpr:4( @@ -806,15 +806,15 @@ MypyFile:1( builtins.BaseException) PassStmt:1()) TryStmt:2( - Block:2( + Block:3( PassStmt:3()) NameExpr(BaseException [builtins.BaseException]) NameExpr(e* [__main__.e]) - Block:4( + Block:5( PassStmt:5()) NameExpr(Err [__main__.Err]) NameExpr(f* [__main__.f]) - Block:6( + Block:7( AssignmentStmt:7( NameExpr(f [__main__.f]) CallExpr:7( @@ -860,7 +860,7 @@ MypyFile:1( NameExpr(decorate [__main__.decorate]) FuncDef:3( g - Block:3( + Block:4( ExpressionStmt:4( CallExpr:4( NameExpr(g [__main__.g]) @@ -877,13 +877,13 @@ MypyFile:1( FuncDef:1( f def () - Block:1( + Block:2( TryStmt:2( - Block:2( + Block:3( PassStmt:3()) NameExpr(object [builtins.object]) NameExpr(o* [l]) - Block:4( + Block:5( PassStmt:5()))))) [case testReuseExceptionVariable] @@ -899,17 +899,17 @@ MypyFile:1( FuncDef:1( f def () - Block:1( + Block:2( TryStmt:2( - Block:2( + Block:3( PassStmt:3()) NameExpr(object [builtins.object]) NameExpr(o* [l]) - Block:4( + Block:5( PassStmt:5()) NameExpr(object [builtins.object]) NameExpr(o [l]) - Block:6( + Block:7( PassStmt:7()))))) [case testWithMultiple] @@ -924,11 +924,11 @@ MypyFile:1( f Args( Var(a)) - Block:1( + Block:2( PassStmt:2())) FuncDef:3( main - Block:3( + Block:4( WithStmt:4( Expr( CallExpr:4( @@ -944,7 +944,7 @@ MypyFile:1( NameExpr(a [l])))) Target( NameExpr(b* [l])) - Block:4( + Block:5( AssignmentStmt:5( NameExpr(x* [l]) TupleExpr:5( @@ -1030,7 +1030,7 @@ MypyFile:1( f Args( Var(a)) - Block:2( + Block:3( ExpressionStmt:3( CallExpr:3( NameExpr(f [__main__.f]) @@ -1082,7 +1082,7 @@ MypyFile:1( IntExpr(0)) Target( NameExpr(y'* [__main__.y'])) - Block:1( + Block:2( AssignmentStmt:2( NameExpr(z* [__main__.z]) NameExpr(y' [__main__.y'])))) @@ -1091,7 +1091,7 @@ MypyFile:1( IntExpr(1)) Target( NameExpr(y* [__main__.y])) - Block:3( + Block:4( AssignmentStmt:4( NameExpr(y [__main__.y]) IntExpr(1))))) @@ -1109,7 +1109,7 @@ MypyFile:1( IntExpr(0)) Target( NameExpr(y* [__main__.y])) - Block:1( + Block:2( AssignmentStmt:2( NameExpr(z* [__main__.z]) NameExpr(y [__main__.y])))) @@ -1121,7 +1121,7 @@ MypyFile:1( IntExpr(1)) Target( NameExpr(y [__main__.y])) - Block:4( + Block:5( AssignmentStmt:5( NameExpr(y [__main__.y]) IntExpr(1))))) diff --git a/test-data/unit/semanal-symtable.test b/test-data/unit/semanal-symtable.test index c886080557b0..1622fd1f1ad4 100644 --- a/test-data/unit/semanal-symtable.test +++ b/test-data/unit/semanal-symtable.test @@ -78,10 +78,6 @@ __main__: non_existing2 : Gdef/Var (__main__.non_existing2) : Any non_existing3 : Gdef/Var (__main__.non_existing3) : Any non_existing4 : Gdef/Var (__main__.non_existing4) : Any) -sys: - SymbolTable( - platform : Gdef/Var (sys.platform) : builtins.str - version_info : Gdef/Var (sys.version_info)) [case testDecorator] from typing import Callable diff --git a/test-data/unit/semanal-typealiases.test b/test-data/unit/semanal-typealiases.test index debc7ecdf722..88d234134350 100644 --- a/test-data/unit/semanal-typealiases.test +++ b/test-data/unit/semanal-typealiases.test @@ -92,7 +92,7 @@ import typing import _m A2 = _m.A x = 1 # type: A2 -[file _m.py] +[fixture _m.py] import typing class A: pass [out] @@ -255,7 +255,7 @@ MypyFile:1( import typing from _m import U def f(x: U) -> None: pass -[file _m.py] +[fixture _m.py] from typing import Union class A: pass U = Union[int, A] @@ -275,7 +275,7 @@ MypyFile:1( import typing import _m def f(x: _m.U) -> None: pass -[file _m.py] +[fixture _m.py] from typing import Union class A: pass U = Union[int, A] @@ -295,7 +295,7 @@ MypyFile:1( import typing from _m import A def f(x: A) -> None: pass -[file _m.py] +[fixture _m.py] import typing A = int [out] @@ -314,7 +314,7 @@ MypyFile:1( import typing import _m def f(x: _m.A) -> None: pass -[file _m.py] +[fixture _m.py] import typing A = int [out] @@ -385,7 +385,7 @@ from typing import Union from _m import U U2 = U x = 1 # type: U2 -[file _m.py] +[fixture _m.py] from typing import Union U = Union[int, str] [out] diff --git a/test-data/unit/semanal-types.test b/test-data/unit/semanal-types.test index 494d701b758a..83c44738f055 100644 --- a/test-data/unit/semanal-types.test +++ b/test-data/unit/semanal-types.test @@ -31,7 +31,7 @@ MypyFile:1( PassStmt:1()) FuncDef:2( f - Block:2( + Block:3( AssignmentStmt:3( NameExpr(x [l]) NameExpr(None [builtins.None]) @@ -69,7 +69,7 @@ MypyFile:1( __init__ Args( Var(self)) - Block:3( + Block:4( AssignmentStmt:4( MemberExpr:4( NameExpr(self [l]) @@ -120,7 +120,7 @@ MypyFile:1( Var(x) Var(y)) def (x: Any, y: __main__.A) - Block:4( + Block:5( AssignmentStmt:5( NameExpr(z* [l]) TupleExpr:5( @@ -255,7 +255,7 @@ MypyFile:1( IntExpr(1)) FuncDef:6( f - Block:6( + Block:7( AssignmentStmt:7( NameExpr(b [l]) NameExpr(None [builtins.None]) @@ -303,7 +303,7 @@ MypyFile:1( import typing import _m typing.cast(_m.C, object) -[file _m.py] +[fixture _m.py] class C: pass [out] MypyFile:1( @@ -318,8 +318,8 @@ MypyFile:1( import typing import _m._n typing.cast(_m._n.C, object) -[file _m/__init__.py] -[file _m/_n.py] +[fixture _m/__init__.py] +[fixture _m/_n.py] class C: pass [out] MypyFile:1( @@ -417,7 +417,7 @@ MypyFile:1( Args( Var(x)) def [t] (x: t`-1) - Block:3( + Block:4( AssignmentStmt:4( NameExpr(y [l]) NameExpr(None [builtins.None]) @@ -591,7 +591,7 @@ MypyFile:1( FuncDef:3( f def () - Block:3( + Block:4( FuncDef:4( g def [t] () -> t`-1 @@ -841,7 +841,7 @@ MypyFile:1( ImportFrom:1(typing, [overload]) FuncDef:2( f - Block:2( + Block:3( OverloadedFuncDef:3( FuncDef:8( g @@ -1043,7 +1043,7 @@ MypyFile:1( default( Var(y) StrExpr())) - def (*x: builtins.int, *, y: builtins.str =) -> Any + def (*x: builtins.int, y: builtins.str =) -> Any VarArg( Var(x)) Block:1( @@ -1113,7 +1113,7 @@ MypyFile:1( ImportFrom:1(typing, [TypeVar]) FuncDef:2( f - Block:2( + Block:3( AssignmentStmt:3( NameExpr(T* [l]) TypeVarExpr:3()) @@ -1152,7 +1152,7 @@ from typing import Generic from _m import T class A(Generic[T]): y = None # type: T -[file _m.py] +[fixture _m.py] from typing import TypeVar T = TypeVar('T') [out] @@ -1175,7 +1175,7 @@ class A(Generic[_m.T]): a = None # type: _m.T def f(self, x: _m.T): b = None # type: _m.T -[file _m.py] +[fixture _m.py] from typing import TypeVar T = TypeVar('T') [out] @@ -1196,7 +1196,7 @@ MypyFile:1( Var(self) Var(x)) def (self: __main__.A[_m.T`1], x: _m.T`1) -> Any - Block:5( + Block:6( AssignmentStmt:6( NameExpr(b [l]) NameExpr(None [builtins.None]) @@ -1206,7 +1206,7 @@ MypyFile:1( import _m def f(x: _m.T) -> None: a = None # type: _m.T -[file _m.py] +[fixture _m.py] from typing import TypeVar T = TypeVar('T') [out] @@ -1217,7 +1217,7 @@ MypyFile:1( Args( Var(x)) def [_m.T] (x: _m.T`-1) - Block:2( + Block:3( AssignmentStmt:3( NameExpr(a [l]) NameExpr(None [builtins.None]) @@ -1235,7 +1235,7 @@ MypyFile:1( Args( Var(x)) def (x: builtins.int) -> Any - Block:2( + Block:3( AssignmentStmt:3( NameExpr(x [l]) IntExpr(1))))) @@ -1256,7 +1256,7 @@ MypyFile:1( Var(self) Var(x)) def (self: __main__.A, x: builtins.int) -> builtins.str - Block:3( + Block:4( AssignmentStmt:4( NameExpr(x [l]) IntExpr(1)))))) @@ -1559,8 +1559,8 @@ MypyFile:1( ImportFrom:1(typing_extensions, [TypeVarTuple]) AssignmentStmt:2( NameExpr(TV* [__main__.TV]) - TypeVarTupleExpr:2())) - + TypeVarTupleExpr:2( + UpperBound(builtins.tuple[builtins.object, ...])))) [builtins fixtures/tuple.pyi] [case testTypeVarTupleCallable] @@ -1576,13 +1576,14 @@ MypyFile:1( ImportFrom:2(typing, [Callable]) AssignmentStmt:3( NameExpr(Ts* [__main__.Ts]) - TypeVarTupleExpr:3()) + TypeVarTupleExpr:3( + UpperBound(builtins.tuple[builtins.object, ...]))) FuncDef:5( foo Args( Var(x)) def [Ts] (x: def (*Unpack[Ts`-1])) - Block:5( + Block:6( PassStmt:6()))) [builtins fixtures/tuple.pyi] diff --git a/test-data/unit/stubgen.test b/test-data/unit/stubgen.test index 8e4285b7de2e..53baa2c0ca06 100644 --- a/test-data/unit/stubgen.test +++ b/test-data/unit/stubgen.test @@ -11,6 +11,11 @@ def f() -> None: ... [case testTwoFunctions] def f(a, b): + """ + this is a docstring + + more. + """ x = 1 def g(arg): pass @@ -22,35 +27,47 @@ def g(arg) -> None: ... def f(a, b=2): ... def g(b=-1, c=0): ... [out] -def f(a, b: int = ...) -> None: ... -def g(b: int = ..., c: int = ...) -> None: ... +def f(a, b: int = 2) -> None: ... +def g(b: int = -1, c: int = 0) -> None: ... [case testDefaultArgNone] def f(x=None): ... [out] from _typeshed import Incomplete -def f(x: Incomplete | None = ...) -> None: ... +def f(x: Incomplete | None = None) -> None: ... [case testDefaultArgBool] def f(x=True, y=False): ... [out] -def f(x: bool = ..., y: bool = ...) -> None: ... +def f(x: bool = True, y: bool = False) -> None: ... + +[case testDefaultArgBool_inspect] +def f(x=True, y=False): ... +[out] +def f(x: bool = ..., y: bool = ...): ... [case testDefaultArgStr] +def f(x='foo',y="how's quotes"): ... +[out] +def f(x: str = 'foo', y: str = "how's quotes") -> None: ... + +[case testDefaultArgStr_inspect] def f(x='foo'): ... [out] -def f(x: str = ...) -> None: ... +def f(x: str = ...): ... [case testDefaultArgBytes] -def f(x=b'foo'): ... +def f(x=b'foo',y=b"what's up",z=b'\xc3\xa0 la une'): ... [out] -def f(x: bytes = ...) -> None: ... +def f(x: bytes = b'foo', y: bytes = b"what's up", z: bytes = b'\xc3\xa0 la une') -> None: ... [case testDefaultArgFloat] -def f(x=1.2): ... +def f(x=1.2,y=1e-6,z=0.0,w=-0.0,v=+1.0): ... +def g(x=float("nan"), y=float("inf"), z=float("-inf")): ... [out] -def f(x: float = ...) -> None: ... +def f(x: float = 1.2, y: float = 1e-06, z: float = 0.0, w: float = -0.0, v: float = +1.0) -> None: ... +def g(x=..., y=..., z=...) -> None: ... [case testDefaultArgOther] def f(x=ord): ... @@ -111,10 +128,10 @@ def i(a, *, b=1): ... def j(a, *, b=1, **c): ... [out] def f(a, *b, **c) -> None: ... -def g(a, *b, c: int = ...) -> None: ... -def h(a, *b, c: int = ..., **d) -> None: ... -def i(a, *, b: int = ...) -> None: ... -def j(a, *, b: int = ..., **c) -> None: ... +def g(a, *b, c: int = 1) -> None: ... +def h(a, *b, c: int = 1, **d) -> None: ... +def i(a, *, b: int = 1) -> None: ... +def j(a, *, b: int = 1, **c) -> None: ... [case testClass] class A: @@ -127,10 +144,52 @@ class A: def g() -> None: ... -[case testVariable] -x = 1 +[case testVariables] +i = 1 +s = 'a' +f = 1.5 +c1 = 1j +c2 = 0j + 1 +bl1 = True +bl2 = False +bts = b'' +[out] +i: int +s: str +f: float +c1: complex +c2: complex +bl1: bool +bl2: bool +bts: bytes + +[case testVariablesWithUnary] +i = +-1 +f = -1.5 +c1 = -1j +c2 = -1j + 1 +bl1 = not True +bl2 = not not False +[out] +i: int +f: float +c1: complex +c2: complex +bl1: bool +bl2: bool + +[case testVariablesWithUnaryWrong] +i = not +1 +bl1 = -True +bl2 = not -False +bl3 = -(not False) [out] -x: int +from _typeshed import Incomplete + +i: Incomplete +bl1: Incomplete +bl2: Incomplete +bl3: Incomplete [case testAnnotatedVariable] x: int = 1 @@ -258,6 +317,7 @@ __all__ = [] __author__ = '' __version__ = '' [out] +__version__: str [case testBaseClass] class A: ... @@ -298,8 +358,8 @@ y: Incomplete def f(x, *, y=1): ... def g(x, *, y=1, z=2): ... [out] -def f(x, *, y: int = ...) -> None: ... -def g(x, *, y: int = ..., z: int = ...) -> None: ... +def f(x, *, y: int = 1) -> None: ... +def g(x, *, y: int = 1, z: int = 2) -> None: ... [case testProperty] class A: @@ -308,6 +368,30 @@ class A: return 1 @f.setter def f(self, x): ... + @f.deleter + def f(self): ... + + def h(self): + self.f = 1 +[out] +class A: + @property + def f(self): ... + @f.setter + def f(self, x) -> None: ... + @f.deleter + def f(self) -> None: ... + def h(self) -> None: ... + +[case testProperty_semanal] +class A: + @property + def f(self): + return 1 + @f.setter + def f(self, x): ... + @f.deleter + def f(self): ... def h(self): self.f = 1 @@ -317,8 +401,84 @@ class A: def f(self): ... @f.setter def f(self, x) -> None: ... + @f.deleter + def f(self) -> None: ... def h(self) -> None: ... +-- a read/write property is treated the same as an attribute +[case testProperty_inspect] +class A: + @property + def f(self): + return 1 + @f.setter + def f(self, x): ... + + def h(self): + self.f = 1 +[out] +from _typeshed import Incomplete + +class A: + f: Incomplete + def h(self): ... + +[case testFunctoolsCachedProperty] +import functools + +class A: + @functools.cached_property + def x(self): + return 'x' +[out] +import functools + +class A: + @functools.cached_property + def x(self): ... + +[case testFunctoolsCachedPropertyAlias] +import functools as ft + +class A: + @ft.cached_property + def x(self): + return 'x' +[out] +import functools as ft + +class A: + @ft.cached_property + def x(self): ... + +[case testCachedProperty] +from functools import cached_property + +class A: + @cached_property + def x(self): + return 'x' +[out] +from functools import cached_property + +class A: + @cached_property + def x(self): ... + +[case testCachedPropertyAlias] +from functools import cached_property as cp + +class A: + @cp + def x(self): + return 'x' +[out] +from functools import cached_property as cp + +class A: + @cp + def x(self): ... + [case testStaticMethod] class A: @staticmethod @@ -337,6 +497,15 @@ class A: @classmethod def f(cls) -> None: ... +[case testClassMethod_inspect] +class A: + @classmethod + def f(cls): ... +[out] +class A: + @classmethod + def f(cls): ... + [case testIfMainCheck] def a(): ... if __name__ == '__main__': @@ -374,6 +543,23 @@ class B: ... class C: def f(self) -> None: ... +[case testNoSpacesBetweenEmptyClasses_inspect] +class X: + def g(self): ... +class A: ... +class B: ... +class C: + def f(self): ... +[out] +class X: + def g(self): ... + +class A: ... +class B: ... + +class C: + def f(self): ... + [case testExceptionBaseClasses] class A(Exception): ... class B(ValueError): ... @@ -392,6 +578,17 @@ class A: class A: def __eq__(self): ... +[case testOmitSomeSpecialMethods_inspect] +class A: + def __str__(self): ... + def __repr__(self): ... + def __eq__(self): ... + def __getstate__(self): ... + def __setstate__(self, state): ... +[out] +class A: + def __eq__(self) -> bool: ... + -- Tests that will perform runtime imports of modules. -- Don't use `_import` suffix if there are unquoted forward references. @@ -400,6 +597,8 @@ __all__ = [] + ['f'] def f(): ... def g(): ... [out] +__all__ = ['f'] + def f() -> None: ... [case testOmitDefsNotInAll_semanal] @@ -407,8 +606,19 @@ __all__ = ['f'] def f(): ... def g(): ... [out] +__all__ = ['f'] + def f() -> None: ... +[case testOmitDefsNotInAll_inspect] +__all__ = [] + ['f'] +def f(): ... +def g(): ... +[out] +__all__ = ['f'] + +def f(): ... + [case testVarDefsNotInAll_import] __all__ = [] + ['f', 'g'] def f(): ... @@ -416,24 +626,53 @@ x = 1 y = 1 def g(): ... [out] +__all__ = ['f', 'g'] + def f() -> None: ... def g() -> None: ... +[case testVarDefsNotInAll_inspect] +__all__ = [] + ['f', 'g'] +def f(): ... +x = 1 +y = 1 +def g(): ... +[out] +__all__ = ['f', 'g'] + +def f(): ... +def g(): ... + [case testIncludeClassNotInAll_import] __all__ = [] + ['f'] def f(): ... class A: ... [out] +__all__ = ['f'] + def f() -> None: ... class A: ... +[case testIncludeClassNotInAll_inspect] +__all__ = [] + ['f'] +def f(): ... +class A: ... +[out] +__all__ = ['f'] + +def f(): ... + +class A: ... + [case testAllAndClass_import] __all__ = ['A'] class A: x = 1 def f(self): ... [out] +__all__ = ['A'] + class A: x: int def f(self) -> None: ... @@ -471,6 +710,8 @@ x = 1 [out] from re import match as match, sub as sub +__all__ = ['match', 'sub', 'x'] + x: int [case testExportModule_import] @@ -481,6 +722,8 @@ y = 2 [out] import re as re +__all__ = ['re', 'x'] + x: int [case testExportModule2_import] @@ -491,6 +734,8 @@ y = 2 [out] import re as re +__all__ = ['re', 'x'] + x: int [case testExportModuleAs_import] @@ -501,6 +746,8 @@ y = 2 [out] import re as rex +__all__ = ['rex', 'x'] + x: int [case testExportModuleInPackage_import] @@ -509,6 +756,8 @@ __all__ = ['p'] [out] import urllib.parse as p +__all__ = ['p'] + [case testExportPackageOfAModule_import] import urllib.parse __all__ = ['urllib'] @@ -516,6 +765,8 @@ __all__ = ['urllib'] [out] import urllib as urllib +__all__ = ['urllib'] + [case testRelativeImportAll] from .x import * [out] @@ -528,6 +779,8 @@ x = 1 class C: def g(self): ... [out] +__all__ = ['f', 'x', 'C', 'g'] + def f() -> None: ... x: int @@ -538,6 +791,25 @@ class C: # Names in __all__ with no definition: # g +[case testCommentForUndefinedName_inspect] +__all__ = ['f', 'x', 'C', 'g'] +def f(): ... +x = 1 +class C: + def g(self): ... +[out] +__all__ = ['f', 'x', 'C', 'g'] + +def f(): ... + +x: int + +class C: + def g(self): ... + +# Names in __all__ with no definition: +# g + [case testIgnoreSlots] class A: __slots__ = () @@ -551,6 +823,13 @@ class A: [out] class A: ... +[case testSkipPrivateProperty_inspect] +class A: + @property + def _foo(self): ... +[out] +class A: ... + [case testIncludePrivateProperty] # flags: --include-private class A: @@ -561,6 +840,16 @@ class A: @property def _foo(self) -> None: ... +[case testIncludePrivateProperty_inspect] +# flags: --include-private +class A: + @property + def _foo(self): ... +[out] +class A: + @property + def _foo(self): ... + [case testSkipPrivateStaticAndClassMethod] class A: @staticmethod @@ -570,6 +859,15 @@ class A: [out] class A: ... +[case testSkipPrivateStaticAndClassMethod_inspect] +class A: + @staticmethod + def _foo(): ... + @classmethod + def _bar(cls): ... +[out] +class A: ... + [case testIncludePrivateStaticAndClassMethod] # flags: --include-private class A: @@ -584,9 +882,24 @@ class A: @classmethod def _bar(cls) -> None: ... +[case testIncludePrivateStaticAndClassMethod_inspect] +# flags: --include-private +class A: + @staticmethod + def _foo(): ... + @classmethod + def _bar(cls): ... +[out] +class A: + @staticmethod + def _foo(): ... + @classmethod + def _bar(cls): ... + [case testNamedtuple] -import collections, x +import collections, typing, x X = collections.namedtuple('X', ['a', 'b']) +Y = typing.NamedTuple('Y', [('a', int), ('b', str)]) [out] from _typeshed import Incomplete from typing import NamedTuple @@ -595,13 +908,75 @@ class X(NamedTuple): a: Incomplete b: Incomplete +class Y(NamedTuple): + a: int + b: str + +[case testNamedTupleClassSyntax_semanal] +from typing import NamedTuple + +class A(NamedTuple): + x: int + y: str = 'a' + +class B(A): + z1: str + z2 = 1 + z3: str = 'b' + +class RegularClass: + x: int + y: str = 'a' + class NestedNamedTuple(NamedTuple): + x: int + y: str = 'a' + z: str = 'b' +[out] +from typing import NamedTuple + +class A(NamedTuple): + x: int + y: str = ... + +class B(A): + z1: str + z2: int + z3: str + +class RegularClass: + x: int + y: str + class NestedNamedTuple(NamedTuple): + x: int + y: str = ... + z: str + + +[case testNestedClassInNamedTuple_semanal-xfail] +from typing import NamedTuple + +# TODO: make sure that nested classes in `NamedTuple` are supported: +class NamedTupleWithNestedClass(NamedTuple): + class Nested: + x: int + y: str = 'a' +[out] +from typing import NamedTuple + +class NamedTupleWithNestedClass(NamedTuple): + class Nested: + x: int + y: str + [case testEmptyNamedtuple] -import collections +import collections, typing X = collections.namedtuple('X', []) +Y = typing.NamedTuple('Y', []) [out] from typing import NamedTuple class X(NamedTuple): ... +class Y(NamedTuple): ... [case testNamedtupleAltSyntax] from collections import namedtuple, xx @@ -641,8 +1016,10 @@ class X(NamedTuple): [case testNamedtupleWithUnderscore] from collections import namedtuple as _namedtuple +from typing import NamedTuple as _NamedTuple def f(): ... X = _namedtuple('X', 'a b') +Y = _NamedTuple('Y', [('a', int), ('b', str)]) def g(): ... [out] from _typeshed import Incomplete @@ -654,6 +1031,10 @@ class X(NamedTuple): a: Incomplete b: Incomplete +class Y(NamedTuple): + a: int + b: str + def g() -> None: ... [case testNamedtupleBaseClass] @@ -672,10 +1053,14 @@ class Y(_X): ... [case testNamedtupleAltSyntaxFieldsTuples] from collections import namedtuple, xx +from typing import NamedTuple X = namedtuple('X', ()) Y = namedtuple('Y', ('a',)) Z = namedtuple('Z', ('a', 'b', 'c', 'd', 'e')) xx +R = NamedTuple('R', ()) +S = NamedTuple('S', (('a', int),)) +T = NamedTuple('T', (('a', int), ('b', str))) [out] from _typeshed import Incomplete from typing import NamedTuple @@ -692,13 +1077,86 @@ class Z(NamedTuple): d: Incomplete e: Incomplete +class R(NamedTuple): ... + +class S(NamedTuple): + a: int + +class T(NamedTuple): + a: int + b: str + [case testDynamicNamedTuple] from collections import namedtuple +from typing import NamedTuple N = namedtuple('N', ['x', 'y'] + ['z']) +M = NamedTuple('M', [('x', int), ('y', str)] + [('z', float)]) +class X(namedtuple('X', ['a', 'b'] + ['c'])): ... [out] from _typeshed import Incomplete N: Incomplete +M: Incomplete + +class X(Incomplete): ... + +[case testNamedTupleInClassBases] +import collections, typing +from collections import namedtuple +from typing import NamedTuple +class X(namedtuple('X', ['a', 'b'])): ... +class Y(NamedTuple('Y', [('a', int), ('b', str)])): ... +class R(collections.namedtuple('R', ['a', 'b'])): ... +class S(typing.NamedTuple('S', [('a', int), ('b', str)])): ... +[out] +import typing +from _typeshed import Incomplete +from typing import NamedTuple + +class X(NamedTuple('X', [('a', Incomplete), ('b', Incomplete)])): ... +class Y(NamedTuple('Y', [('a', int), ('b', str)])): ... +class R(NamedTuple('R', [('a', Incomplete), ('b', Incomplete)])): ... +class S(typing.NamedTuple('S', [('a', int), ('b', str)])): ... + +[case testNotNamedTuple] +from not_collections import namedtuple +from not_typing import NamedTuple +from collections import notnamedtuple +from typing import NotNamedTuple +X = namedtuple('X', ['a', 'b']) +Y = notnamedtuple('Y', ['a', 'b']) +Z = NamedTuple('Z', [('a', int), ('b', str)]) +W = NotNamedTuple('W', [('a', int), ('b', str)]) +[out] +from _typeshed import Incomplete + +X: Incomplete +Y: Incomplete +Z: Incomplete +W: Incomplete + +[case testNamedTupleFromImportAlias] +import collections as c +import typing as t +import typing_extensions as te +X = c.namedtuple('X', ['a', 'b']) +Y = t.NamedTuple('Y', [('a', int), ('b', str)]) +Z = te.NamedTuple('Z', [('a', int), ('b', str)]) +[out] +from _typeshed import Incomplete +from typing import NamedTuple + +class X(NamedTuple): + a: Incomplete + b: Incomplete + +class Y(NamedTuple): + a: int + b: str + +class Z(NamedTuple): + a: int + b: str [case testArbitraryBaseClass] import x @@ -742,11 +1200,78 @@ class D(Generic[T]): ... [out] class D(Generic[T]): ... +[case testGenericClass_semanal] +from typing import Generic, TypeVar +T = TypeVar('T') +class D(Generic[T]): ... +[out] +from typing import Generic, TypeVar + +T = TypeVar('T') + +class D(Generic[T]): ... + +[case testGenericClassTypeVarTuple] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack +Ts = TypeVarTuple('Ts') +class D(Generic[Unpack[Ts]]): ... +[out] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple('Ts') + +class D(Generic[Unpack[Ts]]): ... + +[case testGenericClassTypeVarTuple_semanal] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack +Ts = TypeVarTuple('Ts') +class D(Generic[Unpack[Ts]]): ... +[out] +from typing import Generic +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple('Ts') + +class D(Generic[Unpack[Ts]]): ... + +[case testGenericClassTypeVarTuplePy311] +# flags: --python-version=3.11 +from typing import Generic, TypeVarTuple +Ts = TypeVarTuple('Ts') +class D(Generic[*Ts]): ... +[out] +from typing import Generic, TypeVarTuple + +Ts = TypeVarTuple('Ts') + +class D(Generic[*Ts]): ... + +[case testGenericClassTypeVarTuplePy311_semanal] +# flags: --python-version=3.11 +from typing import Generic, TypeVarTuple +Ts = TypeVarTuple('Ts') +class D(Generic[*Ts]): ... +[out] +from typing import Generic, TypeVarTuple + +Ts = TypeVarTuple('Ts') + +class D(Generic[*Ts]): ... + [case testObjectBaseClass] class A(object): ... [out] class A: ... +[case testObjectBaseClassWithImport] +import builtins as b +class A(b.object): ... +[out] +class A: ... + [case testEmptyLines] def x(): ... def f(): @@ -820,8 +1345,8 @@ from _typeshed import Incomplete class A: x: Incomplete - def __init__(self, a: Incomplete | None = ...) -> None: ... - def method(self, a: Incomplete | None = ...) -> None: ... + def __init__(self, a: Incomplete | None = None) -> None: ... + def method(self, a: Incomplete | None = None) -> None: ... [case testAnnotationImportsFrom] import foo @@ -851,9 +1376,8 @@ x: List[collections.defaultdict] [out] import collections -from typing import List -x: List[collections.defaultdict] +x: list[collections.defaultdict] [case testAnnotationFwRefs] @@ -873,11 +1397,16 @@ y: C [case testTypeVarPreserved] tv = TypeVar('tv') +ps = ParamSpec('ps') +tvt = TypeVarTuple('tvt') [out] from typing import TypeVar +from typing_extensions import ParamSpec, TypeVarTuple tv = TypeVar('tv') +ps = ParamSpec('ps') +tvt = TypeVarTuple('tvt') [case testTypeVarArgsPreserved] tv = TypeVar('tv', int, str) @@ -889,11 +1418,60 @@ tv = TypeVar('tv', int, str) [case testTypeVarNamedArgsPreserved] tv = TypeVar('tv', bound=bool, covariant=True) +ps = ParamSpec('ps', bound=bool, covariant=True) [out] from typing import TypeVar +from typing_extensions import ParamSpec tv = TypeVar('tv', bound=bool, covariant=True) +ps = ParamSpec('ps', bound=bool, covariant=True) + +[case TypeVarImportAlias] +from typing import TypeVar as t_TV, ParamSpec as t_PS +from typing_extensions import TypeVar as te_TV, TypeVarTuple as te_TVT +from x import TypeVar as x_TV + +T = t_TV('T') +U = te_TV('U') +V = x_TV('V') + +PS = t_PS('PS') +TVT = te_TVT('TVT') + +[out] +from _typeshed import Incomplete +from typing import ParamSpec as t_PS, TypeVar as t_TV +from typing_extensions import TypeVar as te_TV, TypeVarTuple as te_TVT + +T = t_TV('T') +U = te_TV('U') +V: Incomplete +PS = t_PS('PS') +TVT = te_TVT('TVT') + +[case testTypeVarFromImportAlias] +import typing as t +import typing_extensions as te +import x + +T = t.TypeVar('T') +U = te.TypeVar('U') +V = x.TypeVar('V') + +PS = t.ParamSpec('PS') +TVT = te.TypeVarTuple('TVT') + +[out] +import typing as t +import typing_extensions as te +from _typeshed import Incomplete + +T = t.TypeVar('T') +U = te.TypeVar('U') +V: Incomplete +PS = t.ParamSpec('PS') +TVT = te.TypeVarTuple('TVT') [case testTypeAliasPreserved] alias = str @@ -1043,6 +1621,9 @@ def h1(): def h2(): yield return "abc" +def h3(): + yield + return None def all(): x = yield 123 return "abc" @@ -1054,6 +1635,7 @@ def f() -> Generator[Incomplete, None, None]: ... def g() -> Generator[None, Incomplete, None]: ... def h1() -> Generator[None, None, None]: ... def h2() -> Generator[None, None, Incomplete]: ... +def h3() -> Generator[None, None, None]: ... def all() -> Generator[Incomplete, Incomplete, Incomplete]: ... [case testFunctionYieldsNone] @@ -1082,6 +1664,69 @@ class Generator: ... def f() -> _Generator[Incomplete, None, None]: ... +[case testGeneratorYieldFrom] +def g1(): + yield from x +def g2(): + y = yield from x +def g3(): + yield from x + return +def g4(): + yield from x + return None +def g5(): + yield from x + return z + +[out] +from _typeshed import Incomplete +from collections.abc import Generator + +def g1() -> Generator[Incomplete, Incomplete, None]: ... +def g2() -> Generator[Incomplete, Incomplete, None]: ... +def g3() -> Generator[Incomplete, Incomplete, None]: ... +def g4() -> Generator[Incomplete, Incomplete, None]: ... +def g5() -> Generator[Incomplete, Incomplete, Incomplete]: ... + +[case testGeneratorYieldAndYieldFrom] +def g1(): + yield x1 + yield from x2 +def g2(): + yield x1 + y = yield from x2 +def g3(): + y = yield x1 + yield from x2 +def g4(): + yield x1 + yield from x2 + return +def g5(): + yield x1 + yield from x2 + return None +def g6(): + yield x1 + yield from x2 + return z +def g7(): + yield None + yield from x2 + +[out] +from _typeshed import Incomplete +from collections.abc import Generator + +def g1() -> Generator[Incomplete, Incomplete, None]: ... +def g2() -> Generator[Incomplete, Incomplete, None]: ... +def g3() -> Generator[Incomplete, Incomplete, None]: ... +def g4() -> Generator[Incomplete, Incomplete, None]: ... +def g5() -> Generator[Incomplete, Incomplete, None]: ... +def g6() -> Generator[Incomplete, Incomplete, Incomplete]: ... +def g7() -> Generator[Incomplete, Incomplete, None]: ... + [case testCallable] from typing import Callable @@ -1419,6 +2064,19 @@ class Outer: class Inner: ... A = Outer.Inner +-- needs improvement +[case testNestedClass_inspect] +class Outer: + class Inner: + pass + +A = Outer.Inner +[out] +class Outer: + class Inner: ... + +class A: ... + [case testFunctionAlias_semanal] from asyncio import coroutine @@ -1557,9 +2215,9 @@ funcs: Dict[Any, Any] f = funcs[a.f] [out] from _typeshed import Incomplete -from typing import Any, Dict +from typing import Any -funcs: Dict[Any, Any] +funcs: dict[Any, Any] f: Incomplete [case testAbstractMethodNameExpr] @@ -1652,6 +2310,25 @@ class A: def f(x) -> None: ... def g(x, y: str): ... +class A: + def f(self, x) -> None: ... + +-- Same as above +[case testFunctionPartiallyAnnotated_inspect] +def f(x) -> None: + pass + +def g(x, y: str): + pass + +class A: + def f(self, x) -> None: + pass + +[out] +def f(x) -> None: ... +def g(x, y: str): ... + class A: def f(self, x) -> None: ... @@ -1672,6 +2349,24 @@ def f(x: Any): ... def g(x, y: Any) -> str: ... def h(x: Any) -> str: ... +-- Same as above +[case testExplicitAnyArg_inspect] +from typing import Any + +def f(x: Any): + pass +def g(x, y: Any) -> str: + pass +def h(x: Any) -> str: + pass + +[out] +from typing import Any + +def f(x: Any): ... +def g(x, y: Any) -> str: ... +def h(x: Any) -> str: ... + [case testExplicitReturnedAny] from typing import Any @@ -1700,10 +2395,12 @@ class B: @property def x(self): return 'x' - @x.setter def x(self, value): self.y = 'y' + @x.deleter + def x(self): + del self.y [out] class A: @@ -1717,6 +2414,8 @@ class B: y: str @x.setter def x(self, value) -> None: ... + @x.deleter + def x(self) -> None: ... [case testMisplacedTypeComment] def f(): @@ -1739,6 +2438,8 @@ else: [out] import cookielib as cookielib +__all__ = ['cookielib'] + [case testCannotCalculateMRO_semanal] class X: pass @@ -1775,10 +2476,40 @@ else: [out] MYPY: bool -class A: ... +class A: ... + +class C(A): + def f(self) -> None: ... + +[case testAbstractPropertyImportAlias] +import abc as abc_alias + +class A: + @abc_alias.abstractproperty + def x(self): pass + +[out] +import abc as abc_alias + +class A: + @property + @abc_alias.abstractmethod + def x(self): ... + +[case testAbstractPropertyFromImportAlias] +from abc import abstractproperty as ap + +class A: + @ap + def x(self): pass + +[out] +import abc -class C(A): - def f(self) -> None: ... +class A: + @property + @abc.abstractmethod + def x(self): ... [case testAbstractProperty1_semanal] import other @@ -1846,7 +2577,7 @@ from _typeshed import Incomplete as _Incomplete Y: _Incomplete -def g(x: _Incomplete | None = ...) -> None: ... +def g(x: _Incomplete | None = None) -> None: ... x: _Incomplete @@ -1973,6 +2704,28 @@ def g() -> None: ... +[case testTestFiles_inspect] +# modules: p p.x p.tests p.tests.test_foo + +[file p/__init__.py] +def f(): pass + +[file p/x.py] +def g(): pass + +[file p/tests/__init__.py] + +[file p/tests/test_foo.py] +def test_thing(): pass + +[out] +# p/__init__.pyi +def f(): ... +# p/x.pyi +def g(): ... + + + [case testVerboseFlag] # Just test that --verbose does not break anything in a basic test case. # flags: --verbose @@ -2132,6 +2885,8 @@ class A: pass # p/__init__.pyi from p.a import A +__all__ = ['a'] + a: A # p/a.pyi class A: ... @@ -2274,6 +3029,8 @@ __uri__ = '' __version__ = '' [out] +from m import __version__ as __version__ + class A: ... [case testHideDunderModuleAttributesWithAll_import] @@ -2303,13 +3060,16 @@ __uri__ = '' __version__ = '' [out] +from m import __about__ as __about__, __author__ as __author__, __version__ as __version__ + +__all__ = ['__about__', '__author__', '__version__'] [case testAttrsClass_semanal] -import attr +import attrs -@attr.s +@attrs.define class C: - x = attr.ib() + x = attrs.field() [out] from _typeshed import Incomplete @@ -2360,9 +3120,9 @@ y: b.Y z: p.a.X [out] +import p.a import p.a as a import p.b as b -import p.a x: a.X y: b.Y @@ -2375,7 +3135,7 @@ from p import a x: a.X [out] -from p import a as a +from p import a x: a.X @@ -2397,7 +3157,7 @@ from p import a x: a.X [out] -from p import a as a +from p import a x: a.X @@ -2447,6 +3207,60 @@ import p.a x: a.X y: p.a.Y +[case testNestedImports] +import p +import p.m1 +import p.m2 + +x: p.X +y: p.m1.Y +z: p.m2.Z + +[out] +import p +import p.m1 +import p.m2 + +x: p.X +y: p.m1.Y +z: p.m2.Z + +[case testNestedImportsAliased] +import p as t +import p.m1 as pm1 +import p.m2 as pm2 + +x: t.X +y: pm1.Y +z: pm2.Z + +[out] +import p as t +import p.m1 as pm1 +import p.m2 as pm2 + +x: t.X +y: pm1.Y +z: pm2.Z + +[case testNestedFromImports] +from p import m1 +from p.m1 import sm1 +from p.m2 import sm2 + +x: m1.X +y: sm1.Y +z: sm2.Z + +[out] +from p import m1 +from p.m1 import sm1 +from p.m2 import sm2 + +x: m1.X +y: sm1.Y +z: sm2.Z + [case testOverload_fromTypingImport] from typing import Tuple, Union, overload @@ -2475,19 +3289,18 @@ def f(*args: Union[int, Tuple[int, int]]) -> int: [out] -from typing import Tuple, overload +from typing import overload class A: @overload def f(self, x: int, y: int) -> int: ... @overload - def f(self, x: Tuple[int, int]) -> int: ... - + def f(self, x: tuple[int, int]) -> int: ... @overload def f(x: int, y: int) -> int: ... @overload -def f(x: Tuple[int, int]) -> int: ... +def f(x: tuple[int, int]) -> int: ... [case testOverload_fromTypingExtensionsImport] from typing import Tuple, Union @@ -2518,20 +3331,18 @@ def f(*args: Union[int, Tuple[int, int]]) -> int: [out] -from typing import Tuple from typing_extensions import overload class A: @overload def f(self, x: int, y: int) -> int: ... @overload - def f(self, x: Tuple[int, int]) -> int: ... - + def f(self, x: tuple[int, int]) -> int: ... @overload def f(x: int, y: int) -> int: ... @overload -def f(x: Tuple[int, int]) -> int: ... +def f(x: tuple[int, int]) -> int: ... [case testOverload_importTyping] import typing @@ -2594,23 +3405,22 @@ class A: @typing.overload def f(self, x: int, y: int) -> int: ... @typing.overload - def f(self, x: typing.Tuple[int, int]) -> int: ... + def f(self, x: tuple[int, int]) -> int: ... @typing.overload @classmethod def g(cls, x: int, y: int) -> int: ... @typing.overload @classmethod - def g(cls, x: typing.Tuple[int, int]) -> int: ... - + def g(cls, x: tuple[int, int]) -> int: ... @typing.overload def f(x: int, y: int) -> int: ... @typing.overload -def f(x: typing.Tuple[int, int]) -> int: ... +def f(x: tuple[int, int]) -> int: ... @typing_extensions.overload def g(x: int, y: int) -> int: ... @typing_extensions.overload -def g(x: typing.Tuple[int, int]) -> int: ... +def g(x: tuple[int, int]) -> int: ... [case testOverload_importTypingAs] import typing as t @@ -2673,23 +3483,43 @@ class A: @t.overload def f(self, x: int, y: int) -> int: ... @t.overload - def f(self, x: t.Tuple[int, int]) -> int: ... + def f(self, x: tuple[int, int]) -> int: ... @t.overload @classmethod def g(cls, x: int, y: int) -> int: ... @t.overload @classmethod - def g(cls, x: t.Tuple[int, int]) -> int: ... - + def g(cls, x: tuple[int, int]) -> int: ... @t.overload def f(x: int, y: int) -> int: ... @t.overload -def f(x: t.Tuple[int, int]) -> int: ... +def f(x: tuple[int, int]) -> int: ... @te.overload def g(x: int, y: int) -> int: ... @te.overload -def g(x: t.Tuple[int, int]) -> int: ... +def g(x: tuple[int, int]) -> int: ... + +[case testOverloadFromImportAlias] +from typing import overload as t_overload +from typing_extensions import overload as te_overload + +@t_overload +def f(x: int, y: int) -> int: + ... + +@te_overload +def g(x: int, y: int) -> int: + ... + +[out] +from typing import overload as t_overload +from typing_extensions import overload as te_overload + +@t_overload +def f(x: int, y: int) -> int: ... +@te_overload +def g(x: int, y: int) -> int: ... [case testProtocol_semanal] from typing import Protocol, TypeVar @@ -2735,16 +3565,99 @@ class P(Protocol): [case testNonDefaultKeywordOnlyArgAfterAsterisk] def func(*, non_default_kwarg: bool, default_kwarg: bool = True): ... [out] -def func(*, non_default_kwarg: bool, default_kwarg: bool = ...): ... +def func(*, non_default_kwarg: bool, default_kwarg: bool = True): ... [case testNestedGenerator] -def f(): +def f1(): def g(): yield 0 - + return 0 +def f2(): + def g(): + yield from [0] return 0 [out] -def f(): ... +def f1(): ... +def f2(): ... + +[case testIncludeDocstrings] +# flags: --include-docstrings +class A: + """class docstring + + a multiline šŸ˜Š docstring""" + def func(): + """func docstring + don't forget to indent""" + ... + def nodoc(): + ... +class B: + def quoteA(): + '''func docstring with quotes"""\\n + and an end quote\'''' + ... + def quoteB(): + '''func docstring with quotes""" + \'\'\' + and an end quote\\"''' + ... + def quoteC(): + """func docstring with end quote\\\"""" + ... + def quoteD(): + r'''raw with quotes\"''' + ... +[out] +class A: + """class docstring + + a multiline šŸ˜Š docstring""" + def func() -> None: + """func docstring + don't forget to indent""" + def nodoc() -> None: ... + +class B: + def quoteA() -> None: + '''func docstring with quotes"""\\n + and an end quote\'''' + def quoteB() -> None: + '''func docstring with quotes""" + \'\'\' + and an end quote\\"''' + def quoteC() -> None: + '''func docstring with end quote\\"''' + def quoteD() -> None: + '''raw with quotes\\"''' + +[case testIgnoreDocstrings] +class A: + """class docstring + + a multiline docstring""" + def func(): + """func docstring + + don't forget to indent""" + def nodoc(): + ... + +class B: + def func(): + """func docstring""" + ... + def nodoc(): + ... + +[out] +class A: + def func() -> None: ... + def nodoc() -> None: ... + +class B: + def func() -> None: ... + def nodoc() -> None: ... [case testKnownMagicMethodsReturnTypes] class Some: @@ -2775,6 +3688,67 @@ class Some: def __float__(self) -> float: ... def __index__(self) -> int: ... +-- Same as above +[case testKnownMagicMethodsReturnTypes_inspect] +class Some: + def __len__(self): ... + def __length_hint__(self): ... + def __init__(self): ... + def __del__(self): ... + def __bool__(self): ... + def __bytes__(self): ... + def __format__(self, spec): ... + def __contains__(self, obj): ... + def __complex__(self): ... + def __int__(self): ... + def __float__(self): ... + def __index__(self): ... +[out] +class Some: + def __len__(self) -> int: ... + def __length_hint__(self) -> int: ... + def __init__(self) -> None: ... + def __del__(self) -> None: ... + def __bool__(self) -> bool: ... + def __bytes__(self) -> bytes: ... + def __format__(self, spec) -> str: ... + def __contains__(self, obj) -> bool: ... + def __complex__(self) -> complex: ... + def __int__(self) -> int: ... + def __float__(self) -> float: ... + def __index__(self) -> int: ... + + +[case testKnownMagicMethodsArgTypes] +class MismatchNames: + def __exit__(self, tp, val, tb): ... + +class MatchNames: + def __exit__(self, type, value, traceback): ... + +[out] +class MismatchNames: + def __exit__(self, tp: type[BaseException] | None, val: BaseException | None, tb: types.TracebackType | None) -> None: ... + +class MatchNames: + def __exit__(self, type: type[BaseException] | None, value: BaseException | None, traceback: types.TracebackType | None) -> None: ... + +-- Same as above (but can generate import statements) +[case testKnownMagicMethodsArgTypes_inspect] +class MismatchNames: + def __exit__(self, tp, val, tb): ... + +class MatchNames: + def __exit__(self, type, value, traceback): ... + +[out] +import types + +class MismatchNames: + def __exit__(self, tp: type[BaseException] | None, val: BaseException | None, tb: types.TracebackType | None): ... + +class MatchNames: + def __exit__(self, type: type[BaseException] | None, value: BaseException | None, traceback: types.TracebackType | None): ... [case testTypeVarPEP604Bound] from typing import TypeVar @@ -2793,3 +3767,578 @@ def f(x: str | None) -> None: ... a: str | int def f(x: str | None) -> None: ... + +[case testTypeddict] +import typing, x +X = typing.TypedDict('X', {'a': int, 'b': str}) +Y = typing.TypedDict('X', {'a': int, 'b': str}, total=False) +[out] +from typing_extensions import TypedDict + +class X(TypedDict): + a: int + b: str + +class Y(TypedDict, total=False): + a: int + b: str + +[case testTypeddictClassWithKeyword] +from typing import TypedDict +class MyDict(TypedDict, total=False): + foo: str + bar: int +[out] +from typing import TypedDict + +class MyDict(TypedDict, total=False): + foo: str + bar: int + +[case testTypeddictKeywordSyntax] +from typing import TypedDict + +X = TypedDict('X', a=int, b=str) +Y = TypedDict('X', a=int, b=str, total=False) +[out] +from typing_extensions import TypedDict + +class X(TypedDict): + a: int + b: str + +class Y(TypedDict, total=False): + a: int + b: str + +[case testTypeddictWithNonIdentifierOrKeywordKeys] +from typing import TypedDict +X = TypedDict('X', {'a-b': int, 'c': str}) +Y = TypedDict('X', {'a-b': int, 'c': str}, total=False) +Z = TypedDict('X', {'a': int, 'in': str}) +[out] +from typing import TypedDict + +X = TypedDict('X', {'a-b': int, 'c': str}) +Y = TypedDict('X', {'a-b': int, 'c': str}, total=False) +Z = TypedDict('X', {'a': int, 'in': str}) + +[case testEmptyTypeddict] +import typing +X = typing.TypedDict('X', {}) +Y = typing.TypedDict('Y', {}, total=False) +Z = typing.TypedDict('Z') +W = typing.TypedDict('W', total=False) +[out] +from typing_extensions import TypedDict + +class X(TypedDict): ... +class Y(TypedDict, total=False): ... +class Z(TypedDict): ... +class W(TypedDict, total=False): ... + +[case testTypeddictAliased] +from typing import TypedDict as t_TypedDict +from typing_extensions import TypedDict as te_TypedDict +def f(): ... +X = t_TypedDict('X', {'a': int, 'b': str}) +Y = te_TypedDict('Y', {'a': int, 'b': str}) +def g(): ... +[out] +from typing_extensions import TypedDict + +def f() -> None: ... + +class X(TypedDict): + a: int + b: str + +class Y(TypedDict): + a: int + b: str + +def g() -> None: ... + +[case testTypeddictFromImportAlias] +import typing as t +import typing_extensions as te +X = t.TypedDict('X', {'a': int, 'b': str}) +Y = te.TypedDict('Y', {'a': int, 'b': str}) +[out] +from typing_extensions import TypedDict + +class X(TypedDict): + a: int + b: str + +class Y(TypedDict): + a: int + b: str + +[case testNotTypeddict] +from x import TypedDict +import y +X = TypedDict('X', {'a': int, 'b': str}) +Y = y.TypedDict('Y', {'a': int, 'b': str}) +[out] +from _typeshed import Incomplete + +X: Incomplete +Y: Incomplete + +[case testTypeddictWithWrongAttributesType] +from typing import TypedDict +R = TypedDict("R", {"a": int, **{"b": str, "c": bytes}}) +S = TypedDict("S", [("b", str), ("c", bytes)]) +T = TypedDict("T", {"a": int}, b=str, total=False) +U = TypedDict("U", {"a": int}, totale=False) +V = TypedDict("V", {"a": int}, {"b": str}) +W = TypedDict("W", **{"a": int, "b": str}) +[out] +from _typeshed import Incomplete + +R: Incomplete +S: Incomplete +T: Incomplete +U: Incomplete +V: Incomplete +W: Incomplete + +[case testUseTypingName] +import collections +import typing +from typing import NamedTuple, TypedDict + +class Incomplete: ... +class Generator: ... +class NamedTuple: ... +class TypedDict: ... + +nt = collections.namedtuple("nt", "a b") +NT = typing.NamedTuple("NT", [("a", int), ("b", str)]) +NT1 = typing.NamedTuple("NT1", [("a", int)] + [("b", str)]) +NT2 = typing.NamedTuple("NT2", [(xx, int), ("b", str)]) +NT3 = typing.NamedTuple(xx, [("a", int), ("b", str)]) +TD = typing.TypedDict("TD", {"a": int, "b": str}) +TD1 = typing.TypedDict("TD1", {"a": int, "b": str}, totale=False) +TD2 = typing.TypedDict("TD2", {xx: int, "b": str}) +TD3 = typing.TypedDict(xx, {"a": int, "b": str}) + +def gen(): + y = yield x + return z + +def gen2(): + y = yield from x + return z + +class X(unknown_call("X", "a b")): ... +class Y(collections.namedtuple("Y", xx)): ... +[out] +from _typeshed import Incomplete as _Incomplete +from collections.abc import Generator as _Generator +from typing import NamedTuple as _NamedTuple +from typing_extensions import TypedDict as _TypedDict + +class Incomplete: ... +class Generator: ... +class NamedTuple: ... +class TypedDict: ... + +class nt(_NamedTuple): + a: _Incomplete + b: _Incomplete + +class NT(_NamedTuple): + a: int + b: str + +NT1: _Incomplete +NT2: _Incomplete +NT3: _Incomplete + +class TD(_TypedDict): + a: int + b: str + +TD1: _Incomplete +TD2: _Incomplete +TD3: _Incomplete + +def gen() -> _Generator[_Incomplete, _Incomplete, _Incomplete]: ... +def gen2() -> _Generator[_Incomplete, _Incomplete, _Incomplete]: ... + +class X(_Incomplete): ... +class Y(_Incomplete): ... + +[case testIgnoreLongDefaults] +def f(x='abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\ +abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\ +abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\ +abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz'): ... + +def g(x=b'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\ +abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\ +abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\ +abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz'): ... + +def h(x=123456789012345678901234567890123456789012345678901234567890\ +123456789012345678901234567890123456789012345678901234567890\ +123456789012345678901234567890123456789012345678901234567890\ +123456789012345678901234567890123456789012345678901234567890): ... + +[out] +def f(x: str = ...) -> None: ... +def g(x: bytes = ...) -> None: ... +def h(x: int = ...) -> None: ... + +[case testDefaultsOfBuiltinContainers] +def f(x=(), y=(1,), z=(1, 2)): ... +def g(x=[], y=[1, 2]): ... +def h(x={}, y={1: 2, 3: 4}): ... +def i(x={1, 2, 3}): ... +def j(x=[(1,"a"), (2,"b")]): ... + +[out] +def f(x=(), y=(1,), z=(1, 2)) -> None: ... +def g(x=[], y=[1, 2]) -> None: ... +def h(x={}, y={1: 2, 3: 4}) -> None: ... +def i(x={1, 2, 3}) -> None: ... +def j(x=[(1, 'a'), (2, 'b')]) -> None: ... + +[case testDefaultsOfBuiltinContainersWithNonTrivialContent] +def f(x=(1, u.v), y=(k(),), z=(w,)): ... +def g(x=[1, u.v], y=[k()], z=[w]): ... +def h(x={1: u.v}, y={k(): 2}, z={m: m}, w={**n}): ... +def i(x={u.v, 2}, y={3, k()}, z={w}): ... + +[out] +def f(x=..., y=..., z=...) -> None: ... +def g(x=..., y=..., z=...) -> None: ... +def h(x=..., y=..., z=..., w=...) -> None: ... +def i(x=..., y=..., z=...) -> None: ... + +[case testDataclass] +import dataclasses +import dataclasses as dcs +from dataclasses import dataclass, InitVar, KW_ONLY +from dataclasses import dataclass as dc +from typing import ClassVar + +@dataclasses.dataclass +class X: + a: int + b: str = "hello" + c: ClassVar + d: ClassVar = 200 + f: list[int] = field(init=False, default_factory=list) + g: int = field(default=2, kw_only=True) + _: KW_ONLY + h: int = 1 + i: InitVar[str] + j: InitVar = 100 + non_field = None + +@dcs.dataclass +class Y: ... + +@dataclass +class Z: ... + +@dc +class W: ... + +@dataclass(init=False, repr=False) +class V: ... + +[out] +import dataclasses +import dataclasses as dcs +from dataclasses import InitVar, KW_ONLY, dataclass, dataclass as dc +from typing import ClassVar + +@dataclasses.dataclass +class X: + a: int + b: str = ... + c: ClassVar + d: ClassVar = ... + f: list[int] = ... + g: int = ... + _: KW_ONLY + h: int = ... + i: InitVar[str] + j: InitVar = ... + non_field = ... + +@dcs.dataclass +class Y: ... +@dataclass +class Z: ... +@dc +class W: ... +@dataclass(init=False, repr=False) +class V: ... + +[case testDataclass_semanal] +from dataclasses import InitVar, dataclass, field +from typing import ClassVar + +@dataclass +class X: + a: int + b: InitVar[str] + c: str = "hello" + d: ClassVar + e: ClassVar = 200 + f: list[int] = field(init=False, default_factory=list) + g: int = field(default=2, kw_only=True) + h: int = 1 + i: InitVar = 100 + j: list[int] = field(default_factory=list) + non_field = None + +@dataclass(init=False, repr=False, frozen=True) +class Y: ... + +[out] +from dataclasses import InitVar, dataclass +from typing import ClassVar + +@dataclass +class X: + a: int + b: InitVar[str] + c: str = ... + d: ClassVar + e: ClassVar = ... + f: list[int] = ... + g: int = ... + h: int = ... + i: InitVar = ... + j: list[int] = ... + non_field = ... + def __init__(self, a, b, c=..., *, g=..., h=..., i=..., j=...) -> None: ... + +@dataclass(init=False, repr=False, frozen=True) +class Y: ... + +[case testDataclassWithKwOnlyField_semanal] +# flags: --python-version=3.10 +from dataclasses import dataclass, field, InitVar, KW_ONLY +from typing import ClassVar + +@dataclass +class X: + a: int + b: str = "hello" + c: ClassVar + d: ClassVar = 200 + f: list[int] = field(init=False, default_factory=list) + g: int = field(default=2, kw_only=True) + _: KW_ONLY + h: int = 1 + i: InitVar[str] + j: InitVar = 100 + non_field = None + +@dataclass(init=False, repr=False, frozen=True) +class Y: ... + +[out] +from dataclasses import InitVar, KW_ONLY, dataclass +from typing import ClassVar + +@dataclass +class X: + a: int + b: str = ... + c: ClassVar + d: ClassVar = ... + f: list[int] = ... + g: int = ... + _: KW_ONLY + h: int = ... + i: InitVar[str] + j: InitVar = ... + non_field = ... + def __init__(self, a, b=..., *, g=..., h=..., i, j=...) -> None: ... + +@dataclass(init=False, repr=False, frozen=True) +class Y: ... + +[case testDataclassWithExplicitGeneratedMethodsOverrides_semanal] +from dataclasses import dataclass + +@dataclass +class X: + a: int + def __init__(self, a: int, b: str = ...) -> None: ... + def __post_init__(self) -> None: ... + +[out] +from dataclasses import dataclass + +@dataclass +class X: + a: int + def __init__(self, a: int, b: str = ...) -> None: ... + def __post_init__(self) -> None: ... + +[case testDataclassInheritsFromAny_semanal] +from dataclasses import dataclass +import missing + +@dataclass +class X(missing.Base): + a: int + +@dataclass +class Y(missing.Base): + generated_args: str + generated_args_: str + generated_kwargs: float + generated_kwargs_: float + +[out] +import missing +from dataclasses import dataclass + +@dataclass +class X(missing.Base): + a: int + def __init__(self, *generated_args, a, **generated_kwargs) -> None: ... + +@dataclass +class Y(missing.Base): + generated_args: str + generated_args_: str + generated_kwargs: float + generated_kwargs_: float + def __init__(self, *generated_args__, generated_args, generated_args_, generated_kwargs, generated_kwargs_, **generated_kwargs__) -> None: ... + +[case testAlwaysUsePEP604Union] +import typing +import typing as t +from typing import Optional, Union, Optional as O, Union as U +import x + +union = Union[int, str] +bad_union = Union[int] +nested_union = Optional[Union[int, str]] +not_union = x.Union[int, str] +u = U[int, str] +o = O[int] + +def f1(a: Union["int", Optional[tuple[int, t.Optional[int]]]]) -> int: ... +def f2(a: typing.Union[int | x.Union[int, int], O[float]]) -> int: ... + +[out] +import x +from _typeshed import Incomplete + +union = int | str +bad_union = int +nested_union = int | str | None +not_union: Incomplete +u = int | str +o = int | None + +def f1(a: int | tuple[int, int | None] | None) -> int: ... +def f2(a: int | x.Union[int, int] | float | None) -> int: ... + +[case testTypingBuiltinReplacements] +import typing +import typing as t +from typing import Tuple +import typing_extensions +import typing_extensions as te +from typing_extensions import List, Type + +# builtins are not builtins +tuple = int +[list,] = float +dict, set, frozenset = str, float, int + +x: Tuple[t.Text, t.FrozenSet[typing.Type[float]]] +y: typing.List[int] +z: t.Dict[str, float] +v: typing.Set[int] +w: List[typing_extensions.Dict[te.FrozenSet[Type[int]], te.Tuple[te.Set[te.Text], ...]]] + +x_alias = Tuple[str, ...] +y_alias = typing.List[int] +z_alias = t.Dict[str, float] +v_alias = typing.Set[int] +w_alias = List[typing_extensions.Dict[str, te.Tuple[int, ...]]] + +[out] +from _typeshed import Incomplete +from builtins import dict as _dict, frozenset as _frozenset, list as _list, set as _set, tuple as _tuple + +tuple = int +list: Incomplete +dict: Incomplete +set: Incomplete +frozenset: Incomplete +x: _tuple[str, _frozenset[type[float]]] +y: _list[int] +z: _dict[str, float] +v: _set[int] +w: _list[_dict[_frozenset[type[int]], _tuple[_set[str], ...]]] +x_alias = _tuple[str, ...] +y_alias = _list[int] +z_alias = _dict[str, float] +v_alias = _set[int] +w_alias = _list[_dict[str, _tuple[int, ...]]] + +[case testHandlingNameCollisions] +# flags: --include-private +from typing import Tuple +tuple = int +_tuple = range +__tuple = map +x: Tuple[int, str] +[out] +from builtins import tuple as ___tuple + +tuple = int +_tuple = range +__tuple = map +x: ___tuple[int, str] + +[case testPEP570PosOnlyParams] +def f(x=0, /): ... +def f1(x: int, /): ... +def f2(x: int, y: float = 1, /): ... +def f3(x: int, /, y: float): ... +def f4(x: int, /, y: float = 1): ... +def f5(x: int, /, *, y: float): ... +def f6(x: int = 0, /, *, y: float): ... +def f7(x: int, /, *, y: float = 1): ... +def f8(x: int = 0, /, *, y: float = 1): ... + +[out] +def f(x: int = 0, /) -> None: ... +def f1(x: int, /): ... +def f2(x: int, y: float = 1, /): ... +def f3(x: int, /, y: float): ... +def f4(x: int, /, y: float = 1): ... +def f5(x: int, /, *, y: float): ... +def f6(x: int = 0, /, *, y: float): ... +def f7(x: int, /, *, y: float = 1): ... +def f8(x: int = 0, /, *, y: float = 1): ... + +[case testPreserveEmptyTuple] +ann: tuple[()] +alias = tuple[()] +def f(x: tuple[()]): ... +class C(tuple[()]): ... + +[out] +ann: tuple[()] +alias = tuple[()] + +def f(x: tuple[()]): ... + +class C(tuple[()]): ... diff --git a/test-data/unit/typexport-basic.test b/test-data/unit/typexport-basic.test index cd4071eb14ee..d78cf0f179f2 100644 --- a/test-data/unit/typexport-basic.test +++ b/test-data/unit/typexport-basic.test @@ -294,8 +294,8 @@ import typing x = () [builtins fixtures/primitives.pyi] [out] -NameExpr(2) : Tuple[] -TupleExpr(2) : Tuple[] +NameExpr(2) : Tuple[()] +TupleExpr(2) : Tuple[()] [case testInferTwoTypes] ## NameExpr @@ -313,8 +313,8 @@ def f() -> None: x = () [builtins fixtures/primitives.pyi] [out] -NameExpr(3) : Tuple[] -TupleExpr(3) : Tuple[] +NameExpr(3) : Tuple[()] +TupleExpr(3) : Tuple[()] -- Basic generics @@ -727,7 +727,7 @@ class A: pass class B: a = None # type: A [out] -LambdaExpr(2) : def (B) -> A +LambdaExpr(2) : def (x: B) -> A MemberExpr(2) : A NameExpr(2) : B @@ -756,7 +756,7 @@ class B: a = None # type: A [builtins fixtures/list.pyi] [out] -LambdaExpr(2) : def (B) -> builtins.list[A] +LambdaExpr(2) : def (x: B) -> builtins.list[A] ListExpr(2) : builtins.list[A] [case testLambdaAndHigherOrderFunction] @@ -775,7 +775,7 @@ map( CallExpr(9) : builtins.list[B] NameExpr(9) : def (f: def (A) -> B, a: builtins.list[A]) -> builtins.list[B] CallExpr(10) : B -LambdaExpr(10) : def (A) -> B +LambdaExpr(10) : def (x: A) -> B NameExpr(10) : def (a: A) -> B NameExpr(10) : builtins.list[A] NameExpr(10) : A @@ -795,7 +795,7 @@ map( [builtins fixtures/list.pyi] [out] NameExpr(10) : def (f: def (A) -> builtins.list[B], a: builtins.list[A]) -> builtins.list[B] -LambdaExpr(11) : def (A) -> builtins.list[B] +LambdaExpr(11) : def (x: A) -> builtins.list[B] ListExpr(11) : builtins.list[B] NameExpr(11) : def (a: A) -> B NameExpr(11) : builtins.list[A] @@ -817,7 +817,7 @@ map( -- context. Perhaps just fail instead? CallExpr(7) : builtins.list[Any] NameExpr(7) : def (f: builtins.list[def (A) -> Any], a: builtins.list[A]) -> builtins.list[Any] -LambdaExpr(8) : def (A) -> A +LambdaExpr(8) : def (x: A) -> A ListExpr(8) : builtins.list[def (A) -> Any] NameExpr(8) : A NameExpr(9) : builtins.list[A] @@ -838,7 +838,7 @@ map( [out] CallExpr(9) : builtins.list[B] NameExpr(9) : def (f: def (A) -> B, a: builtins.list[A]) -> builtins.list[B] -LambdaExpr(10) : def (A) -> B +LambdaExpr(10) : def (x: A) -> B MemberExpr(10) : B NameExpr(10) : A NameExpr(11) : builtins.list[A] @@ -860,7 +860,7 @@ map( CallExpr(9) : builtins.list[B] NameExpr(9) : def (f: def (A) -> B, a: builtins.list[A]) -> builtins.list[B] NameExpr(10) : builtins.list[A] -LambdaExpr(11) : def (A) -> B +LambdaExpr(11) : def (x: A) -> B MemberExpr(11) : B NameExpr(11) : A @@ -1055,6 +1055,21 @@ CallExpr(7) : builtins.str NameExpr(7) : def (x: builtins.str) -> builtins.str NameExpr(7) : S +[case testTypeVariableWithValueRestrictionInFunction] +## NameExpr +from typing import TypeVar + +T = TypeVar("T", int, str) + +def f(x: T) -> T: + y = 1 + return x +[out] +NameExpr(7) : builtins.int +NameExpr(7) : builtins.int +NameExpr(8) : builtins.int +NameExpr(8) : builtins.str + -- Binary operations -- ----------------- @@ -1197,7 +1212,7 @@ f( [builtins fixtures/list.pyi] [out] NameExpr(8) : Overload(def (x: builtins.int, f: def (builtins.int) -> builtins.int), def (x: builtins.str, f: def (builtins.str) -> builtins.str)) -LambdaExpr(9) : def (builtins.int) -> builtins.int +LambdaExpr(9) : def (x: builtins.int) -> builtins.int NameExpr(9) : builtins.int [case testExportOverloadArgTypeNested] @@ -1216,10 +1231,31 @@ f( lambda x: x) [builtins fixtures/list.pyi] [out] -LambdaExpr(9) : def (builtins.int) -> builtins.int -LambdaExpr(10) : def (builtins.int) -> builtins.int -LambdaExpr(12) : def (builtins.str) -> builtins.str -LambdaExpr(13) : def (builtins.str) -> builtins.str +LambdaExpr(9) : def (y: builtins.int) -> builtins.int +LambdaExpr(10) : def (x: builtins.int) -> builtins.int +LambdaExpr(12) : def (y: builtins.str) -> builtins.str +LambdaExpr(13) : def (x: builtins.str) -> builtins.str + +[case testExportOverloadArgTypeDict] +## DictExpr +from typing import TypeVar, Generic, Any, overload, Dict +T = TypeVar("T") +class Key(Generic[T]): ... +@overload +def f(x: Key[T], y: T) -> T: ... +@overload +def f(x: int, y: Any) -> Any: ... +def f(x, y): ... +d: Dict = {} +d.get( + "", {}) +f( + 2, {}) +[builtins fixtures/dict.pyi] +[out] +DictExpr(10) : builtins.dict[Any, Any] +DictExpr(12) : builtins.dict[Any, Any] +DictExpr(14) : builtins.dict[Any, Any] -- TODO -- diff --git a/test-requirements.in b/test-requirements.in new file mode 100644 index 000000000000..637f5b948055 --- /dev/null +++ b/test-requirements.in @@ -0,0 +1,19 @@ +# If you change this file (or mypy-requirements.txt or build-requirements.txt), please run: +# pip-compile --output-file=test-requirements.txt --strip-extras --allow-unsafe test-requirements.in + +-r mypy-requirements.txt +-r build-requirements.txt +attrs>=18.0 +black==24.3.0 # must match version in .pre-commit-config.yaml +filelock>=3.3.0 +# lxml 4.9.3 switched to manylinux_2_28, the wheel builder still uses manylinux2014 +lxml>=4.9.1,<4.9.3; (python_version<'3.11' or sys_platform!='win32') and python_version<'3.12' +pre-commit +pre-commit-hooks==4.5.0 +psutil>=4.0 +pytest>=8.1.0 +pytest-xdist>=1.34.0 +pytest-cov>=2.10.0 +ruff==0.2.0 # must match version in .pre-commit-config.yaml +setuptools>=65.5.1 +tomli>=1.1.0 # needed even on py311+ so the self check passes with --python-version 3.8 diff --git a/test-requirements.txt b/test-requirements.txt index aec11e87e96f..9005daab2876 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,20 +1,87 @@ --r mypy-requirements.txt --r build-requirements.txt -attrs>=18.0 -black==22.12.0 # must match version in .pre-commit-config.yaml -filelock>=3.3.0 -flake8==5.0.4 # must match version in .pre-commit-config.yaml -flake8-bugbear==22.12.6 # must match version in .pre-commit-config.yaml -flake8-noqa==1.3.0 # must match version in .pre-commit-config.yaml -isort[colors]==5.11.4 # must match version in .pre-commit-config.yaml -lxml>=4.9.1; (python_version<'3.11' or sys_platform!='win32') and python_version<'3.12' -psutil>=4.0 -# pytest 6.2.3 does not support Python 3.10 -pytest>=6.2.4 -pytest-xdist>=1.34.0 -pytest-forked>=1.3.0,<2.0.0 -pytest-cov>=2.10.0 -py>=1.5.2 -setuptools>=65.5.1 -six -tomli>=1.1.0 +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --output-file=test-requirements.txt --strip-extras test-requirements.in +# +attrs==23.1.0 + # via -r test-requirements.in +black==24.3.0 + # via -r test-requirements.in +cfgv==3.4.0 + # via pre-commit +click==8.1.7 + # via black +coverage==7.3.2 + # via pytest-cov +distlib==0.3.7 + # via virtualenv +execnet==2.0.2 + # via pytest-xdist +filelock==3.12.4 + # via + # -r test-requirements.in + # virtualenv +identify==2.5.30 + # via pre-commit +iniconfig==2.0.0 + # via pytest +lxml==4.9.2 ; (python_version < "3.11" or sys_platform != "win32") and python_version < "3.12" + # via -r test-requirements.in +mypy-extensions==1.0.0 + # via + # -r mypy-requirements.txt + # black +nodeenv==1.8.0 + # via pre-commit +packaging==23.2 + # via + # black + # pytest +pathspec==0.11.2 + # via black +platformdirs==3.11.0 + # via + # black + # virtualenv +pluggy==1.4.0 + # via pytest +pre-commit==3.5.0 + # via -r test-requirements.in +pre-commit-hooks==4.5.0 + # via -r test-requirements.in +psutil==5.9.6 + # via -r test-requirements.in +pytest==8.1.1 + # via + # -r test-requirements.in + # pytest-cov + # pytest-xdist +pytest-cov==4.1.0 + # via -r test-requirements.in +pytest-xdist==3.3.1 + # via -r test-requirements.in +pyyaml==6.0.1 + # via pre-commit +ruamel-yaml==0.17.40 + # via pre-commit-hooks +ruamel-yaml-clib==0.2.8 + # via ruamel-yaml +ruff==0.2.0 + # via -r test-requirements.in +tomli==2.0.1 + # via -r test-requirements.in +types-psutil==5.9.5.17 + # via -r build-requirements.txt +types-setuptools==68.2.0.0 + # via -r build-requirements.txt +typing-extensions==4.8.0 + # via -r mypy-requirements.txt +virtualenv==20.24.5 + # via pre-commit + +# The following packages are considered to be unsafe in a requirements file: +setuptools==68.2.2 + # via + # -r test-requirements.in + # nodeenv diff --git a/tox.ini b/tox.ini index 443f05dc8bcf..c2abd05d7b6c 100644 --- a/tox.ini +++ b/tox.ini @@ -2,13 +2,14 @@ minversion = 4.4.4 skip_missing_interpreters = {env:TOX_SKIP_MISSING_INTERPRETERS:True} envlist = - py37, py38, py39, py310, + py311, + py312, + docs, lint, type, - docs, isolated_build = true [testenv] @@ -21,12 +22,31 @@ passenv = deps = -rtest-requirements.txt commands = python -m pytest {posargs} +[testenv:dev] +description = generate a DEV environment, that has all project libraries +usedevelop = True +deps = + -rtest-requirements.txt + -rdocs/requirements-docs.txt +commands = + python -m pip list --format=columns + python -c 'import sys; print(sys.executable)' + {posargs} + +[testenv:docs] +description = invoke sphinx-build to build the HTML docs +passenv = + VERIFY_MYPY_ERROR_CODES +deps = -rdocs/requirements-docs.txt +commands = + sphinx-build -n -d "{toxworkdir}/docs_doctree" docs/source "{toxworkdir}/docs_out" --color -W -bhtml {posargs} + python -c 'import pathlib; print("documentation available under file://\{0\}".format(pathlib.Path(r"{toxworkdir}") / "docs_out" / "index.html"))' + [testenv:lint] description = check the code style -commands = - flake8 {posargs} - black --check --diff --color . - isort --check --diff --color . +skip_install = true +deps = pre-commit +commands = pre-commit run --all-files --show-diff-on-failure [testenv:type] description = type check ourselves @@ -35,22 +55,6 @@ passenv = MYPY_FORCE_COLOR MYPY_FORCE_TERMINAL_WIDTH commands = - python -m mypy --config-file mypy_self_check.ini -p mypy -p mypyc - python -m mypy --config-file mypy_self_check.ini misc --exclude misc/fix_annotate.py --exclude misc/async_matrix.py --exclude misc/sync-typeshed.py - -[testenv:docs] -description = invoke sphinx-build to build the HTML docs -deps = -rdocs/requirements-docs.txt -commands = - sphinx-build -d "{toxworkdir}/docs_doctree" docs/source "{toxworkdir}/docs_out" --color -W -bhtml {posargs} - python -c 'import pathlib; print("documentation available under file://\{0\}".format(pathlib.Path(r"{toxworkdir}") / "docs_out" / "index.html"))' - -[testenv:dev] -description = generate a DEV environment, that has all project libraries -usedevelop = True -deps = - -rtest-requirements.txt - -rdocs/requirements-docs.txt -commands = - python -m pip list --format=columns - python -c 'import sys; print(sys.executable)' + python runtests.py self + python -m mypy --config-file mypy_self_check.ini misc --exclude misc/sync-typeshed.py + python -m mypy --config-file mypy_self_check.ini test-data/unit/plugins